diff --git a/include/fmt/color.h b/include/fmt/color.h index f1b1f753f84a..5b51e5d8edb7 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -463,14 +463,14 @@ template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { } template -inline void reset_color(basic_memory_buffer& buffer) FMT_NOEXCEPT { +inline void reset_color(buffer& buffer) FMT_NOEXCEPT { const char* begin = data::reset_color; const char* end = begin + sizeof(data::reset_color) - 1; buffer.append(begin, end); } template -void vformat_to(basic_memory_buffer& buf, const text_style& ts, +void vformat_to(buffer& buf, const text_style& ts, basic_string_view format_str, basic_format_args> args) { bool has_style = false; @@ -563,6 +563,52 @@ inline std::basic_string format(const text_style& ts, const S& format_str, fmt::make_args_checked(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 with +// vformat_to(...) overload, so SFINAE on iterator type instead. +template , + FMT_ENABLE_IF(detail::is_output_iterator::value)> +OutputIt vformat_to( + OutputIt out, const text_style& ts, const S& format_str, + basic_format_args>> args) { + decltype(detail::get_buffer(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 out; + fmt::format_to(std::back_inserter(out), + fmt::emphasis::bold | fg(fmt::color::red), "{}", 42); \endrst + */ +template ::value&& + detail::is_string::value)> +inline OutputIt format_to(OutputIt out, const text_style& ts, S& format_str, + Args&&... args) { + basic_format_args>>> store = + fmt::make_args_checked(format_str, args...); + return vformat_to(out, ts, to_string_view(format_str), store); +} + +template ::value, char_t>> +inline typename buffer_context::iterator format_to( + basic_memory_buffer& buf, const text_style& ts, + const S& format_str, Args&&... args) { + basic_format_args>> store = + fmt::make_args_checked(format_str, args...); + detail::vformat_to(buf, ts, to_string_view(format_str), store); + return detail::buffer_appender(buf); +} + FMT_END_NAMESPACE #endif // FMT_COLOR_H_ diff --git a/test/color-test.cc b/test/color-test.cc index 454a0660d4cf..22aa39611802 100644 --- a/test/color-test.cc +++ b/test/color-test.cc @@ -7,80 +7,126 @@ #include "fmt/color.h" +#include +#include +#include + #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 struct ColorsTest; +template <> struct ColorsTest : public testing::Test { + virtual ~ColorsTest(); + + template + void expect_color(const std::string& expected, const fmt::text_style& ts, + Args&&... args) { + EXPECT_WRITE(stdout, fmt::print(stdout, ts, std::forward(args)...), + expected); + } +}; + +template <> struct ColorsTest : public testing::Test { + virtual ~ColorsTest(); + + template + void expect_color(const std::string& expected, const fmt::text_style& ts, + Args&&... args) { + EXPECT_EQ(expected, fmt::format(ts, std::forward(args)...)); + } +}; +template <> +struct ColorsTest : public testing::Test { + virtual ~ColorsTest(); + + template + 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)...); + EXPECT_EQ(expected, str); + } +}; +template <> +struct ColorsTest : public testing::Test { + virtual ~ColorsTest(); + + template + 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)...); + EXPECT_EQ(expected, + std::string(buffer.data(), buffer.data() + buffer.size())); + } +}; + +// Clang: error: 'ColorsTest' has no out-of-line +// virtual method definitions +ColorsTest::~ColorsTest() {} +ColorsTest::~ColorsTest() {} +ColorsTest::~ColorsTest() {} +ColorsTest::~ColorsTest() {} + +using types = ::testing::Types; +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"); } diff --git a/test/gtest-extra.h b/test/gtest-extra.h index 3ed8052b3fcb..0a79ff11c345 100644 --- a/test/gtest-extra.h +++ b/test/gtest-extra.h @@ -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 struct ScopedMock : testing::StrictMock {