diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 40138f0..fa0a549 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -24,6 +24,7 @@ PRIVATE yycc/encoding/iconv.cpp yycc/carton/pycodec.cpp + yycc/carton/termcolor.cpp yycc/carton/wcwidth.cpp ) target_sources(YYCCommonplace @@ -68,6 +69,7 @@ FILES yycc/encoding/iconv.hpp yycc/carton/pycodec.hpp + yycc/carton/termcolor.hpp yycc/carton/wcwidth.hpp ) # Setup header infomations diff --git a/src/YYCCLegacy/ConsoleHelper.hpp b/src/YYCCLegacy/ConsoleHelper.hpp index 32e96c8..ed29a01 100644 --- a/src/YYCCLegacy/ConsoleHelper.hpp +++ b/src/YYCCLegacy/ConsoleHelper.hpp @@ -11,77 +11,6 @@ */ namespace YYCC::ConsoleHelper { - /// @brief The head of ASCII escape code of black color. -#define YYCC_COLORHDR_BLACK "\033[30m" - /// @brief The head of ASCII escape code of red color. -#define YYCC_COLORHDR_RED "\033[31m" - /// @brief The head of ASCII escape code of green color. -#define YYCC_COLORHDR_GREEN "\033[32m" - /// @brief The head of ASCII escape code of yellow color. -#define YYCC_COLORHDR_YELLOW "\033[33m" - /// @brief The head of ASCII escape code of blue color. -#define YYCC_COLORHDR_BLUE "\033[34m" - /// @brief The head of ASCII escape code of magenta color. -#define YYCC_COLORHDR_MAGENTA "\033[35m" - /// @brief The head of ASCII escape code of cyan color. -#define YYCC_COLORHDR_CYAN "\033[36m" - /// @brief The head of ASCII escape code of white color. -#define YYCC_COLORHDR_WHITE "\033[37m" - - /// @brief The head of ASCII escape code of light black color. -#define YYCC_COLORHDR_LIGHT_BLACK "\033[90m" - /// @brief The head of ASCII escape code of light red color. -#define YYCC_COLORHDR_LIGHT_RED "\033[91m" - /// @brief The head of ASCII escape code of light green color. -#define YYCC_COLORHDR_LIGHT_GREEN "\033[92m" - /// @brief The head of ASCII escape code of light yellow color. -#define YYCC_COLORHDR_LIGHT_YELLOW "\033[93m" - /// @brief The head of ASCII escape code of light blue color. -#define YYCC_COLORHDR_LIGHT_BLUE "\033[94m" - /// @brief The head of ASCII escape code of light magenta color. -#define YYCC_COLORHDR_LIGHT_MAGENTA "\033[95m" - /// @brief The head of ASCII escape code of light cyan color. -#define YYCC_COLORHDR_LIGHT_CYAN "\033[96m" - /// @brief The head of ASCII escape code of light white color. -#define YYCC_COLORHDR_LIGHT_WHITE "\033[97m" - - /// @brief The tail of ASCII escape code of every color. -#define YYCC_COLORTAIL "\033[0m" - - /// @brief The ASCII escape code pair of black color. -#define YYCC_COLOR_BLACK(T) "\033[30m" T "\033[0m" - /// @brief The ASCII escape code pair of red color. -#define YYCC_COLOR_RED(T) "\033[31m" T "\033[0m" - /// @brief The ASCII escape code pair of green color. -#define YYCC_COLOR_GREEN(T) "\033[32m" T "\033[0m" - /// @brief The ASCII escape code pair of yellow color. -#define YYCC_COLOR_YELLOW(T) "\033[33m" T "\033[0m" - /// @brief The ASCII escape code pair of blue color. -#define YYCC_COLOR_BLUE(T) "\033[34m" T "\033[0m" - /// @brief The ASCII escape code pair of magenta color. -#define YYCC_COLOR_MAGENTA(T) "\033[35m" T "\033[0m" - /// @brief The ASCII escape code pair of cyan color. -#define YYCC_COLOR_CYAN(T) "\033[36m" T "\033[0m" - /// @brief The ASCII escape code pair of white color. -#define YYCC_COLOR_WHITE(T) "\033[37m" T "\033[0m" - - /// @brief The ASCII escape code pair of light black color. -#define YYCC_COLOR_LIGHT_BLACK(T) "\033[90m" T "\033[0m" - /// @brief The ASCII escape code pair of light red color. -#define YYCC_COLOR_LIGHT_RED(T) "\033[91m" T "\033[0m" - /// @brief The ASCII escape code pair of light green color. -#define YYCC_COLOR_LIGHT_GREEN(T) "\033[92m" T "\033[0m" - /// @brief The ASCII escape code pair of light yellow color. -#define YYCC_COLOR_LIGHT_YELLOW(T) "\033[93m" T "\033[0m" - /// @brief The ASCII escape code pair of light blue color. -#define YYCC_COLOR_LIGHT_BLUE(T) "\033[94m" T "\033[0m" - /// @brief The ASCII escape code pair of light magenta color. -#define YYCC_COLOR_LIGHT_MAGENTA(T) "\033[95m" T "\033[0m" - /// @brief The ASCII escape code pair of light cyan color. -#define YYCC_COLOR_LIGHT_CYAN(T) "\033[96m" T "\033[0m" - /// @brief The ASCII escape code pair of light white color. -#define YYCC_COLOR_LIGHT_WHITE(T) "\033[97m" T "\033[0m" - /** * @brief Enable console color support for Windows. * @details This actually is enable virtual console feature for \c stdout and \c stderr. diff --git a/src/yycc/carton/termcolor.cpp b/src/yycc/carton/termcolor.cpp new file mode 100644 index 0000000..b90364f --- /dev/null +++ b/src/yycc/carton/termcolor.cpp @@ -0,0 +1,234 @@ +#include "termcolor.hpp" +#include "../flag_enum.hpp" +#include "../string/reinterpret.hpp" +#include +#include + +#define FLAG_ENUM ::yycc::flag_enum +#define REINTERPRET ::yycc::string::reinterpret + +using namespace std::literals::string_view_literals; + +namespace yycc::carton::termcolor { + +#pragma region Lowlevel Functions + + const std::u8string_view foreground(Color color) { + switch (color) { + case Color::Default: + return u8""sv; + case Color::Black: + return u8"\033[30m"sv; + case Color::Red: + return u8"\033[31m"sv; + case Color::Green: + return u8"\033[32m"sv; + case Color::Yellow: + return u8"\033[33m"sv; + case Color::Blue: + return u8"\033[34m"sv; + case Color::Magenta: + return u8"\033[35m"sv; + case Color::Cyan: + return u8"\033[36m"sv; + case Color::White: + return u8"\033[37m"sv; + case Color::LightBlack: + return u8"\033[90m"sv; + case Color::LightRed: + return u8"\033[91m"sv; + case Color::LightGreen: + return u8"\033[92m"sv; + case Color::LightYellow: + return u8"\033[93m"sv; + case Color::LightBlue: + return u8"\033[94m"sv; + case Color::LightMagenta: + return u8"\033[95m"sv; + case Color::LightCyan: + return u8"\033[96m"sv; + case Color::LightWhite: + return u8"\033[97m"sv; + default: + throw std::invalid_argument("invalid color kind"); + } + } + + const std::u8string_view background(Color color) { + switch (color) { + case Color::Default: + return u8""sv; + case Color::Black: + return u8"\033[40m"sv; + case Color::Red: + return u8"\033[41m"sv; + case Color::Green: + return u8"\033[42m"sv; + case Color::Yellow: + return u8"\033[43m"sv; + case Color::Blue: + return u8"\033[44m"sv; + case Color::Magenta: + return u8"\033[45m"sv; + case Color::Cyan: + return u8"\033[46m"sv; + case Color::White: + return u8"\033[47m"sv; + case Color::LightBlack: + return u8"\033[100m"sv; + case Color::LightRed: + return u8"\033[101m"sv; + case Color::LightGreen: + return u8"\033[102m"sv; + case Color::LightYellow: + return u8"\033[103m"sv; + case Color::LightBlue: + return u8"\033[104m"sv; + case Color::LightMagenta: + return u8"\033[105m"sv; + case Color::LightCyan: + return u8"\033[106m"sv; + case Color::LightWhite: + return u8"\033[107m"sv; + default: + throw std::invalid_argument("invalid color kind"); + } + } + + const std::u8string_view style(Attribute attr) { + // Return for Default first because it can not pass following test + if (attr == Attribute::Default) { + return u8""sv; + } + + // Check whether it only has one flag + if (!std::has_single_bit(FLAG_ENUM::integer(attr))) { + throw std::invalid_argument("style() only accept single flag attribute"); + } + + // Return result + switch (attr) { + case Attribute::Bold: + return u8"\033[1m"sv; + case Attribute::Dark: + return u8"\033[2m"sv; + case Attribute::Italic: + return u8"\033[3m"sv; + case Attribute::Underline: + return u8"\033[4m"sv; + case Attribute::Blink: + return u8"\033[5m"sv; + case Attribute::Reverse: + return u8"\033[6m"sv; + case Attribute::Concealed: + return u8"\033[7m"sv; + default: + throw std::invalid_argument("invalid attribute kind"); + } + } + + /** + * @private + * @brief The possible maximum length of ANSI Escape Sequence used in this module. + * @details This const value is used for computing reserved size of final built string. + */ + static constexpr size_t ANSI_ESC_LEN = sizeof(u8"\033[000m") - 1; + + /** + * @private + * @brief Count how many single flags combine given attributes. + * @details + * For function styles() involving multiple font style ANSI Escape Sequence, + * this function may be useful for computing desired size of final result, + * to reduce useless memory re-allocation. + * @param[in] attrs Attributes for counting. + * @return The count of single flag. + */ + static size_t count_attribute_flags(Attribute attrs) { + return static_cast(std::popcount(FLAG_ENUM::integer(attrs))); + } + + /** + * @private + * @brief Append multiple font styles into given string. + * @details + * This function will decompose given font styles into single flag. + * And append its components one by one into given string. + * If there is enough reserved space in given string, + * there is no memory re-allocation happened. + * @remarks + * This function is served for styles() and colored(). + * @param[in] s The string to be appended. + * @param[in] attrs The attributes for writting. + */ + static void append_styles(std::u8string& s, Attribute attrs) { +#define CHECK_ATTR(probe) \ + if (FLAG_ENUM::has(attrs, probe)) s.append(termcolor::style(probe)); + + if (attrs != Attribute::Default) { + CHECK_ATTR(Attribute::Bold); + CHECK_ATTR(Attribute::Dark); + CHECK_ATTR(Attribute::Italic); + CHECK_ATTR(Attribute::Blink); + CHECK_ATTR(Attribute::Reverse); + CHECK_ATTR(Attribute::Concealed); + } + +#undef CHECK_ATTR + } + + std::u8string styles(Attribute attrs) { + // Prepare the result string + std::u8string rv; + rv.reserve(count_attribute_flags(attrs) * ANSI_ESC_LEN); + // Append styles and return + append_styles(rv, attrs); + return rv; + } + + const std::u8string_view reset() { + return u8"\033[0m"sv; + } + +#pragma endregion + +#pragma region Highlevel Functions + + std::u8string colored(const std::u8string_view& words, Color foreground, Color background, Attribute styles) { + // Calculate the expected size of result string. + // final count = styles count + 1 (foreground) + 1 (background) + 1 (reset) + std::u8string rv; + size_t ansi_esc_count = count_attribute_flags(styles) + 3; + rv.reserve(ansi_esc_count * ANSI_ESC_LEN + words.size()); + + // Append data one by one + rv.append(termcolor::foreground(foreground)); + rv.append(termcolor::background(background)); + append_styles(rv, styles); + rv.append(words); + rv.append(termcolor::reset()); + + // Return result + return rv; + } + + void cprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) { + dst << REINTERPRET::as_ordinary_view(colored(words, foreground, background, styles)); + } + + void ecprint(const std::u8string_view& words, Color foreground, Color background, Attribute styles) { + cprint(words, foreground, background, styles, std::cerr); + } + + void cprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles, std::ostream& dst) { + cprint(words, foreground, background, styles, dst); + dst << std::endl; + } + + void ecprintln(const std::u8string_view& words, Color foreground, Color background, Attribute styles) { + cprintln(words, foreground, background, styles, std::cerr); + } + +#pragma endregion + +} // namespace yycc::carton::termcolor diff --git a/src/yycc/carton/termcolor.hpp b/src/yycc/carton/termcolor.hpp new file mode 100644 index 0000000..624a302 --- /dev/null +++ b/src/yycc/carton/termcolor.hpp @@ -0,0 +1,166 @@ +#pragma once +#include +#include +#include + +/** + * @brief The namespace for terminal font color and style. + * @details + * This namespace provides functions to generate ANSI escape sequence for terminal font color and style. + * It also provides functions to add color and style for given string with ANSI Escape Sequence. + * + * This namespace is basically the immitation of the Python package with same name. + */ +namespace yycc::carton::termcolor { + +#pragma region Lowlevel Functions + + /** + * @brief The color of font. + */ + enum class Color { + Black, + Red, + Green, + Yellow, + Blue, + Magenta, + Cyan, + White, + LightBlack, + LightRed, + LightGreen, + LightYellow, + LightBlue, + LightMagenta, + LightCyan, + LightWhite, + Default + }; + + /** + * @brief Get ANSI escape sequence for foreground color + * @param[in] color The color to generate sequence for + * @return Gotten ANSI escape sequence + */ + const std::u8string_view foreground(Color color); + /** + * @brief Get ANSI escape sequence for background color + * @param[in] color The color to generate sequence for + * @return Gotten ANSI escape sequence + */ + const std::u8string_view background(Color color); + + /** + * @brief The attribute of font + * @remarks We define this enum as unsigned integral, so that we can use \c std::has_single_bit. + */ + enum class Attribute : uint32_t { + Default = 0, + Bold = 1 << 0, + Dark = 1 << 1, + Italic = 1 << 2, + Underline = 1 << 3, + Blink = 1 << 4, + Reverse = 1 << 5, + Concealed = 1 << 6 + }; + + /** + * @brief Get ANSI escape sequence for text style + * @details + * Please note that this function only support single attribute flag. + * If you want to use multiple attributes, please use styles() instead. + * + * However, the difference between this function and styles() is that + * there is no memory allocation in this function. + * It may have better performance that styles(). + * @param[in] attr Single attribute to generate sequence for + * @return Gotten ANSI escape sequence + * @throws std::invalid_argument if attribute is not a single flag + */ + const std::u8string_view style(Attribute attr); + /** + * @brief Generates ANSI escape sequence for multiple text styles + * @param[in] attrs Combination of attributes to generate sequences for + * @return Generated ANSI escape sequence + */ + std::u8string styles(Attribute attrs); + /** + * @brief Get ANSI escape sequence for reset style + * @return Gotten ANSI reset sequence + */ + const std::u8string_view reset(); + +#pragma endregion + +#pragma region Highlevel Functions + + /** + * @brief Add color and style for given string with ANSI Escape Sequence. + * @param[in] words The words to be decorated. + * @param[in] foreground The foreground of words. + * @param[in] background The background of words. + * @param[in] styles The font style of words. + * @return Decorated words. + */ + std::u8string colored(const std::u8string_view& words, + Color foreground = Color::Default, + Color background = Color::Default, + Attribute styles = Attribute::Default); + + /** + * @brief Print words into stream with given styles. + * @param[in] words The words to be printed. + * @param[in] foreground The foreground of words. + * @param[in] background The background of words. + * @param[in] styles The font style of words. + * @param[in] dst The stream written into. \c stdout in default. + */ + void cprint(const std::u8string_view& words = std::u8string_view(u8""), + Color foreground = Color::Default, + Color background = Color::Default, + Attribute styles = Attribute::Default, + std::ostream& dst = std::cout); + + /** + * @brief Print words into \c stderr with given styles. + * @param[in] words The words to be printed. + * @param[in] foreground The foreground of words. + * @param[in] background The background of words. + * @param[in] styles The font style of words. + */ + void ceprint(const std::u8string_view& words = std::u8string_view(u8""), + Color foreground = Color::Default, + Color background = Color::Default, + Attribute styles = Attribute::Default); + + /** + * @brief Print words into stream with given styles and break line. + * @param[in] words The words to be printed. + * @param[in] foreground The foreground of words. + * @param[in] background The background of words. + * @param[in] styles The font style of words. + * @param[in] dst The stream written into. \c stdout in default. + */ + void cprintln(const std::u8string_view& words = std::u8string_view(u8""), + Color foreground = Color::Default, + Color background = Color::Default, + Attribute styles = Attribute::Default, + std::ostream& dst = std::cout); + + /** + * @brief Print words into \c stderr with given styles and break line. + * @param[in] words The words to be printed. + * @param[in] foreground The foreground of words. + * @param[in] background The background of words. + * @param[in] styles The font style of words. + */ + void ceprintln(const std::u8string_view& words = std::u8string_view(u8""), + Color foreground = Color::Default, + Color background = Color::Default, + Attribute styles = Attribute::Default); + +#pragma endregion + +} // namespace yycc::carton::termcolor diff --git a/src/yycc/flag_enum.hpp b/src/yycc/flag_enum.hpp index 5edb045..6ff40dc 100644 --- a/src/yycc/flag_enum.hpp +++ b/src/yycc/flag_enum.hpp @@ -191,4 +191,17 @@ namespace yycc::flag_enum { return static_cast(static_cast(e)); } + /** + * @brief Cast given enum flags to its equvalent underlying integer value like performing static_cast>(e) + * @tparam TEnum Enum type for processing. + * @param e The enum flags to be cast. + * @return The equvalent integer value of given enum flag. + */ + template + requires(std::is_enum_v) + constexpr std::underlying_type_t integer(TEnum e) { + using ut = std::underlying_type_t; + return static_cast(e); + } + } // namespace yycc::flag_enum diff --git a/testbench/CMakeLists.txt b/testbench/CMakeLists.txt index 582f773..a55e3b3 100644 --- a/testbench/CMakeLists.txt +++ b/testbench/CMakeLists.txt @@ -33,6 +33,7 @@ PRIVATE yycc/windows/winfct.cpp yycc/carton/pycodec.cpp + yycc/carton/termcolor.cpp yycc/carton/wcwidth.cpp ) target_sources(YYCCTestbench diff --git a/testbench/yycc/carton/termcolor.cpp b/testbench/yycc/carton/termcolor.cpp new file mode 100644 index 0000000..979c69b --- /dev/null +++ b/testbench/yycc/carton/termcolor.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#define TERMCOLOR ::yycc::carton::termcolor +#define FLAG_ENUM ::yycc::flag_enum + +using namespace std::literals::string_view_literals; +using Color = TERMCOLOR::Color; +using Attribute = TERMCOLOR::Attribute; + +namespace yycctest::carton::termcolor { + + TEST(CartonTermcolor, Lowlevel) { + EXPECT_EQ(TERMCOLOR::foreground(Color::Default), u8""); + EXPECT_EQ(TERMCOLOR::foreground(Color::Red), u8"\033[31m"); + EXPECT_EQ(TERMCOLOR::foreground(Color::LightRed), u8"\033[91m"); + + EXPECT_EQ(TERMCOLOR::background(Color::Default), u8""); + EXPECT_EQ(TERMCOLOR::background(Color::Red), u8"\033[41m"); + EXPECT_EQ(TERMCOLOR::background(Color::LightRed), u8"\033[101m"); + + EXPECT_EQ(TERMCOLOR::style(Attribute::Default), u8""); + EXPECT_EQ(TERMCOLOR::style(Attribute::Italic), u8"\033[3m"); + EXPECT_EQ(TERMCOLOR::styles(FLAG_ENUM::merge(Attribute::Italic, Attribute::Bold)), + u8"\033[1m" + "\033[3m"); + + EXPECT_EQ(TERMCOLOR::reset(), u8"\033[0m"sv); + } + + TEST(CartonTermcolor, Highlevel) { + EXPECT_EQ(TERMCOLOR::colored(u8"hello"sv), u8"hello\033[0m"); + EXPECT_EQ(TERMCOLOR::colored(u8"hello"sv, Color::LightWhite, Color::Red, FLAG_ENUM::merge(Attribute::Italic, Attribute::Bold)), + u8"\033[97m" + "\033[41m" + "\033[1m" + "\033[3m" + "hello" + "\033[0m"); + } + +} // namespace yycctest::carton::termcolor diff --git a/testbench/yycc/flag_enum.cpp b/testbench/yycc/flag_enum.cpp index 636c3ab..976086a 100644 --- a/testbench/yycc/flag_enum.cpp +++ b/testbench/yycc/flag_enum.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include @@ -74,4 +75,9 @@ namespace yycctest::flag_enum { EXPECT_TRUE(FLAG_ENUM::boolean(TestEnum::MergedBit247)); } + TEST(FlagEnum, Integer) { + EXPECT_EQ(FLAG_ENUM::integer(TestEnum::Empty), UINT8_C(0)); + EXPECT_EQ(FLAG_ENUM::integer(TestEnum::Bit1), UINT8_C(1)); + } + }