diff --git a/.gitmodules b/.gitmodules index f843000..17b061e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "asio"] path = src/include/impl/asio url = https://github.com/boostorg/asio.git +[submodule "src/include/fmt"] + path = src/external/fmt + url = https://github.com/fmtlib/fmt.git diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f33c1bb..8c4b01a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.8) - # Setup source files +add_subdirectory(external) add_subdirectory(common) add_subdirectory(test) +add_subdirectory(bot) diff --git a/src/bot/CMakeLists.txt b/src/bot/CMakeLists.txt new file mode 100644 index 0000000..90e75aa --- /dev/null +++ b/src/bot/CMakeLists.txt @@ -0,0 +1,12 @@ +# Setup source files +set(SOURCE_FILES + main.cpp) + +# Setup executable build target +add_executable(jessibot ${SOURCE_FILES}) + +# Setup include directories +target_include_directories(jessibot PRIVATE .) + +# Link with jessilib +target_link_libraries(jessibot jessilib) diff --git a/src/bot/main.cpp b/src/bot/main.cpp new file mode 100644 index 0000000..11fd8a7 --- /dev/null +++ b/src/bot/main.cpp @@ -0,0 +1,35 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include +#include +#include "app_parameters.hpp" +#include "parsers/json.hpp" + +// TODO: input loop + +int main(int argc, char** argv) { + jessilib::app_parameters parameters{ argc, argv }; + + if (parameters.hasSwitch("echoParameters")) { + // TODO: Write pretty JSON serializer based on JSON serializer + std::cout << std::endl << jessilib::json_parser{}.serialize(parameters) << std::endl; + } + + return 0; +} diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index f31812e..7a8872a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,6 +1,6 @@ # Setup source files set(SOURCE_FILES - timer/timer.cpp timer/timer_manager.cpp thread_pool.cpp timer/timer_context.cpp timer/cancel_token.cpp timer/synchronized_timer.cpp object.cpp parser/parser.cpp parser/parser_manager.cpp config.cpp serialize.cpp parsers/json.cpp unicode.cpp app_parameters.cpp) + timer/timer.cpp timer/timer_manager.cpp thread_pool.cpp timer/timer_context.cpp timer/cancel_token.cpp timer/synchronized_timer.cpp object.cpp parser/parser.cpp parser/parser_manager.cpp config.cpp serialize.cpp parsers/json.cpp unicode.cpp io/message.cpp app_parameters.cpp) # Setup library build target add_library(jessilib ${SOURCE_FILES}) diff --git a/src/common/io/message.cpp b/src/common/io/message.cpp new file mode 100644 index 0000000..4a54770 --- /dev/null +++ b/src/common/io/message.cpp @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "io/message.hpp" + +namespace jessilib { +namespace io { + +text::property text::properties() const { + return m_properties; +} + +bool text::has_property(property in_property) const { + if (in_property == property::normal) { + return m_properties == property::normal; + } + + return static_cast(m_properties) & static_cast(in_property); +} + +void text:: set_property(property in_property) { + m_properties = static_cast(static_cast(m_properties) + | static_cast(in_property)); +} + +void text::unset_property(property in_property) { + m_properties = static_cast(static_cast(m_properties) + & ~static_cast(in_property)); +} + +/** Colors */ + +color text::get_color() const { + return m_color; +} + +void text::set_color(color in_color) { + m_color = in_color; + set_property(property::colored); +} + +color text::get_color_bg() const { + return m_color_bg; +} + +void text::set_color_bg(color in_color) { + m_color_bg = in_color; + set_property(property::colored_bg); +} + +/** Text */ + +const std::string& text::string() const { + return m_string; +} + +void text::set_string(std::string_view in_string) { + m_string = in_string; +} + +} // namespace io +} // namespace jessilib diff --git a/src/external/CMakeLists.txt b/src/external/CMakeLists.txt new file mode 100644 index 0000000..1509c1b --- /dev/null +++ b/src/external/CMakeLists.txt @@ -0,0 +1,2 @@ +# Bring in external dependencies +add_subdirectory(fmt) diff --git a/src/external/fmt b/src/external/fmt new file mode 160000 index 0000000..0476a51 --- /dev/null +++ b/src/external/fmt @@ -0,0 +1 @@ +Subproject commit 0476a51cba8dca9591f022856e2ff51c4d3f36b0 diff --git a/src/include/io/ansi/ansi_text.hpp b/src/include/io/ansi/ansi_text.hpp new file mode 100644 index 0000000..489735a --- /dev/null +++ b/src/include/io/ansi/ansi_text.hpp @@ -0,0 +1,126 @@ +/** + * Copyright (C) 2020 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include "io/message.hpp" + +namespace jessilib { +namespace io { +namespace ansi { + +// Message wrapper to allow passing messages into fmt::format +class text_wrapper : public text {}; + +// Control characters +static constexpr uint8_t ESCAPE_CHR{ 0x1B }; +static constexpr std::string_view ESCAPE{ "\x1B[" }; +// ESCAPE + '[' + + 'm' + +// Graphics modes +static constexpr uint8_t NORMAL{ '0' }; +static constexpr uint8_t BOLD{ '1' }; +static constexpr uint8_t UNDERLINE{ '4' }; +static constexpr uint8_t BLINK{ '5' }; +static constexpr uint8_t REVERSE{ '7' }; +static constexpr uint8_t CONCEALED{ '8' }; +static constexpr uint8_t GRAPHICS_SEP{ ';' }; +static constexpr uint8_t GRAPHICS_END{ 'm' }; + +static constexpr std::string_view COLOR_HEX{ "38;2" }; +static constexpr std::string_view COLOR_DEFAULT{ "39" }; +static constexpr std::string_view COLOR_BG_HEX{ "48;2" }; +static constexpr std::string_view COLOR_BG_DEFAULT{ "49" }; + +} // namespace ansi + +template<> +std::string text_to_string(const ansi::text_wrapper& in_text) { + std::string result; + result.reserve(in_text.string().size() + 8); + + auto set_graphic_option = [&result](auto in_option) { + if (result.empty()) { + // Append escape sequence + result = ansi::ESCAPE; + } + else { + // Append value separator + result += ansi::GRAPHICS_SEP; + } + + // Append graphics option + result += in_option; + }; + + // Set graphics properties + if (in_text.has_property(text::property::bold)) { + set_graphic_option(ansi::BOLD); + } + if (in_text.has_property(text::property::underline)) { + set_graphic_option(ansi::UNDERLINE); + } + + // Set foreground color (if there is one) + if (in_text.has_property(text::property::colored)) { + auto color = in_text.get_color(); + set_graphic_option(ansi::COLOR_HEX); + set_graphic_option(std::to_string(color.red())); + set_graphic_option(std::to_string(color.green())); + set_graphic_option(std::to_string(color.blue())); + } + + // Set background color (if there is one) + if (in_text.has_property(text::property::colored_bg)) { + auto color = in_text.get_color_bg(); + set_graphic_option(ansi::COLOR_BG_HEX); + set_graphic_option(std::to_string(color.red())); + set_graphic_option(std::to_string(color.green())); + set_graphic_option(std::to_string(color.blue())); + } + + // If any graphics options were set, mark them complete + if (!result.empty()) { + result += ansi::GRAPHICS_END; + } + + // Append textual string + result += in_text.string(); + + // Reset (if needed) + if (in_text.properties() != text::property::normal) { + // Result any properties which were set + result += ansi::ESCAPE; + result += ansi::NORMAL; + result += ansi::GRAPHICS_END; + } + + return result; +} + +} // namespace io +} // namespace jessilib + +template<> +struct fmt::formatter : formatter { + template + auto format(const jessilib::io::ansi::text_wrapper& in_text, FormatContext& in_context) { + // Pass result to base + return formatter::format(jessilib::io::text_to_string(in_text), in_context); + } +}; diff --git a/src/include/io/color.hpp b/src/include/io/color.hpp new file mode 100644 index 0000000..fb0bc2b --- /dev/null +++ b/src/include/io/color.hpp @@ -0,0 +1,115 @@ +/** + * Copyright (C) 2020 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "util.hpp" + +namespace jessilib { +namespace io { + +class color { +public: + // Constructors + constexpr color() { + // Empty ctor body + } + + constexpr color(uint32_t in_value) + : m_value{ in_value } { + // Empty ctor body + } + + constexpr color(uint8_t in_red, uint8_t in_green, uint8_t in_blue) + : m_value{ static_cast(in_red << 16) | in_green << 8 | in_blue } { + // Empty ctor body + } + + constexpr color(const color&) = default; + constexpr color(color&&) = default; + + // Methods + + constexpr uint8_t red() const { + return (m_value & 0xFF0000) >> 16; + } + + constexpr uint8_t green() const { + return (m_value & 0xFF00) >> 8; + } + + constexpr uint8_t blue() const { + return m_value & 0xFF; + } + + constexpr uint32_t value() const { + return m_value; + } + + constexpr operator uint32_t() const { + return m_value; + } + + constexpr double distance(const color& in_color) const { + return std::sqrt(distance_sq(in_color)); + } + + constexpr uint32_t distance_sq(const color& in_color) const { + return square(red() - in_color.red()) + square(green() - in_color.green()) + square(blue() - in_color.blue()); + } + + // Assignment operators + constexpr color& operator=(const color&) = default; + constexpr color& operator=(color&&) = default; + + // Comparison operators + bool operator==(const color& rhs) const { + return m_value == rhs.m_value; + } + + bool operator!=(const color& rhs) const { + return m_value != rhs.m_value; + } + + bool operator<(const color& rhs) const { + return m_value < rhs.m_value; + } + + bool operator>(const color& rhs) const { + return m_value >= rhs.m_value; + } + + bool operator<=(const color& rhs) const { + return m_value <= rhs.m_value; + } + + bool operator>=(const color& rhs) const { + return m_value >= rhs.m_value; + } + +private: + uint32_t m_value{}; +}; + +} // namespace io +} // namespace jessilib diff --git a/src/include/io/irc/irc_text.hpp b/src/include/io/irc/irc_text.hpp new file mode 100644 index 0000000..0036320 --- /dev/null +++ b/src/include/io/irc/irc_text.hpp @@ -0,0 +1,187 @@ +/** + * Copyright (C) 2019-2020 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include "io/message.hpp" + +namespace jessilib { +namespace io { +namespace irc { + +// Message wrapper to allow passing messages into fmt::format +class text_wrapper : public text {}; + +// Color table +static constexpr color s_irc_colors[] { + // Basic 16 colors (0-15) + 0xFFFFFF, 0x000000, 0x00007F, 0x009300, 0xFF0000, 0x7F0000, 0x9C009C, 0xFC7F00, + 0xFFFF00, 0x00FC00, 0x009393, 0x00FFFF, 0x0000FC, 0xFF00FF, 0x7F7F7F, 0xD2D2D2, + +#ifndef JESSILIB_IRC_SIMPLE_COLORS + // Extended colors (16-98, making a total of 99 color choices) + 0x470000, 0x472100, 0x474700, 0x324700, 0x004700, 0x00472C, 0x004747, 0x002747, 0x000047, 0x2E0047, 0x470047, 0x47002A, + 0x740000, 0x743a00, 0x747400, 0x517400, 0x007400, 0x007449, 0x007474, 0x004074, 0x000074, 0x4b0074, 0x740074, 0x740045, + 0xb50000, 0xb56300, 0xb5b500, 0x7db500, 0x00b500, 0x00b571, 0x00b5b5, 0x0063b5, 0x0000b5, 0x7500b5, 0xb500b5, 0xb5006b, + 0xff0000, 0xff8c00, 0xffff00, 0xb2ff00, 0x00ff00, 0x00ffa0, 0x00ffff, 0x008cff, 0x0000ff, 0xa500ff, 0xff00ff, 0xff0098, + 0xff5959, 0xffb459, 0xffff71, 0xcfff60, 0x6fff6f, 0x65ffc9, 0x6dffff, 0x59b4ff, 0x5959ff, 0xc459ff, 0xff66ff, 0xff59bc, + 0xff9c9c, 0xffd39c, 0xffff9c, 0xe2ff9c, 0x9cff9c, 0x9cffdb, 0x9cffff, 0x9cd3ff, 0x9c9cff, 0xdc9cff, 0xff9cff, 0xff94d3, + 0x000000, 0x131313, 0x282828, 0x363636, 0x4d4d4d, 0x656565, 0x818181, 0x9f9f9f, 0xbcbcbc, 0xe2e2e2, 0xffffff +#endif // JESSILIB_IRC_SIMPLE_COLORS +}; + +static constexpr std::string_view s_irc_color_codes[] { + // Basic 16 colors (0-15) + "00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", + +#ifndef JESSILIB_IRC_SIMPLE_COLORS + // Extended colors (16-98, making a total of 99 color choices) + "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", + "28", "29", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", + "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "50", "51", + "52", "53", "54", "55", "56", "57", "58", "59", "60", "61", "62", "63", + "64", "65", "66", "67", "68", "69", "70", "71", "72", "73", "74", "75", + "76", "77", "78", "79", "80", "81", "82", "83", "84", "85", "86", "87", + "88", "89", "90", "91", "92", "93", "94", "95", "96", "97", "98" +#endif // JESSILIB_IRC_SIMPLE_COLORS +}; + +static_assert(sizeof(s_irc_colors) / sizeof(color) == sizeof(s_irc_color_codes) / sizeof(std::string_view), "Color tables not aligned"); +static constexpr size_t s_irc_colors_length = sizeof(s_irc_colors) / sizeof(color); + +constexpr color to_color(uint8_t in_irc_color) { + // Safety check + if (in_irc_color >= s_irc_colors_length) { + return {}; + } + + return s_irc_colors[in_irc_color]; +} + +constexpr uint8_t from_color(color in_color) { + uint8_t best_match{}; + uint32_t best_match_distance_sq{ 0xFFFFFFFF }; + uint32_t distance_sq{}; + + for (uint8_t index = 0; index != s_irc_colors_length; ++index) { + distance_sq = in_color.distance_sq(s_irc_colors[index]); + if (distance_sq < best_match_distance_sq) { + best_match_distance_sq = distance_sq; + best_match = index; + + if (distance_sq == 0) { // TODO: figure out smallest possible distance between any two points in the color table + // Exact match; stop searching + break; + } + } + } + + return best_match; +} + +constexpr color normalize_color(color in_color) { + return s_irc_colors[from_color(in_color)]; +} + +constexpr std::string_view color_to_code(color in_color) { + return s_irc_color_codes[from_color(in_color)]; +} + +static constexpr uint8_t BOLD{ 0x02 }; +static constexpr uint8_t ITALIC{ 0x1D }; +static constexpr uint8_t UNDERLINE{ 0x1F }; +static constexpr uint8_t STRIKETHROUGH{ 0x1E }; +static constexpr uint8_t MONOSPACE{ 0x11 }; +static constexpr uint8_t COLOR{ 0x03 }; +static constexpr uint8_t COLOR_HEX{ 0x04 }; +static constexpr uint8_t REVERSE{ 0x16 }; +static constexpr uint8_t NORMAL{ 0x0F }; + +text::property properties_to_toggle(text::property in_active_properties, text::property in_text_properties, uint8_t in_active_color, uint8_t in_text_color, uint8_t in_active_color_bg, uint8_t in_text_color_bg) { + text::property_backing_t active_properties_backing = static_cast(in_active_properties); + text::property_backing_t text_properties_backing = static_cast(in_text_properties); + + text::property_backing_t result{ active_properties_backing ^ text_properties_backing }; + + // If both inputs had an active color, and that color isn't the same, explicitly force colored property. + if (text_properties_backing & static_cast(text::property::colored) + && (active_properties_backing & static_cast(text::property::colored)) + && in_active_color != in_text_color) { + result |= static_cast(text::property::colored); + } + + // If both inputs had an active background color, and that color isn't the same, explicitly force colored_bg property. + if (text_properties_backing & static_cast(text::property::colored_bg) + && (active_properties_backing & static_cast(text::property::colored_bg)) + && in_active_color_bg != in_text_color_bg) { + result |= static_cast(text::property::colored_bg); + } + + return text::property{ result }; +} + +} // namespace irc + +template<> +std::string text_to_string(const irc::text_wrapper& in_text) { + std::string result; + result.reserve(in_text.string().size() + 8); + + // Prepend properties + if (in_text.has_property(text::property::bold)) { + result += irc::BOLD; + } + if (in_text.has_property(text::property::italic)) { + result += irc::ITALIC; + } + if (in_text.has_property(text::property::underline)) { + result += irc::UNDERLINE; + } + if (in_text.has_property(text::property::strikethrough)) { + result += irc::STRIKETHROUGH; + } + + // Prepend color (if there is one) + if (in_text.has_property(text::property::colored)) { + result += irc::COLOR; + result += irc::color_to_code(in_text.get_color()); + } + + // Append textual string + result += in_text.string(); + + // Reset (if needed) + if (in_text.properties() != text::property::normal) { + // Result any properties which were set + result += irc::NORMAL; + } + + return result; +} + +} // namespace io +} // namespace jessilib + +template<> +struct fmt::formatter : formatter { + template + auto format(const jessilib::io::irc::text_wrapper& in_text, FormatContext& in_context) { + // Pass result to base + return formatter::format(jessilib::io::text_to_string(in_text), in_context); + } +}; diff --git a/src/include/io/message.hpp b/src/include/io/message.hpp new file mode 100644 index 0000000..b9662de --- /dev/null +++ b/src/include/io/message.hpp @@ -0,0 +1,226 @@ +/** + * Copyright (C) 2019 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "color.hpp" + +namespace jessilib { +namespace io { + +class text { +public: + /** Types */ + + using property_backing_t = unsigned int; + enum class property : property_backing_t { + normal = 0x00, + bold = 0x01, + italic = 0x02, + underline = 0x04, + strikethrough = 0x08, + colored = 0x10, // foreground + colored_bg = 0x20, // background + colord_fg_bg = 0x30 // foreground + background + }; + + /** Constructors */ + + text() = default; + + template::type>::value>::type* = nullptr> + text(StringT&& in_text) + : m_string{ std::forward(in_text) } { + // Empty ctor body + } + + template + text(StringT&& in_text, property in_properties) + : m_string{ std::forward(in_text) }, + m_properties{ in_properties } { + // Empty ctor body + } + + template + text(StringT&& in_text, color in_color) + : m_string{ std::forward(in_text) }, + m_properties{ property::colored }, + m_color{ in_color } { + // Empty ctor body + } + + template + text(StringT&& in_text, property in_properties, color in_color) + : m_string{ std::forward(in_text) }, + m_properties{ static_cast(in_properties) | static_cast(property::colored) }, + m_color{ in_color } { + // Empty ctor body + } + + template + text(StringT&& in_text, color in_color, color in_color_bg) + : m_string{ std::forward(in_text) }, + m_properties{ property::colord_fg_bg }, + m_color{ in_color }, + m_color_bg{ in_color_bg } { + // Empty ctor body + } + + template + text(StringT&& in_text, property in_properties, color in_color, color in_color_bg) + : m_string{ std::forward(in_text) }, + m_properties{ static_cast(in_properties) | static_cast(property::colord_fg_bg) }, + m_color{ in_color }, + m_color_bg{ in_color_bg } { + // Empty ctor body + } + + text(const text&) = default; + text(text&&) = default; + text& operator=(const text&) = default; + text& operator=(text&&) = default; + + /** Properties */ + property properties() const; + bool has_property(property in_property) const; + void set_property(property in_property); + void unset_property(property in_property); + + /** Colors */ + color get_color() const; + void set_color(color in_color); + color get_color_bg() const; + void set_color_bg(color in_color); + + /** Text */ + const std::string& string() const; + void set_string(std::string_view in_string); + +private: + std::string m_string; + property m_properties{ property::normal }; + color m_color{}; + color m_color_bg{}; +}; + +/** + * message class consisting of a series of text segments that are then combined (or otherwise output in sequence). + * + * This class is necessary to facilitate colored messages and other properties that will be serialized differently for + * differing outputs (i.e: IRC uses color codes, consoles are platform-specific, etc). + */ +class message { +public: + template + message(Args&& ... args) + : m_message{ std::forward(args)... } { + // Empty ctor body + } + + const std::vector& get_message() const { + return m_message; + } + +private: + std::vector m_message; +}; + +/** + * formatted_message class similar to the message class, except that a format is specified to determine order of segments + * + * See fmtlib for details on the expected format syntax + */ +class formatted_message { +public: + template + formatted_message(std::string in_format, Args&& ... args) + : m_format{ std::move(in_format) }, + m_message{ std::forward(args)... } { + // Empty ctor body + } + + const std::string& format() const { + return m_format; + } + + const std::vector& get_message() const { + return m_message; + } + +private: + std::string m_format; + std::vector m_message; +}; + +template +std::string text_to_string(const WrapperT& in_text) { + return in_text.string(); +} + +template::value && sizeof(text) == sizeof(WrapperT)>::type* = nullptr> +const WrapperT& wrap_text(const text& in_text) { + // WrapperT extends text, which is non virtual (so the vtables must be the same) and the size is the same (so members must be the same) + // Thus, a simple upcast should be safe + return static_cast(in_text); +} + +template::value || sizeof(text) != sizeof(WrapperT)>::type* = nullptr> +WrapperT wrap_text(const text& in_text) { + // WrapperT either doesn't extend text, or adds additional members. + // Attempt to construct WrapperT with in_text + return { in_text }; +} + +template +std::string process_message(const jessilib::io::message& msg) { + std::string result; + + // Concatenate text portions together + for (auto& text : msg.get_message()) { + result += text_to_string(wrap_text(text)); + } + + // Return result + return result; +} + +template +std::string process_message(const jessilib::io::formatted_message& msg) { + using format_arg = fmt::format_args::format_arg; + std::vector args; + + // Populate args + for (auto& text : msg.get_message()) { + args.emplace_back(fmt::internal::make_arg(wrap_text(text))); + } + + // Pass args into vformat + fmt::format_args text_args{ args.data(), args.size() }; + return fmt::vformat(msg.format(), text_args); +} + +} // namespace io +} // namespace jessilib diff --git a/src/include/util.hpp b/src/include/util.hpp index 654146b..b94f412 100644 --- a/src/include/util.hpp +++ b/src/include/util.hpp @@ -87,6 +87,10 @@ std::from_chars_result from_chars(const char* in_str, const char* in_str_end, T& } } +template +constexpr T square(T in_value) { + return in_value * in_value; +} /** Implementation details */ diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 77710c2..aa31688 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -1,6 +1,6 @@ # Setup source files set(SOURCE_FILES - timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp app_parameters.cpp) + timer.cpp thread_pool.cpp util.cpp object.cpp parser.cpp config.cpp parsers/json.cpp unicode.cpp app_parameters.cpp io/color.cpp) # Setup gtest add_subdirectory(googletest/googletest) diff --git a/src/test/io/color.cpp b/src/test/io/color.cpp new file mode 100644 index 0000000..fe5ec80 --- /dev/null +++ b/src/test/io/color.cpp @@ -0,0 +1,88 @@ +/** + * Copyright (C) 2020 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "io/color.hpp" +#include "test.hpp" + +using namespace jessilib; +using namespace jessilib::io; +using namespace std::literals; + +static constexpr color g_constexpr_color{ 0x123456 }; + +TEST(ColorTest, red) { + color color_value{ 0xFF0000 }; + color color_rgb{ 0xFF, 0, 0 }; + + EXPECT_EQ(color_value, color_rgb); + EXPECT_EQ(color_value.value(), 0xFF0000U); + EXPECT_EQ(color_value.red(), 0xFF); + EXPECT_EQ(color_value.green(), 0x00); + EXPECT_EQ(color_value.blue(), 0x00); +} + +TEST(ColorTest, green) { + color color_value{ 0xFF00 }; + color color_rgb{ 0, 0xFF, 0 }; + + EXPECT_EQ(color_value, color_rgb); + EXPECT_EQ(color_value.value(), 0xFF00U); + EXPECT_EQ(color_value.red(), 0x00); + EXPECT_EQ(color_value.green(), 0xFF); + EXPECT_EQ(color_value.blue(), 0x00); +} + +TEST(ColorTest, blue) { + color color_value{ 0xFF }; + color color_rgb{ 0, 0, 0xFF }; + + EXPECT_EQ(color_value, color_rgb); + EXPECT_EQ(color_value.value(), 0xFFU); + EXPECT_EQ(color_value.red(), 0x00); + EXPECT_EQ(color_value.green(), 0x00); + EXPECT_EQ(color_value.blue(), 0xFF); +} + +TEST(ColorTest, mixed) { + color color_value{ 0x123456 }; + color color_rgb{ 0x12, 0x34, 0x56 }; + + EXPECT_EQ(color_value, color_rgb); + EXPECT_EQ(color_value.value(), 0x123456U); + EXPECT_EQ(color_value.red(), 0x12); + EXPECT_EQ(color_value.green(), 0x34); + EXPECT_EQ(color_value.blue(), 0x56); +} + +TEST(ColorTest, copy) { + color color_value{ g_constexpr_color }; + color color_rgb{ 0x12, 0x34, 0x56 }; + + EXPECT_EQ(color_value, color_rgb); + EXPECT_EQ(color_value.value(), g_constexpr_color); + EXPECT_EQ(color_value.red(), 0x12); + EXPECT_EQ(color_value.green(), 0x34); + EXPECT_EQ(color_value.blue(), 0x56); +} + +TEST(ColorTest, distance) { + EXPECT_EQ(color{}.distance({}), 0); + EXPECT_EQ(color{ 0xFF }.distance({}), 0xFF); + EXPECT_EQ(color{ 0xFF00 }.distance({}), 0xFF); + EXPECT_EQ(color{ 0xFF0000 }.distance({}), 0xFF); +}