Util: Split formatting print into print and colored print
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // FILE, fputs, stdout
|
||||
#include <sstream> // stringstream
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "util/format/color.h"
|
||||
#include "util/format/format.h"
|
||||
#include "util/meta/assert.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
TextStyle::TextStyle(Emphasis emphasis)
|
||||
: m_emphasis(emphasis)
|
||||
{
|
||||
}
|
||||
|
||||
TextStyle::TextStyle(bool isForeground, TerminalColor color)
|
||||
{
|
||||
if (isForeground) {
|
||||
m_foregroundColor = color;
|
||||
}
|
||||
else {
|
||||
m_backgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
TextStyle& TextStyle::operator|=(const TextStyle& rhs)
|
||||
{
|
||||
if (m_foregroundColor == TerminalColor::None) {
|
||||
m_foregroundColor = rhs.m_foregroundColor;
|
||||
}
|
||||
else {
|
||||
VERIFY(rhs.m_foregroundColor == TerminalColor::None, "can't OR a terminal color");
|
||||
}
|
||||
|
||||
if (m_backgroundColor == TerminalColor::None) {
|
||||
m_backgroundColor = rhs.m_backgroundColor;
|
||||
}
|
||||
else {
|
||||
VERIFY(rhs.m_backgroundColor == TerminalColor::None, "can't OR a terminal color");
|
||||
}
|
||||
|
||||
m_emphasis = static_cast<Emphasis>(static_cast<uint8_t>(m_emphasis) | static_cast<uint8_t>(rhs.m_emphasis));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
TextStyle fg(TerminalColor foreground)
|
||||
{
|
||||
return TextStyle(true, foreground);
|
||||
}
|
||||
|
||||
TextStyle bg(TerminalColor background)
|
||||
{
|
||||
return TextStyle(false, background);
|
||||
}
|
||||
|
||||
TextStyle operator|(TextStyle lhs, const TextStyle& rhs)
|
||||
{
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
TextStyle operator|(Emphasis lhs, Emphasis rhs)
|
||||
{
|
||||
return TextStyle { lhs } | rhs;
|
||||
}
|
||||
|
||||
bool operator&(Emphasis lhs, Emphasis rhs)
|
||||
{
|
||||
return static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void setDisplayAttributes(std::stringstream& stream, const TextStyle& style)
|
||||
{
|
||||
bool hasForeground = style.foregroundColor() != TerminalColor::None;
|
||||
bool hasBackground = style.backgroundColor() != TerminalColor::None;
|
||||
bool hasEmphasis = style.emphasis() != Emphasis::None;
|
||||
if (!hasForeground && !hasBackground && !hasEmphasis) {
|
||||
return;
|
||||
}
|
||||
|
||||
stream.write("\033[", 2);
|
||||
|
||||
if (hasForeground) {
|
||||
stream << format("{}", static_cast<uint8_t>(style.foregroundColor()));
|
||||
}
|
||||
|
||||
if (hasBackground) {
|
||||
if (hasForeground) {
|
||||
stream.write(";", 1);
|
||||
}
|
||||
stream << format("{}", static_cast<uint8_t>(style.backgroundColor()) + 10);
|
||||
}
|
||||
|
||||
stream.write("m", 1);
|
||||
|
||||
if (hasEmphasis) {
|
||||
|
||||
#define ESCAPE_ATTRIBUTE(escape, attribute) \
|
||||
if (style.emphasis() & escape) { \
|
||||
stream.write("\033[", 2); \
|
||||
stream.write(attribute, 1); \
|
||||
stream.write("m", 1); \
|
||||
}
|
||||
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Bold, "1");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Faint, "2");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Italic, "3");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Underline, "4");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Blink, "5");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Reverse, "7");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Conceal, "8");
|
||||
ESCAPE_ATTRIBUTE(Emphasis::Strikethrough, "9");
|
||||
}
|
||||
}
|
||||
|
||||
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters)
|
||||
{
|
||||
setDisplayAttributes(stream, style);
|
||||
variadicFormat(stream, format, parameters);
|
||||
stream.write("\033[0m", 4);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
setDisplayAttributes(stream, style);
|
||||
variadicFormat(stream, format, parameters);
|
||||
stream.write("\033[0m", 4);
|
||||
|
||||
std::string string = stream.str();
|
||||
fputs(string.c_str(), file);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
ColorPrintOperatorStyle::ColorPrintOperatorStyle(FILE* file, const TextStyle& style)
|
||||
: m_file(file)
|
||||
, m_style(style)
|
||||
, m_stream()
|
||||
, m_builder(m_stream)
|
||||
{
|
||||
setDisplayAttributes(m_stream, style);
|
||||
}
|
||||
|
||||
ColorPrintOperatorStyle::~ColorPrintOperatorStyle()
|
||||
{
|
||||
m_stream.write("\033[0m", 4);
|
||||
std::string string = m_stream.str();
|
||||
fputs(string.c_str(), m_file);
|
||||
}
|
||||
|
||||
ColorPrintOperatorStyle print(const TextStyle& style)
|
||||
{
|
||||
return ColorPrintOperatorStyle(stdout, style);
|
||||
}
|
||||
|
||||
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style)
|
||||
{
|
||||
return ColorPrintOperatorStyle(file, style);
|
||||
}
|
||||
|
||||
} // namespace Util::Format
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright (C) 2022 Riyyi
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstddef> // size_t
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // FILE, stdout
|
||||
#include <sstream> // stringstream
|
||||
#include <string_view>
|
||||
|
||||
#include "util/format/format.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
enum class TerminalColor : uint8_t {
|
||||
None = 0,
|
||||
Black = 30,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
White,
|
||||
BrightBlack = 90,
|
||||
BrightRed,
|
||||
BrightGreen,
|
||||
BrightYellow,
|
||||
BrightBlue,
|
||||
BrightMagenta,
|
||||
BrightCyan,
|
||||
BrightWhite
|
||||
};
|
||||
|
||||
// Bit field
|
||||
enum class Emphasis : uint8_t {
|
||||
None = 0, // Attribute 0
|
||||
Bold = 1, // Attribute 1
|
||||
Faint = 1 << 1, // Attribute 2
|
||||
Italic = 1 << 2, // Attribute 3
|
||||
Underline = 1 << 3, // Attribute 4
|
||||
Blink = 1 << 4, // Attribute 5
|
||||
Reverse = 1 << 5, // Attribute 7
|
||||
Conceal = 1 << 6, // Attribute 8
|
||||
Strikethrough = 1 << 7, // Attribute 9
|
||||
};
|
||||
|
||||
class TextStyle {
|
||||
private:
|
||||
friend TextStyle fg(TerminalColor foreground);
|
||||
friend TextStyle bg(TerminalColor background);
|
||||
|
||||
public:
|
||||
TextStyle(Emphasis emphasis);
|
||||
|
||||
// Operator pipe equal, reads the same way as +=
|
||||
TextStyle& operator|=(const TextStyle& rhs);
|
||||
|
||||
TerminalColor foregroundColor() const { return m_foregroundColor; }
|
||||
TerminalColor backgroundColor() const { return m_backgroundColor; }
|
||||
Emphasis emphasis() const { return m_emphasis; }
|
||||
|
||||
private:
|
||||
TextStyle(bool isForeground, TerminalColor color);
|
||||
|
||||
TerminalColor m_foregroundColor { TerminalColor::None };
|
||||
TerminalColor m_backgroundColor { TerminalColor::None };
|
||||
Emphasis m_emphasis { 0 };
|
||||
};
|
||||
|
||||
TextStyle fg(TerminalColor foreground);
|
||||
TextStyle bg(TerminalColor background);
|
||||
TextStyle operator|(TextStyle lhs, const TextStyle& rhs);
|
||||
TextStyle operator|(Emphasis lhs, Emphasis rhs);
|
||||
bool operator&(Emphasis lhs, Emphasis rhs);
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void coloredVariadicFormat(std::stringstream& stream, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters);
|
||||
|
||||
template<typename... Parameters>
|
||||
std::string format(const TextStyle& style, std::string_view format, const Parameters&... parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
coloredVariadicFormat(stream, style, format, variadicParameters);
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
template<typename... Parameters>
|
||||
void formatTo(std::string& output, const TextStyle& style, std::string_view format, const Parameters&... parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
coloredVariadicFormat(stream, style, format, variadicParameters);
|
||||
output += stream.str();
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
void coloredVariadicPrint(FILE* file, const TextStyle& style, std::string_view format, TypeErasedParameters& parameters);
|
||||
|
||||
template<size_t N, typename... Parameters>
|
||||
void print(const TextStyle& style, const char (&format)[N], const Parameters&... parameters)
|
||||
{
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
coloredVariadicPrint(stdout, style, { format, N - 1 }, variadicParameters);
|
||||
}
|
||||
|
||||
template<size_t N, typename... Parameters>
|
||||
void print(FILE* file, const TextStyle& style, const char (&format)[N], const Parameters&... parameters)
|
||||
{
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
coloredVariadicPrint(file, style, { format, N - 1 }, variadicParameters);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class ColorPrintOperatorStyle {
|
||||
public:
|
||||
ColorPrintOperatorStyle(FILE* file, const TextStyle& style);
|
||||
virtual ~ColorPrintOperatorStyle();
|
||||
|
||||
Builder& builder() { return m_builder; }
|
||||
|
||||
private:
|
||||
FILE* m_file;
|
||||
TextStyle m_style;
|
||||
|
||||
std::stringstream m_stream;
|
||||
Builder m_builder;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const ColorPrintOperatorStyle& operator<<(const ColorPrintOperatorStyle& colorPrintOperatorStyle, const T& value)
|
||||
{
|
||||
_format(const_cast<ColorPrintOperatorStyle&>(colorPrintOperatorStyle).builder(), value);
|
||||
return colorPrintOperatorStyle;
|
||||
}
|
||||
|
||||
ColorPrintOperatorStyle print(const TextStyle& style);
|
||||
ColorPrintOperatorStyle print(FILE* file, const TextStyle& style);
|
||||
|
||||
} // namespace Util::Format
|
||||
|
||||
namespace Util {
|
||||
|
||||
using Util::Format::format;
|
||||
using Util::Format::formatTo;
|
||||
using Util::Format::print;
|
||||
|
||||
} // namespace Util
|
||||
@@ -4,7 +4,7 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdio> // FILE, fputs
|
||||
#include <cstdio> // FILE, fputs, stdout, stderr
|
||||
#include <iomanip> // setprecision
|
||||
#include <ios> // defaultfloat, fixed
|
||||
#include <sstream> // stringstream
|
||||
@@ -15,47 +15,9 @@
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
void printTimeElapsedAndTypePrefix(std::stringstream& stream, Type type, bool bold)
|
||||
{
|
||||
stream << std::fixed << std::setprecision(3)
|
||||
<< s_timer.elapsedNanoseconds() / 1000000000.0 << "s "
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
|
||||
stream << "[\033[";
|
||||
if (bold) {
|
||||
stream << "1";
|
||||
}
|
||||
switch (type) {
|
||||
case Type::None:
|
||||
stream << ";35mdebug";
|
||||
break;
|
||||
case Type::Info:
|
||||
stream << ";34minfo";
|
||||
break;
|
||||
case Type::Warn:
|
||||
stream << ";33mwarn";
|
||||
break;
|
||||
case Type::Critical:
|
||||
stream << ";31mcritical";
|
||||
break;
|
||||
case Type::Success:
|
||||
stream << ";32msuccess";
|
||||
break;
|
||||
case Type::Comment:
|
||||
stream << "mcomment";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
stream << "\033[0m] ";
|
||||
}
|
||||
|
||||
void prettyVariadicFormat(FILE* file, Type type, bool bold, std::string_view format, TypeErasedParameters& parameters)
|
||||
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters)
|
||||
{
|
||||
std::stringstream stream;
|
||||
|
||||
printTimeElapsedAndTypePrefix(stream, type, bold);
|
||||
|
||||
variadicFormat(stream, format, parameters);
|
||||
|
||||
std::string string = stream.str();
|
||||
@@ -64,80 +26,27 @@ void prettyVariadicFormat(FILE* file, Type type, bool bold, std::string_view for
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
FormatPrint::FormatPrint(FILE* file, Type type, bool bold)
|
||||
PrintOperatorStyle::PrintOperatorStyle(FILE* file)
|
||||
: m_file(file)
|
||||
, m_type(type)
|
||||
, m_bold(bold)
|
||||
, m_stream()
|
||||
, m_builder(m_stream)
|
||||
{
|
||||
printTimeElapsedAndTypePrefix(m_stream, type, bold);
|
||||
}
|
||||
|
||||
FormatPrint::~FormatPrint()
|
||||
PrintOperatorStyle::~PrintOperatorStyle()
|
||||
{
|
||||
std::string string = m_stream.str();
|
||||
fputs(string.c_str(), m_file);
|
||||
}
|
||||
|
||||
FormatPrint dbg()
|
||||
PrintOperatorStyle print()
|
||||
{
|
||||
return FormatPrint(stdout, Type::None, false);
|
||||
return PrintOperatorStyle(stdout);
|
||||
}
|
||||
|
||||
FormatPrint dbgb()
|
||||
PrintOperatorStyle print(FILE* file)
|
||||
{
|
||||
return FormatPrint(stdout, Type::None, true);
|
||||
}
|
||||
|
||||
FormatPrint info()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Info, false);
|
||||
}
|
||||
|
||||
FormatPrint infob()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Info, true);
|
||||
}
|
||||
|
||||
FormatPrint warn()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Warn, false);
|
||||
}
|
||||
|
||||
FormatPrint warnb()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Warn, true);
|
||||
}
|
||||
|
||||
FormatPrint critical()
|
||||
{
|
||||
return FormatPrint(stderr, Type::Critical, false);
|
||||
}
|
||||
|
||||
FormatPrint criticalb()
|
||||
{
|
||||
return FormatPrint(stderr, Type::Critical, true);
|
||||
}
|
||||
|
||||
FormatPrint success()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Success, false);
|
||||
}
|
||||
|
||||
FormatPrint successb()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Success, true);
|
||||
}
|
||||
|
||||
FormatPrint comment()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Comment, false);
|
||||
}
|
||||
|
||||
FormatPrint commentb()
|
||||
{
|
||||
return FormatPrint(stdout, Type::Comment, true);
|
||||
return PrintOperatorStyle(file);
|
||||
}
|
||||
|
||||
} // namespace Util::Format
|
||||
|
||||
+28
-83
@@ -4,90 +4,59 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
|
||||
#include <cstdio> // FILE
|
||||
#pragma once
|
||||
|
||||
#include <cstdint> // uint8_t
|
||||
#include <cstdio> // FILE, stdout
|
||||
#include <sstream> // stringstream
|
||||
#include <string_view>
|
||||
|
||||
#include "util/format/format.h"
|
||||
#include "util/timer.h"
|
||||
|
||||
namespace Util::Format {
|
||||
|
||||
static Util::Timer s_timer;
|
||||
void variadicPrint(FILE* file, std::string_view format, TypeErasedParameters& parameters);
|
||||
|
||||
enum class Type {
|
||||
None, // Foreground
|
||||
Info, // Blue
|
||||
Warn, // Yellow
|
||||
Critical, // Red
|
||||
Success, // Green
|
||||
Comment, // White
|
||||
};
|
||||
template<size_t N, typename... Parameters>
|
||||
void print(const char (&format)[N], const Parameters&... parameters)
|
||||
{
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
variadicPrint(stdout, { format, N - 1 }, variadicParameters);
|
||||
}
|
||||
|
||||
void prettyVariadicFormat(FILE* file, Type type, bool bold, std::string_view format, TypeErasedParameters& parameters);
|
||||
|
||||
#define FORMAT_FUNCTION(name, type, bold) \
|
||||
template<size_t N, typename... Parameters> \
|
||||
void name(const char(&format)[N], const Parameters&... parameters) \
|
||||
{ \
|
||||
VariadicParameters variadicParameters { parameters... }; \
|
||||
prettyVariadicFormat(stdout, Type::type, bold, { format, N - 1 }, variadicParameters); \
|
||||
} \
|
||||
template<size_t N, typename... Parameters> \
|
||||
void name(FILE* file, const char(&format)[N], const Parameters&... parameters) \
|
||||
{ \
|
||||
VariadicParameters variadicParameters { parameters... }; \
|
||||
prettyVariadicFormat(file, Type::type, bold, { format, N - 1 }, variadicParameters); \
|
||||
}
|
||||
|
||||
FORMAT_FUNCTION(dbgln, None, false);
|
||||
FORMAT_FUNCTION(dbgbln, None, true);
|
||||
FORMAT_FUNCTION(infoln, Info, false);
|
||||
FORMAT_FUNCTION(infobln, Info, true);
|
||||
FORMAT_FUNCTION(warnln, Warn, false);
|
||||
FORMAT_FUNCTION(warnbln, Warn, true);
|
||||
FORMAT_FUNCTION(criticalln, Critical, false);
|
||||
FORMAT_FUNCTION(criticalbln, Critical, true);
|
||||
FORMAT_FUNCTION(successln, Success, false);
|
||||
FORMAT_FUNCTION(successbln, Success, true);
|
||||
FORMAT_FUNCTION(commentln, Comment, false);
|
||||
FORMAT_FUNCTION(commentbln, Comment, true);
|
||||
template<size_t N, typename... Parameters>
|
||||
void print(FILE* file, const char (&format)[N], const Parameters&... parameters)
|
||||
{
|
||||
VariadicParameters variadicParameters { parameters... };
|
||||
variadicPrint(file, { format, N - 1 }, variadicParameters);
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
class FormatPrint {
|
||||
class PrintOperatorStyle {
|
||||
public:
|
||||
FormatPrint(FILE* file, Type type, bool bold);
|
||||
virtual ~FormatPrint();
|
||||
PrintOperatorStyle(FILE* file);
|
||||
virtual ~PrintOperatorStyle();
|
||||
|
||||
Builder& builder() { return m_builder; }
|
||||
|
||||
private:
|
||||
FILE* m_file;
|
||||
Type m_type;
|
||||
bool m_bold;
|
||||
|
||||
std::stringstream m_stream;
|
||||
Builder m_builder;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
const FormatPrint& operator<<(const FormatPrint& formatPrint, const T& value)
|
||||
const PrintOperatorStyle& operator<<(const PrintOperatorStyle& printOperatorStyle, const T& value)
|
||||
{
|
||||
_format(const_cast<FormatPrint&>(formatPrint).builder(), value);
|
||||
return formatPrint;
|
||||
_format(const_cast<PrintOperatorStyle&>(printOperatorStyle).builder(), value);
|
||||
return printOperatorStyle;
|
||||
}
|
||||
|
||||
FormatPrint dbg();
|
||||
FormatPrint dbgb();
|
||||
FormatPrint info();
|
||||
FormatPrint infob();
|
||||
FormatPrint warn();
|
||||
FormatPrint warnb();
|
||||
FormatPrint critical();
|
||||
FormatPrint criticalb();
|
||||
FormatPrint success();
|
||||
FormatPrint successb();
|
||||
FormatPrint comment();
|
||||
FormatPrint commentb();
|
||||
PrintOperatorStyle print();
|
||||
PrintOperatorStyle print(FILE* file);
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
@@ -95,30 +64,6 @@ FormatPrint commentb();
|
||||
|
||||
namespace Util {
|
||||
|
||||
using Util::Format::commentbln;
|
||||
using Util::Format::commentln;
|
||||
using Util::Format::criticalbln;
|
||||
using Util::Format::criticalln;
|
||||
using Util::Format::dbgbln;
|
||||
using Util::Format::dbgln;
|
||||
using Util::Format::infobln;
|
||||
using Util::Format::infoln;
|
||||
using Util::Format::successbln;
|
||||
using Util::Format::successln;
|
||||
using Util::Format::warnbln;
|
||||
using Util::Format::warnln;
|
||||
|
||||
using Util::Format::comment;
|
||||
using Util::Format::commentb;
|
||||
using Util::Format::critical;
|
||||
using Util::Format::criticalb;
|
||||
using Util::Format::dbg;
|
||||
using Util::Format::dbgb;
|
||||
using Util::Format::info;
|
||||
using Util::Format::infob;
|
||||
using Util::Format::success;
|
||||
using Util::Format::successb;
|
||||
using Util::Format::warn;
|
||||
using Util::Format::warnb;
|
||||
using Util::Format::print;
|
||||
|
||||
} // namespace Util
|
||||
|
||||
Reference in New Issue
Block a user