Browse Source

Added `color` type & tests

Added initial `text`, `message`, `formatted_message` types
Added ANSI & IRC `text` object formatting / conversions
Added fmt submodule
Added `jessibot` target
master
Jessica James 5 years ago
parent
commit
b9163b14af
  1. 3
      .gitmodules
  2. 4
      src/CMakeLists.txt
  3. 12
      src/bot/CMakeLists.txt
  4. 35
      src/bot/main.cpp
  5. 2
      src/common/CMakeLists.txt
  6. 77
      src/common/io/message.cpp
  7. 2
      src/external/CMakeLists.txt
  8. 1
      src/external/fmt
  9. 126
      src/include/io/ansi/ansi_text.hpp
  10. 115
      src/include/io/color.hpp
  11. 187
      src/include/io/irc/irc_text.hpp
  12. 226
      src/include/io/message.hpp
  13. 4
      src/include/util.hpp
  14. 2
      src/test/CMakeLists.txt
  15. 88
      src/test/io/color.cpp

3
.gitmodules

@ -4,3 +4,6 @@
[submodule "asio"] [submodule "asio"]
path = src/include/impl/asio path = src/include/impl/asio
url = https://github.com/boostorg/asio.git url = https://github.com/boostorg/asio.git
[submodule "src/include/fmt"]
path = src/external/fmt
url = https://github.com/fmtlib/fmt.git

4
src/CMakeLists.txt

@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.8)
# Setup source files # Setup source files
add_subdirectory(external)
add_subdirectory(common) add_subdirectory(common)
add_subdirectory(test) add_subdirectory(test)
add_subdirectory(bot)

12
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)

35
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 <jessica.aj@outlook.com>
*/
#include <unordered_set>
#include <iostream>
#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;
}

2
src/common/CMakeLists.txt

@ -1,6 +1,6 @@
# Setup source files # Setup source files
set(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 # Setup library build target
add_library(jessilib ${SOURCE_FILES}) add_library(jessilib ${SOURCE_FILES})

77
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 <jessica.aj@outlook.com>
*/
#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<property_backing_t>(m_properties) & static_cast<property_backing_t>(in_property);
}
void text:: set_property(property in_property) {
m_properties = static_cast<property>(static_cast<property_backing_t>(m_properties)
| static_cast<property_backing_t>(in_property));
}
void text::unset_property(property in_property) {
m_properties = static_cast<property>(static_cast<property_backing_t>(m_properties)
& ~static_cast<property_backing_t>(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

2
src/external/CMakeLists.txt

@ -0,0 +1,2 @@
# Bring in external dependencies
add_subdirectory(fmt)

1
src/external/fmt

@ -0,0 +1 @@
Subproject commit 0476a51cba8dca9591f022856e2ff51c4d3f36b0

126
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 <jessica.aj@outlook.com>
*/
#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 + '[' + <color or graphics code list> + '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<ansi::text_wrapper>(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<jessilib::io::ansi::text_wrapper> : formatter<std::string> {
template <typename FormatContext>
auto format(const jessilib::io::ansi::text_wrapper& in_text, FormatContext& in_context) {
// Pass result to base
return formatter<std::string>::format(jessilib::io::text_to_string(in_text), in_context);
}
};

115
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 <jessica.aj@outlook.com>
*/
#pragma once
#include <cmath>
#include <vector>
#include <string>
#include <string_view>
#include <fmt/format.h>
#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<uint32_t>(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

187
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 <jessica.aj@outlook.com>
*/
#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<text::property_backing_t>(in_active_properties);
text::property_backing_t text_properties_backing = static_cast<text::property_backing_t>(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_backing_t>(text::property::colored)
&& (active_properties_backing & static_cast<text::property_backing_t>(text::property::colored))
&& in_active_color != in_text_color) {
result |= static_cast<text::property_backing_t>(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_backing_t>(text::property::colored_bg)
&& (active_properties_backing & static_cast<text::property_backing_t>(text::property::colored_bg))
&& in_active_color_bg != in_text_color_bg) {
result |= static_cast<text::property_backing_t>(text::property::colored_bg);
}
return text::property{ result };
}
} // namespace irc
template<>
std::string text_to_string<irc::text_wrapper>(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<jessilib::io::irc::text_wrapper> : formatter<std::string> {
template <typename FormatContext>
auto format(const jessilib::io::irc::text_wrapper& in_text, FormatContext& in_context) {
// Pass result to base
return formatter<std::string>::format(jessilib::io::text_to_string(in_text), in_context);
}
};

226
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 <jessica.aj@outlook.com>
*/
#pragma once
#include <vector>
#include <string>
#include <string_view>
#include <type_traits>
#include <fmt/format.h>
#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<typename StringT,
typename std::enable_if<!std::is_base_of<text, typename std::remove_reference<StringT>::type>::value>::type* = nullptr>
text(StringT&& in_text)
: m_string{ std::forward<StringT>(in_text) } {
// Empty ctor body
}
template<typename StringT>
text(StringT&& in_text, property in_properties)
: m_string{ std::forward<StringT>(in_text) },
m_properties{ in_properties } {
// Empty ctor body
}
template<typename StringT>
text(StringT&& in_text, color in_color)
: m_string{ std::forward<StringT>(in_text) },
m_properties{ property::colored },
m_color{ in_color } {
// Empty ctor body
}
template<typename StringT>
text(StringT&& in_text, property in_properties, color in_color)
: m_string{ std::forward<StringT>(in_text) },
m_properties{ static_cast<property_backing_t>(in_properties) | static_cast<property_backing_t>(property::colored) },
m_color{ in_color } {
// Empty ctor body
}
template<typename StringT>
text(StringT&& in_text, color in_color, color in_color_bg)
: m_string{ std::forward<StringT>(in_text) },
m_properties{ property::colord_fg_bg },
m_color{ in_color },
m_color_bg{ in_color_bg } {
// Empty ctor body
}
template<typename StringT>
text(StringT&& in_text, property in_properties, color in_color, color in_color_bg)
: m_string{ std::forward<StringT>(in_text) },
m_properties{ static_cast<property_backing_t>(in_properties) | static_cast<property_backing_t>(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<typename... Args>
message(Args&& ... args)
: m_message{ std::forward<Args>(args)... } {
// Empty ctor body
}
const std::vector<text>& get_message() const {
return m_message;
}
private:
std::vector<text> 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<typename... Args>
formatted_message(std::string in_format, Args&& ... args)
: m_format{ std::move(in_format) },
m_message{ std::forward<Args>(args)... } {
// Empty ctor body
}
const std::string& format() const {
return m_format;
}
const std::vector<text>& get_message() const {
return m_message;
}
private:
std::string m_format;
std::vector<text> m_message;
};
template<typename WrapperT>
std::string text_to_string(const WrapperT& in_text) {
return in_text.string();
}
template<typename WrapperT,
typename std::enable_if<std::is_base_of<text, WrapperT>::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<const WrapperT&>(in_text);
}
template<typename WrapperT,
typename std::enable_if<!std::is_base_of<text, WrapperT>::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<typename WrapperT>
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<WrapperT>(text));
}
// Return result
return result;
}
template<typename WrapperT>
std::string process_message(const jessilib::io::formatted_message& msg) {
using format_arg = fmt::format_args::format_arg;
std::vector<format_arg> args;
// Populate args
for (auto& text : msg.get_message()) {
args.emplace_back(fmt::internal::make_arg<fmt::format_context>(wrap_text<WrapperT>(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

4
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<typename T>
constexpr T square(T in_value) {
return in_value * in_value;
}
/** Implementation details */ /** Implementation details */

2
src/test/CMakeLists.txt

@ -1,6 +1,6 @@
# Setup source files # Setup source files
set(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 # Setup gtest
add_subdirectory(googletest/googletest) add_subdirectory(googletest/googletest)

88
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 <jessica.aj@outlook.com>
*/
#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);
}
Loading…
Cancel
Save