summaryrefslogtreecommitdiffstats
path: root/cxxformat.hpp
diff options
context:
space:
mode:
authorMarkus Mittendrein <git@maxmitti.tk>2019-07-20 23:22:45 +0200
committerMarkus Mittendrein <git@maxmitti.tk>2019-07-20 23:22:45 +0200
commit14d7e7275eb54efdfe59ba3ce3b53634286c3d05 (patch)
treec7d0eaed2826dda18a05da9e91d798c4a9a3418f /cxxformat.hpp
downloadcxxformat-14d7e7275eb54efdfe59ba3ce3b53634286c3d05.tar.gz
cxxformat-14d7e7275eb54efdfe59ba3ce3b53634286c3d05.zip
Initial
Diffstat (limited to 'cxxformat.hpp')
-rw-r--r--cxxformat.hpp435
1 files changed, 435 insertions, 0 deletions
diff --git a/cxxformat.hpp b/cxxformat.hpp
new file mode 100644
index 0000000..d4e8dfd
--- /dev/null
+++ b/cxxformat.hpp
@@ -0,0 +1,435 @@
+/*
+Copyright 2019 Mittendrein Markus
+
+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.
+*/
+
+#pragma once
+#include <string>
+#include <string_view>
+#include <optional>
+#include <cstring>
+#include <stdexcept>
+#include <cstdint>
+
+namespace format {
+
+using FormatException = std::invalid_argument;
+
+template<typename To, typename From, typename Enable = void>
+struct FormatConvert {};
+
+template<typename From>
+struct FormatConvert<std::intmax_t, From, std::enable_if_t<std::is_signed_v<From> && std::is_integral_v<From>>>
+{
+ static constexpr std::intmax_t convert(From from)
+ {
+ return from;
+ }
+};
+
+template<typename From>
+struct FormatConvert<std::intmax_t, From, std::enable_if_t<std::is_unsigned_v<From> && std::is_integral_v<From> && (sizeof(From) < sizeof(std::intmax_t))>>
+{
+ static constexpr std::intmax_t convert(From from)
+ {
+ return from;
+ }
+};
+
+template<typename From>
+struct FormatConvert<std::uintmax_t, From, std::enable_if_t<std::is_unsigned_v<From> && std::is_integral_v<From>>>
+{
+ static constexpr std::uintmax_t convert(From from)
+ {
+ return from;
+ }
+};
+
+template<typename String>
+struct FormatConvert<std::string_view, String, std::enable_if_t<std::is_convertible_v<String, std::string_view>>>
+{
+ static constexpr std::string_view convert(const String &str)
+ {
+ return str;
+ }
+};
+
+template<typename From>
+struct FormatConvert<double, From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>>
+{
+ static constexpr double convert(const From& val)
+ {
+ return val;
+ }
+};
+
+template<typename Pointer>
+struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && !std::is_array_v<std::remove_pointer_t<Pointer>> && !std::is_same_v<std::remove_pointer_t<Pointer>, char>>>
+{
+ static constexpr const void *convert(const Pointer* pointer)
+ {
+ return pointer;
+ }
+};
+
+template<typename Pointer>
+struct FormatConvert<const void *, Pointer, std::enable_if_t<std::is_pointer_v<Pointer> && std::is_array_v<std::remove_pointer_t<Pointer>>>>
+{
+ static constexpr const void *convert(const Pointer pointer)
+ {
+ return pointer;
+ }
+};
+
+template<typename From, typename Enable = void>
+struct AutoConversion
+{
+ static std::string conversion(const From &)
+ {
+ throw FormatException{""};
+ }
+};
+
+template<typename From>
+struct AutoConversion<From, std::enable_if_t<std::is_integral_v<From> && std::is_unsigned_v<From>>>
+{
+ static std::string conversion(const From &)
+ {
+ return "u";
+ }
+};
+
+template<typename From>
+struct AutoConversion<From, std::enable_if_t<std::is_integral_v<From> && std::is_signed_v<From>>>
+{
+ static std::string conversion(const From &)
+ {
+ return "d";
+ }
+};
+
+template<typename From>
+struct AutoConversion<From, std::enable_if_t<std::is_convertible_v<From, double> && !std::is_integral_v<From>>>
+{
+ static std::string conversion(const From &)
+ {
+ return "G";
+ }
+};
+
+template<typename From>
+struct AutoConversion<From, std::enable_if_t<std::is_convertible_v<From, std::string_view>>>
+{
+ static std::string conversion(const From &)
+ {
+ return "s";
+ }
+};
+
+template<>
+struct AutoConversion<char>
+{
+ static std::string conversion(const char)
+ {
+ return "c";
+ }
+};
+
+namespace detail
+{
+ template<typename T>
+ struct remove_cvref { using type = std::remove_cv_t<std::remove_reference_t<T>>; };
+
+ template<typename T>
+ using remove_cvref_t = typename remove_cvref<T>::type;
+
+ template<typename T, bool Ok = false>
+ struct StointHelper
+ {
+ static T convert(const std::string &, std::size_t *, int)
+ {
+ static_assert(Ok, "stoint: Unsupported type");
+ return T{};
+ }
+ };
+
+ template<typename T, T(converter)(const std::string &, std::size_t *, int)>
+ struct StointHelperConverter
+ {
+ static T convert(const std::string &str, std::size_t *pos, int base)
+ {
+ return converter(str, pos, base);
+ }
+ };
+
+ template<>
+ struct StointHelper<int> : StointHelperConverter<int, std::stoi> {};
+ template<>
+ struct StointHelper<long> : StointHelperConverter<long, std::stol> {};
+ template<>
+ struct StointHelper<long long> : StointHelperConverter<long long, std::stoll> {};
+ template<>
+ struct StointHelper<unsigned long> : StointHelperConverter<unsigned long, std::stoul> {};
+ template<>
+ struct StointHelper<unsigned long long> : StointHelperConverter<unsigned long long, std::stoull> {};
+
+ template<typename T>
+ T stoint(const std::string &str, std::size_t *pos = nullptr, int base = 10)
+ {
+ return StointHelper<T>::convert(str, pos, base);
+ }
+
+ template<typename... Args>
+ std::string strprintf(const char *fmt, Args... args)
+ {
+ int size = std::snprintf(nullptr, 0, fmt, args...) + 1;
+ std::string result;
+ result.resize(size);
+ std::snprintf(result.data(), size, fmt, args...);
+ result.resize(size - 1);
+ return result;
+ }
+
+ template<typename To, typename From, bool allowFallback, typename Enable = void>
+ struct FormatConvertHelper
+ {
+ static constexpr To convert(const From &)
+ {
+ static_assert(allowFallback, "Impossible conversion needed");
+ throw FormatException{""};
+ }
+ };
+
+ template<typename T, bool allowFallback>
+ struct FormatConvertHelper<T, T, allowFallback>
+ {
+ static constexpr const T &convert(const T &from)
+ {
+ return from;
+ }
+ };
+
+ template<typename To, typename From, bool allowFallback>
+ struct FormatConvertHelper<To, From, allowFallback, std::enable_if_t<!std::is_same_v<To, From> && std::is_same_v<remove_cvref_t<decltype(FormatConvert<To, remove_cvref_t<From>>::convert(std::declval<From>()))>, remove_cvref_t<To>>>>
+ {
+ static constexpr To convert(const From &from)
+ {
+ return FormatConvert<To, remove_cvref_t<From>>::convert(from);
+ }
+ };
+
+ template<typename To, bool allowFallback = true, typename From>
+ auto convert(const From &from, const char *toName, std::size_t index = 0)
+ {
+ try
+ {
+ return FormatConvertHelper<To, From, allowFallback>::convert(from);
+ }
+ catch (const FormatException &)
+ {
+ throw FormatException{"Argument#" + std::to_string(index) + " is not convertible to " + std::string{toName}};
+ }
+ }
+
+ template<typename From, typename Enable = void>
+ struct StringOrStringViewHelper
+ {
+ using type = std::string_view;
+ };
+
+ template<typename From>
+ struct StringOrStringViewHelper<From, std::enable_if_t<std::is_same_v<std::string, std::decay_t<decltype(FormatConvert<std::string, From>::convert(std::declval<From>()))>>>>
+ {
+ using type = std::string;
+ };
+
+ template<typename From>
+ using string_or_string_view = typename StringOrStringViewHelper<From>::type;
+
+ template<bool allowPointer = false, typename From>
+ std::string autoConversionSpecifier(From &&from, std::size_t index = 0)
+ {
+ try
+ {
+ return AutoConversion<remove_cvref_t<From>>::conversion(std::forward<From>(from));
+ }
+ catch (const FormatException &)
+ {
+ if constexpr (std::is_pointer_v<From>)
+ {
+ if constexpr (allowPointer)
+ {
+ return "p";
+ }
+ else
+ {
+ throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion with %v, use %V to also allow arbitrary pointers"};
+ }
+ }
+ throw FormatException{"Argument#" + std::to_string(index) + " is not applicable for auto conversion (%v / %V)"};
+ }
+ }
+
+ std::string format(std::string_view fmt, const std::size_t argumentIndex)
+ {
+ const auto fmtSize = fmt.size();
+ for (std::size_t i = 0; i < fmtSize; ++i)
+ {
+ if (fmt[i] == '%')
+ {
+ if (i >= fmtSize - 1)
+ {
+ throw FormatException{"Incomplete directive"};
+ }
+
+ if (fmt[++i] == '%')
+ {
+ return std::string{fmt.substr(0, i)} + format(fmt.substr(i + 1), argumentIndex);
+ }
+
+ throw FormatException{"Not enough arguments passed"};
+ }
+ }
+ return std::string{fmt};
+ }
+
+ template<typename Arg, typename... Args>
+ std::string format(std::string_view fmt, const std::size_t argumentIndex, Arg &&arg, Args &&... args)
+ {
+ const auto fmtSize = fmt.size();
+ for (std::size_t i = 0; i < fmtSize; ++i)
+ {
+ auto checkFmtLength = [i, fmtSize]
+ {
+ if (i >= fmtSize - 1)
+ {
+ throw FormatException{"Incomplete directive"};
+ }
+ };
+ const auto current = fmt[i];
+ if (current == '%')
+ {
+ const auto directiveStart = i;
+ checkFmtLength();
+ std::string specification{"%"};
+
+ const auto flag = fmt[++i];
+ if (flag == '%') // handle %% here
+ {
+ return std::string{fmt.substr(0, directiveStart + 1)} + format(fmt.substr(i + 1), argumentIndex, std::forward<Arg>(arg), std::forward<Args>(args)...);
+ }
+ switch (flag)
+ {
+ case '#': case '0': case '-': case ' ': case '+':
+ specification.push_back(flag);
+ checkFmtLength();
+ break;
+ default:
+ --i;
+ }
+
+ std::string tempNumber;
+ // field width
+ while (std::isdigit(fmt[++i]))
+ {
+ tempNumber.push_back(fmt[i]);
+ checkFmtLength();
+ }
+ --i;
+ const auto fieldWidth = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber);
+ tempNumber.clear();
+
+ std::optional<std::size_t> precision;
+ // precision
+ if (fmt[++i] == '.')
+ {
+ checkFmtLength();
+ while (std::isdigit(fmt[++i]))
+ {
+ tempNumber.push_back(fmt[i]);
+ checkFmtLength();
+ }
+ precision = tempNumber.empty() ? 0 : stoint<std::size_t>(tempNumber);
+ }
+ --i;
+
+ std::string conversion{fmt[++i]};
+ if(conversion.front() == 'v')
+ {
+ conversion = autoConversionSpecifier(std::forward<Arg>(arg), argumentIndex);
+ }
+ else if(conversion.front() == 'V')
+ {
+ conversion = autoConversionSpecifier<true>(std::forward<Arg>(arg), argumentIndex);
+ }
+
+ auto continueFormatting = [directiveStart, &args..., fmt, &specification, &fieldWidth, &precision, i, conversion, argumentIndex](auto length, auto converted)
+ {
+ if (fieldWidth) specification += std::to_string(fieldWidth);
+ if (precision) specification += '.' + std::to_string(*precision);
+ if (length) specification += length;
+ specification += conversion;
+ return std::string{fmt.substr(0, directiveStart)} + strprintf(specification.c_str(), converted) + format(fmt.substr(i + 1), argumentIndex + 1, std::forward<Args>(args)...);
+ };
+
+ switch (conversion.back())
+ {
+ case 'c':
+ return continueFormatting(false, convert<char>(std::forward<Arg>(arg), "character", argumentIndex));
+
+ case 'd': case 'i':
+ return continueFormatting('j', convert<std::intmax_t>(std::forward<Arg>(arg), "signed integer", argumentIndex));
+
+ case 'u': case 'o': case 'x': case 'X':
+ return continueFormatting('j', convert<std::uintmax_t>(std::forward<Arg>(arg), "unsigned integer", argumentIndex));
+
+ case 's':
+ {
+ const auto& string = convert<string_or_string_view<remove_cvref_t<Arg>>>(std::forward<Arg>(arg), "string", argumentIndex);
+ precision = precision ? std::min(*precision, string.size()) : string.size();
+ return continueFormatting(false, string.data());
+ }
+
+ case 'e': case 'E': case 'f': case 'F': case 'g': case 'G': case 'a': case 'A':
+ return continueFormatting(false, convert<double>(std::forward<Arg>(arg), "double", argumentIndex));
+
+ case 'p':
+ return continueFormatting(false, convert<const void *>(std::forward<Arg>(arg), "pointer", argumentIndex));
+
+ default:
+ throw FormatException{std::string{"Unknown conversion specifier: \""} + conversion + "\""};
+ }
+ }
+ }
+
+ throw FormatException{std::to_string(argumentIndex - 1) + " placeholders found, but " + std::to_string(argumentIndex + sizeof...(args)) + " arguments passed"};
+ }
+}
+
+template<bool NoThrow = false, typename String, typename... Args>
+std::string format(const String &fmt, Args &&... args)
+{
+ const auto fmtStr = detail::convert<std::string_view, false>(fmt, "string");
+ try
+ {
+ return detail::format(fmtStr, 1, std::forward<Args>(args)...);
+ }
+ catch (const FormatException &e)
+ {
+ const auto &message = std::string{"format(\""} + std::string{fmtStr} + "\", " + std::to_string(sizeof...(args)) + " more arguments...): " + e.what();
+
+ if constexpr (NoThrow) return message;
+ else throw FormatException(message);
+ }
+}
+
+} \ No newline at end of file