Skip to content

Commit

Permalink
Add color format_to overloads
Browse files Browse the repository at this point in the history
* Fix variable size basic_memory_buffer colorization
* Rewrite the color unit tests to test the same colors against multiple backends
* Fix an unused arguments warning on GCC that blocks the CI otherwise
* Ref fmtlib#1842
* Ref fmtlib#1593
  • Loading branch information
Naios committed Aug 31, 2020
1 parent e66ba16 commit 2f57faf
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 75 deletions.
50 changes: 48 additions & 2 deletions include/fmt/color.h
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,14 @@ template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
}

template <typename Char>
inline void reset_color(basic_memory_buffer<Char>& buffer) FMT_NOEXCEPT {
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
const char* begin = data::reset_color;
const char* end = begin + sizeof(data::reset_color) - 1;
buffer.append(begin, end);
}

template <typename Char>
void vformat_to(basic_memory_buffer<Char>& buf, const text_style& ts,
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> format_str,
basic_format_args<buffer_context<Char>> args) {
bool has_style = false;
Expand Down Expand Up @@ -563,6 +563,52 @@ inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
fmt::make_args_checked<Args...>(format_str, args...));
}

/** Formats a string with the given text_style and writes the output to ``out``.
*/
// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with
// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead.
template <typename OutputIt, typename S, typename Char = char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value)>
OutputIt vformat_to(
OutputIt out, const text_style& ts, const S& format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out));
detail::vformat_to(buf, ts, to_string_view(format_str), args);
return detail::get_iterator(buf);
}

/**
\rst
Formats arguments with the given text_style, writes the result to the output
iterator ``out`` and returns the iterator past the end of the output range.
**Example**::
std::vector<char> out;
fmt::format_to(std::back_inserter(out),
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); \endrst
*/
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt>::value&&
detail::is_string<S>::value)>
inline OutputIt format_to(OutputIt out, const text_style& ts, S& format_str,
Args&&... args) {
basic_format_args<buffer_context<type_identity_t<char_t<S>>>> store =
fmt::make_args_checked<Args...>(format_str, args...);
return vformat_to(out, ts, to_string_view(format_str), store);
}

template <typename S, typename... Args, size_t SIZE = inline_buffer_size,
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
inline typename buffer_context<Char>::iterator format_to(
basic_memory_buffer<Char, SIZE>& buf, const text_style& ts,
const S& format_str, Args&&... args) {
basic_format_args<buffer_context<type_identity_t<Char>>> store =
fmt::make_args_checked<Args...>(format_str, args...);
detail::vformat_to(buf, ts, to_string_view(format_str), store);
return detail::buffer_appender<Char>(buf);
}

FMT_END_NAMESPACE

#endif // FMT_COLOR_H_
190 changes: 118 additions & 72 deletions test/color-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -7,80 +7,126 @@

#include "fmt/color.h"

#include <iterator>
#include <string>
#include <utility>

#include "gtest-extra.h"

TEST(ColorsTest, ColorsPrint) {
EXPECT_WRITE(stdout, fmt::print(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_WRITE(
stdout,
fmt::print(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::bold, "bold"),
"\x1b[1mbold\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::italic, "italic"),
"\x1b[3mitalic\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::emphasis::underline, "underline"),
"\x1b[4munderline\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(fmt::emphasis::strikethrough, "strikethrough"),
"\x1b[9mstrikethrough\x1b[0m");
EXPECT_WRITE(
stdout,
fmt::print(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
EXPECT_WRITE(stderr, fmt::print(stderr, fmt::emphasis::bold, "bold error"),
"\x1b[1mbold error\x1b[0m");
EXPECT_WRITE(stderr, fmt::print(stderr, fg(fmt::color::blue), "blue log"),
"\x1b[38;2;000;000;255mblue log\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(fmt::text_style(), "hi"), "hi");
EXPECT_WRITE(stdout, fmt::print(fg(fmt::terminal_color::red), "tred"),
"\x1b[31mtred\x1b[0m");
EXPECT_WRITE(stdout, fmt::print(bg(fmt::terminal_color::cyan), "tcyan"),
"\x1b[46mtcyan\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(fg(fmt::terminal_color::bright_green), "tbgreen"),
"\x1b[92mtbgreen\x1b[0m");
EXPECT_WRITE(stdout,
fmt::print(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
"\x1b[105mtbmagenta\x1b[0m");
struct ColorFunctionPrintTag {};
struct ColorFunctionFormatTag {};
struct ColorFunctionFormatToOutputTag {};
struct ColorFunctionFormatToNTag {};
struct ColorFunctionFormatToBufferTag {};

template <typename T> struct ColorsTest;
template <> struct ColorsTest<ColorFunctionPrintTag> : public testing::Test {
virtual ~ColorsTest();

template <typename... Args>
void expect_color(const std::string& expected, const fmt::text_style& ts,
Args&&... args) {
EXPECT_WRITE(stdout, fmt::print(stdout, ts, std::forward<Args>(args)...),
expected);
}
};

template <> struct ColorsTest<ColorFunctionFormatTag> : public testing::Test {
virtual ~ColorsTest();

template <typename... Args>
void expect_color(const std::string& expected, const fmt::text_style& ts,
Args&&... args) {
EXPECT_EQ(expected, fmt::format(ts, std::forward<Args>(args)...));
}
};
template <>
struct ColorsTest<ColorFunctionFormatToOutputTag> : public testing::Test {
virtual ~ColorsTest();

template <typename... Args>
void expect_color(const std::string& expected, const fmt::text_style& ts,
Args&&... args) {
std::string str;
fmt::format_to(std::back_inserter(str), ts, std::forward<Args>(args)...);
EXPECT_EQ(expected, str);
}
};
template <>
struct ColorsTest<ColorFunctionFormatToBufferTag> : public testing::Test {
virtual ~ColorsTest();

template <typename... Args>
void expect_color(const std::string& expected, const fmt::text_style& ts,
Args&&... args) {
fmt::memory_buffer buffer;
fmt::format_to(buffer, ts, std::forward<Args>(args)...);
EXPECT_EQ(expected,
std::string(buffer.data(), buffer.data() + buffer.size()));
}
};

// Clang: error: 'ColorsTest<ColorFunctionFormatToBufferTag>' has no out-of-line
// virtual method definitions
ColorsTest<ColorFunctionPrintTag>::~ColorsTest() {}
ColorsTest<ColorFunctionFormatTag>::~ColorsTest() {}
ColorsTest<ColorFunctionFormatToOutputTag>::~ColorsTest() {}
ColorsTest<ColorFunctionFormatToBufferTag>::~ColorsTest() {}

using types = ::testing::Types<ColorFunctionPrintTag, ColorFunctionFormatTag,
ColorFunctionFormatToOutputTag,
ColorFunctionFormatToBufferTag>;
TYPED_TEST_CASE(ColorsTest, types);

TYPED_TEST(ColorsTest, CanColorizeRBG) {
this->expect_color("\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m",
fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)");

this->expect_color("\x1b[38;2;000;000;255mblue\x1b[0m", fg(fmt::color::blue),
"blue");

this->expect_color(
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m",
fg(fmt::color::blue) | bg(fmt::color::red), "two color");
}

TYPED_TEST(ColorsTest, CanColorizeEmphasis) {
this->expect_color("\x1b[1mbold\x1b[0m", fmt::emphasis::bold, "bold");

this->expect_color("\x1b[3mitalic\x1b[0m", fmt::emphasis::italic, "italic");

this->expect_color("\x1b[4munderline\x1b[0m", fmt::emphasis::underline,
"underline");

this->expect_color("\x1b[9mstrikethrough\x1b[0m",
fmt::emphasis::strikethrough, "strikethrough");
}

TEST(ColorsTest, Format) {
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), L"rgb(255,20,30) wide"),
L"\x1b[38;2;255;020;030mrgb(255,20,30) wide\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_EQ(
fmt::format(fg(fmt::color::blue) | bg(fmt::color::red), "two color"),
"\x1b[38;2;000;000;255m\x1b[48;2;255;000;000mtwo color\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold"), "\x1b[1mbold\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::italic, "italic"),
"\x1b[3mitalic\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::underline, "underline"),
"\x1b[4munderline\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::strikethrough, "strikethrough"),
"\x1b[9mstrikethrough\x1b[0m");
EXPECT_EQ(
fmt::format(fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold"),
"\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m");
EXPECT_EQ(fmt::format(fmt::emphasis::bold, "bold error"),
"\x1b[1mbold error\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue log"),
"\x1b[38;2;000;000;255mblue log\x1b[0m");
EXPECT_EQ(fmt::format(fmt::text_style(), "hi"), "hi");
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "tred"),
"\x1b[31mtred\x1b[0m");
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::cyan), "tcyan"),
"\x1b[46mtcyan\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::bright_green), "tbgreen"),
"\x1b[92mtbgreen\x1b[0m");
EXPECT_EQ(fmt::format(bg(fmt::terminal_color::bright_magenta), "tbmagenta"),
"\x1b[105mtbmagenta\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::terminal_color::red), "{}", "foo"),
"\x1b[31mfoo\x1b[0m");
TYPED_TEST(ColorsTest, CanColorizeRBGEmphasisMixed) {
this->expect_color("\x1b[1m\x1b[38;2;000;000;255mblue/bold\x1b[0m",
fg(fmt::color::blue) | fmt::emphasis::bold, "blue/bold");

this->expect_color("\x1b[1mbold error\x1b[0m", fmt::emphasis::bold,
"bold error");

this->expect_color("\x1b[38;2;000;000;255mblue log\x1b[0m",
fg(fmt::color::blue), "blue log");
}

TYPED_TEST(ColorsTest, CanColorizeEmptyStyle) {
this->expect_color("hi", fmt::text_style(), "hi");
}

TYPED_TEST(ColorsTest, CanColorizeTerminalColor) {
this->expect_color("\x1b[31mtred\x1b[0m", fg(fmt::terminal_color::red),
"tred");

this->expect_color("\x1b[46mtcyan\x1b[0m", bg(fmt::terminal_color::cyan),
"tcyan");

this->expect_color("\x1b[92mtbgreen\x1b[0m",
fg(fmt::terminal_color::bright_green), "tbgreen");

this->expect_color("\x1b[105mtbmagenta\x1b[0m",
bg(fmt::terminal_color::bright_magenta), "tbmagenta");
}
8 changes: 7 additions & 1 deletion test/gtest-extra.h
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,13 @@ std::string read(fmt::file& f, size_t count);
read(file, fmt::string_view(expected_content).size()))

#else
# define EXPECT_WRITE(file, statement, expected_output) SUCCEED()
# define EXPECT_WRITE(file, statement, expected_output) \
{ \
(void)file; \
(void)statement; \
(void)expected_output; \
} \
SUCCEED()
#endif // FMT_USE_FCNTL

template <typename Mock> struct ScopedMock : testing::StrictMock<Mock> {
Expand Down

0 comments on commit 2f57faf

Please sign in to comment.