Files
cpp-utils/pretty_print.h
Tom Hicks 3dd951eee9 Updates tests to work with the latest TinyTest.
Adds StringTraits::Literal variants that work with strings.
Names the workspace CPPUtils to match the import name in TinyTest.
This resolves the dependency issue.
2023-05-10 18:27:40 -07:00

511 lines
22 KiB
C++

/**********************************************************************************************************************
*
* @file pretty_print.h
*
* @brief Declares function templates for printing objects in a friendlier manner.
*
* This specifically helps for printing stl containers, tuples, pairs, and queues as well as quoting strings
* contained within them.
*
* @copyright Copyright (C) 2023 by Tom Hicks <headhunter3@gmail.com>
*
* Licensed under the MIT license see below for details.
*
**********************************************************************************************************************/
/*
* MIT License
*
* Copyright (c) 2023 Tom Hicks <headhunter3@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
* to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of
* the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
* THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
**********************************************************************************************************************/
#ifndef CPPUtils__pretty_print_h__
#define CPPUtils__pretty_print_h__
#include <initializer_list>
#include <iostream>
#include <ostream>
#include <queue>
#include <regex>
#include <string>
#include <tuple>
namespace CPPUtils {
/// @addtogroup pretty_print Pretty Print
/// @{
/// @name Helper Templates
/// These are not directly tested.
/// @{
/// @brief This SFINAE struct is used to select the correct wide or narrow string based the TChar.
/// @tparam TChar The character type of the string we want. This should either be char or wchar_t.
template <typename TChar> struct StringTraits;
/// @brief This SFINAE struct is use to select a narrow string.
template <> struct StringTraits<char> {
/// @brief Gets the narrow string.
/// @param narrow The narrow string. This is always returned by this specialization.
/// @param wide The wide string. This is ignored for this specialization.
/// @return The narrow string.
static constexpr const char *Literal(const char *narrow, const wchar_t *wide) { return narrow; }
/// @brief Gets the narrow string.
/// @param narrow The narrow string. This is always returned by this specialization.
/// @param wide The wide string. This is ignored for this specialization.
/// @return The narrow string.
static const std::string Literal(const std::string &narrow, const std::wstring &wide) { return narrow; }
};
/// @brief This SFINAE struct is used to select a wide string.
template <> struct StringTraits<wchar_t> {
/// @brief Gets the wide string.
/// @param narrow The narrow string. This ignored for this specialization.
/// @param wide The wide string. This is is always returned by this specialization.
/// @return The wide string.
static constexpr const wchar_t *Literal(const char *narrow, const wchar_t *wide) { return wide; }
/// @brief Gets the wide string.
/// @param narrow The narrow string. This ignored for this specialization.
/// @param wide The wide string. This is is always returned by this specialization.
/// @return The wide string.
static const std::wstring Literal(const std::string &narrow, const std::wstring &wide) { return wide; }
};
/// @brief This SFINAE struct is used to help select container like types.
/// @tparam T The potential container type.
template <typename T> struct is_container {
/// @brief Returns true because the type has begin and end methods.
/// @tparam U The type to check.
/// @param <anonymous> A pointer that is not used.
/// @return True because the type is a container.
template <typename U> static constexpr bool test(decltype(std::begin(std::declval<U>())) *) { return true; }
/// @brief Returns false because the type is not a container.
/// @tparam U The type to check.
/// @param <anonymous> A pointer that is not used.
/// @return False because the type is not a container.
template <typename U> static constexpr bool test(...) { return false; }
/// @brief Tests whether type T is a container.
static constexpr bool value = test<T>(nullptr);
};
/// @}
/// @name Escape for Printing
/// These methods all escape different kinds of strings for printing. Currently they replace the ansi escape character
/// with a printable version of its code "\033".
/// @{
/// @brief This function escapes a char* or wchar_t* for printing to cout or wcout.
///
/// It replaces the escape character with a its code.
/// @tparam TChar The type of character of our string. This should be dertermined automatically based on the input type.
/// @param text The text to escape.
/// @return The escaped string.
template <typename TChar> std::basic_string<TChar> EscapeForPrinting(const TChar *text);
// std::string and std::wstring
/// @brief This function escapes a std::string or std::wstring for printing to cout or wcout.
/// @tparam TChar The type of character of our string. This should be dertermined automatically based on the input type.
/// @tparam TTraits The char_traits type of our string. This should be determined automatically based on the input type.
/// @param text The text to escape.
/// @return The escaped string.
template <typename TChar, typename TTraits>
std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string<TChar, TTraits> &text);
// std::string_view and std::wstring_view
/// @brief This function escapes a std::string_view or std::wstring_view for printing to cout or wcout.
/// @tparam TChar The type of character of our string. This should be dertermined automatically based on the input type.
/// @tparam TTraits The char_traits type of our string. This should be determined automatically based on the input type.
/// @param text The text to escape.
/// @return The escaped string.
template <typename TChar, typename TTraits>
std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string_view<TChar, TTraits> &text);
/// @}
/// @name Pretty Print
/// These functions attempt to print friendlier versions of strings and objects that don't have operator<< defined for
/// them.
/// @{
/// @brief This function prints a pointer.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @param os The output stream to write to.
/// @param pointer The pointer to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const void *pointer);
/// @brief This function prints a const char* or const wchar_t* to the output stream.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @param os The output stream to write to.
/// @param text The text to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const TChar *text);
/// @brief This function prints a std::string or std::wstring to the output stream.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @param os The output stream to write to.
/// @param text The text to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::basic_string<TChar, TTraits> &text);
/// @brief This function prints a std::string_view or std::wstring_view to the output stream.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @param os The output stream to write to.
/// @param text The text to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::basic_string_view<TChar, TTraits> &text);
/// @brief This function prints a std::tuple to the output stream.
///
/// The output format for an empty tuple is "[]". For a tuple of (1, "two", 3.0) the output format is "[ 1, "two", 3.0
/// ]".
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam ...TArgs The types of the tuple.
/// @param os The output stream to write to.
/// @param tuple The tuple to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::tuple<TArgs...> &tuple);
/// @brief This function prints any stl-like container to the output stream.
///
/// It works with any class that has begin and end methods that return iterators.
///
/// The output format for an empty container is "[]". For a (vector<int>){1, 2, 3} the output format is "[ 1, 2, 3 ]".
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam TContainer The type of the container to print.
/// @tparam Ignore this parameter it has a default value.
/// @tparam Ignore this parameter it has a default value.
/// @param os The output stream to write to.
/// @param container The container to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename TContainer, typename, typename>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, TContainer container);
/// @brief This function prints an initializer list to the output stream.
///
/// The output format for a list {"hello", "world"} is "[ "hello", "world" ]". Unfortunately empty initializer lists are
/// treated as null pointers and will either print "null" or however your platform prints pointers.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam TItem The type of the item in the initializer list.
/// @param os The output stream to write to.
/// @param list The initializer_list to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename TItem>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::initializer_list<TItem> list);
/// @brief This function prints STL queues to the output stream.
///
/// The output format for an empty queue is "[]". The output format for a queue of 5,1,3 is "[ 5, 1, 3 ]".
///
/// The queue is copied and the copy is emptied.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam TItem The type of the item in the initializer list.
/// @param os The output stream to write to.
/// @param queue The queue to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename TItem>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::queue<TItem> queue);
/// @brief This function prints STL pairs to the output stream.
///
/// The output format for a pair of (3, "Kansas") is "(3, "Kansas")".
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam TFirst The type of the first item in the pair.
/// @tparam TSecond The type of the second item in the pair.
/// @param os The output stream to write to.
/// @param value The pair to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename TFirst, typename TSecond>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::pair<TFirst, TSecond> value);
/// @brief This function prints other items. It just pipes the item to the output stream.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam TItem The type of the item to print.
/// @param os The output stream to write to.
/// @param item The item to print.
/// @return The output stream for chaining.
template <typename TChar,
typename TTraits,
typename TItem,
typename std::enable_if<!is_container<TItem>::value>::type * = nullptr>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, TItem item);
/// @}
/// @name Pretty Print with Separator
/// These functions pretty print items with a separator.
/// @{
/// @brief This function prints varargs with a const char* or const wchar_t* separator between each pair.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam ...Args The types of the arguments to print.
/// @param os The output stream to write to.
/// @param separator The separator to use between the items.
/// @param ...args The items to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename... Args>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os, const TChar *separator, Args &&...args);
/// @brief This function prints varargs with a std::string or std::wstring separator between each pair.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam ...Args The types of the arguments to print.
/// @param os The output stream to write to.
/// @param separator The separator to use between the items.
/// @param ...args The items to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os,
std::basic_string<TChar, TTraits> separator,
TArgs &&...args);
/// @brief This function prints varargs with a std::string_view or std::wstring_view separator between each pair.
/// @tparam TChar The character type of our stream.
/// @tparam TTraits The character_traits type of our stream.
/// @tparam ...Args The types of the arguments to print.
/// @param os The output stream to write to.
/// @param separator The separator to use between the items.
/// @param ...args The items to print.
/// @return The output stream for chaining.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os,
std::basic_string_view<TChar, TTraits> separator,
TArgs &&...args);
/// @}
// const char* and const wchar_t*
template <typename TChar> std::basic_string<TChar> EscapeForPrinting(const TChar *text) {
std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033"));
const TChar *replace = StringTraits<TChar>::Literal("\\033", L"\\033");
return std::regex_replace(text, regex, replace);
}
// std::string and std::wstring
template <typename TChar, typename TTraits>
std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string<TChar, TTraits> &text) {
std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033"));
std::basic_string<TChar, TTraits> replace = StringTraits<TChar>::Literal("\\033", L"\\033");
return std::regex_replace(text, regex, replace);
}
// std::string_view and std::wstring_view
template <typename TChar, typename TTraits>
std::basic_string<TChar, TTraits> EscapeForPrinting(const std::basic_string_view<TChar, TTraits> &text) {
std::basic_string<TChar, TTraits> text_as_string = std::basic_string<TChar, TTraits>(text);
std::regex regex = std::regex(StringTraits<TChar>::Literal("\033", L"\033"));
std::basic_string<TChar, TTraits> replace = StringTraits<TChar>::Literal("\\033", L"\\033");
return std::regex_replace(text_as_string, regex, replace);
}
// pointers
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const void *pointer) {
if (pointer == nullptr) {
os << StringTraits<TChar>::Literal("null", L"null");
} else {
os << pointer;
}
return os;
}
// const char* and const wchar_t*
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const TChar *item) {
os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(item)
<< StringTraits<TChar>::Literal("\"", L"\"");
return os;
}
// std::string and std::wstring
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::basic_string<TChar, TTraits> &text) {
os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(text)
<< StringTraits<TChar>::Literal("\"", L"\"");
return os;
}
// std::string_view and std::wstring_view
template <typename TChar, typename TTraits>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::basic_string_view<TChar, TTraits> &text) {
os << StringTraits<TChar>::Literal("\"", L"\"") << EscapeForPrinting(text)
<< StringTraits<TChar>::Literal("\"", L"\"");
return os;
}
// std::tuple
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, const std::tuple<TArgs...> &tuple) {
std::apply(
[&os](auto &&...args) {
if (sizeof...(TArgs) == 0) {
os << StringTraits<TChar>::Literal("[]", L"[]]");
return;
}
size_t n = 0;
os << StringTraits<TChar>::Literal("[ ", L"[ ");
((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? StringTraits<TChar>::Literal(", ", L", ")
: StringTraits<TChar>::Literal("", L""))),
...);
os << StringTraits<TChar>::Literal(" ]", L" ]");
},
tuple);
return os;
}
// STL containers
template <typename TChar,
typename TTraits,
typename TContainer,
typename = decltype(std::begin(std::declval<TContainer>())),
typename = decltype(std::end(std::declval<TContainer>())),
typename = typename TContainer::value_type>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, TContainer container) {
if (container.size() <= 0) {
os << StringTraits<TChar>::Literal("[]", L"[]");
} else {
os << StringTraits<TChar>::Literal("[ ", L"[ ");
for (auto it = std::begin(container); it != std::end(container); it++) {
if (it != std::begin(container)) {
os << StringTraits<TChar>::Literal(", ", L", ");
}
PrettyPrint(os, *it);
}
os << StringTraits<TChar>::Literal(" ]", L" ]");
}
return os;
}
// std::initializer_list
template <typename TChar, typename TTraits, typename TItem>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::initializer_list<TItem> list) {
if (list.size() <= 0) {
os << StringTraits<TChar>::Literal("[]", L"[]");
return os;
}
os << StringTraits<TChar>::Literal("[ ", L"[ ");
for (auto it = std::begin(list); it != std::end(list); it++) {
if (it != std::begin(list)) {
os << StringTraits<TChar>::Literal(", ", L", ");
}
PrettyPrint(os, *it);
}
os << StringTraits<TChar>::Literal(" ]", L" ]");
return os;
}
// std::queue
template <typename TChar, typename TTraits, typename TItem>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::queue<TItem> queue) {
if (queue.empty()) {
os << StringTraits<TChar>::Literal("[]", L"[]");
return os;
}
os << StringTraits<TChar>::Literal("[ ", L"[ ");
PrettyPrint(os, queue.front());
queue.pop();
while (!queue.empty()) {
os << StringTraits<TChar>::Literal(", ", L", ");
PrettyPrint(os, queue.front());
queue.pop();
}
os << StringTraits<TChar>::Literal(" ]", L" ]");
return os;
}
// std::pair
template <typename TChar, typename TTraits, typename TFirst, typename TSecond>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, std::pair<TFirst, TSecond> value) {
os << StringTraits<TChar>::Literal("(", L"(");
PrettyPrint(os, value.first);
os << StringTraits<TChar>::Literal(", ", L", ");
PrettyPrint(os, value.second);
os << StringTraits<TChar>::Literal(")", L")");
return os;
}
// Catch-all for everything else.
template <typename TChar,
typename TTraits,
typename TItem,
typename std::enable_if<!is_container<TItem>::value>::type *>
auto &PrettyPrint(std::basic_ostream<TChar, TTraits> &os, TItem item) {
os << item;
return os;
}
// Prints args with separator between them. const char* separator.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os, const TChar *separator, TArgs &&...args) {
if (sizeof...(TArgs) == 0) {
os << StringTraits<TChar>::Literal("", L"");
return os;
}
size_t n = 0;
((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? separator : StringTraits<TChar>::Literal("", L""))), ...);
return os;
}
// Prints args with separator between them. std::string separator.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os,
std::basic_string<TChar, TTraits> separator,
TArgs &&...args) {
if (sizeof...(TArgs) == 0) {
os << StringTraits<TChar>::Literal("", L"");
return os;
}
size_t n = 0;
((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? separator : StringTraits<TChar>::Literal("", L""))), ...);
return os;
}
// Prints args with separator between them. std::string_view separator.
template <typename TChar, typename TTraits, typename... TArgs>
auto &PrettyPrintWithSeparator(std::basic_ostream<TChar, TTraits> &os,
std::basic_string_view<TChar, TTraits> separator,
TArgs &&...args) {
if (sizeof...(TArgs) == 0) {
os << StringTraits<TChar>::Literal("", L"");
return os;
}
size_t n = 0;
((PrettyPrint(os, args) << (++n != sizeof...(TArgs) ? separator : StringTraits<TChar>::Literal("", L""))), ...);
return os;
}
/// @}
} // End namespace CPPUtils
#endif // End !defined CPPUtils__pretty_print_h__