From aaa00bc989e5c05318fcfbdd01c3ceacd4c16e3c Mon Sep 17 00:00:00 2001 From: Danny Robson Date: Fri, 21 Jul 2023 14:19:18 +1000 Subject: [PATCH] format: add 'quoted' helper function that emulates std::quoted --- CMakeLists.txt | 2 + format/quoted.hpp | 85 ++++++++++++++++++++++++++++++++++++++++++ test/format/quoted.cpp | 32 ++++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 format/quoted.hpp create mode 100644 test/format/quoted.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fd39941d..32532a20 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -352,6 +352,7 @@ list ( fixed_string.hpp float.cpp float.hpp + format/quoted.hpp fourcc.cpp fourcc.hpp fs/scoped.cpp @@ -730,6 +731,7 @@ if (TESTS) extent fixed float + format/quoted fs/scoped fs/tmp geom/aabb diff --git a/format/quoted.hpp b/format/quoted.hpp new file mode 100644 index 00000000..e1fdb287 --- /dev/null +++ b/format/quoted.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +namespace cruft::format { + namespace detail { + /// A utility string container that is rendered enclosed in quotes when formatted. + template + struct escaped_string { + /// The string that needs to be escaped + string_type source; + /// The character marking the beginning and end of the string that needs to be escaped + typename string_type::value_type delim; + /// The character that escapes other characters + typename string_type::value_type escape; + + constexpr bool + escaped_character (char c) const + { + return delim == c or escape == c; + } + + + /// Copies the string characters to an output iterator, inserting + /// delim and escape characters as required. + template + constexpr + OutputT + copy (OutputT dst) const + { + *dst++ = delim; + + for (const auto& c : source) { + if (escaped_character (c)) + *dst++ = escape; + + *dst++ = c; + } + + *dst++ = delim; + return dst; + } + }; + } + + /// Returns an object that will be enclosed between delimiters. All + /// occurrences of this delimiter will be escaped with the supplied + /// character (as will occurences of this escape character). + constexpr + auto + quoted ( + std::string_view source, + char delim = '"', + char escape = '\\' + ) { + return cruft::format::detail::escaped_string< + std::string_view + > { + .source = source, + .delim = delim, + .escape = escape, + }; + } +} + + +/////////////////////////////////////////////////////////////////////////////// +template +struct fmt::formatter> { + constexpr + format_parse_context::iterator + parse (fmt::format_parse_context ctx) const + { + return ctx.begin(); + } + + constexpr + format_context::iterator + format( + cruft::format::detail::escaped_string const& val, + format_context & ctx + ) const { + return val.copy (ctx.out()); + } +}; diff --git a/test/format/quoted.cpp b/test/format/quoted.cpp new file mode 100644 index 00000000..3f7ce86b --- /dev/null +++ b/test/format/quoted.cpp @@ -0,0 +1,32 @@ +#include + +#include + + +int main () +{ + static constexpr struct { + std::string_view format; + std::string_view inner; + std::string_view result; + std::string_view message; + } TESTS[] = { + { "{}", "Foo", R"("Foo")", "Simple word" }, + { "{}", "F\"oo", R"("F\"oo")", "Embedded delim" }, + { "{}", "F\\oo", R"("F\\oo")", "Embedded escape" }, + }; + + cruft::TAP::logger tap; + + for (auto const &t: TESTS) { + auto const result = fmt::vformat ( + t.format, + fmt::make_format_args ( + cruft::format::quoted (t.inner) + ) + ); + tap.expect_eq (result, t.result, "quoted format: {}", t.message); + } + + return tap.status (); +} \ No newline at end of file