summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libadhocutil/compileTimeFormatter.h111
-rw-r--r--libadhocutil/unittests/Jamfile.jam11
-rw-r--r--libadhocutil/unittests/testCompileTimeFormatter.cpp138
3 files changed, 260 insertions, 0 deletions
diff --git a/libadhocutil/compileTimeFormatter.h b/libadhocutil/compileTimeFormatter.h
new file mode 100644
index 0000000..aa30e20
--- /dev/null
+++ b/libadhocutil/compileTimeFormatter.h
@@ -0,0 +1,111 @@
+#include <iostream>
+#include <string.h>
+#include <boost/static_assert.hpp>
+
+namespace AdHoc {
+ template <char...> struct Buffer { };
+
+ template <char stop, char ...>
+ struct Upto {
+ template<typename stream>
+ static auto stuff(stream &, const Buffer<> & f)
+ {
+ return f;
+ }
+ };
+ template <char stop, char s0, char... sn>
+ struct Upto<stop, s0, sn...> {
+ template<typename stream, char... sm>
+ static auto stuff(stream & s, const Buffer<sm...> &)
+ {
+ return Upto<stop, sn...>::stuff(s, Buffer<sm..., s0>());
+ }
+ };
+ template <char stop, char... sn>
+ struct Upto<stop, stop, sn...> {
+ template<typename stream, char... sm>
+ static auto stuff(stream & s, const Buffer<sm...> &)
+ {
+ char buf[] = {sm...};
+ s.write(buf, sizeof...(sm));
+ return Buffer<stop, sn...>();
+ }
+ };
+ template <char stop, char... sn>
+ struct Upto<stop, 0, sn...> {
+ template<typename stream, char... sm>
+ static auto stuff(stream & s, const Buffer<sm...> &)
+ {
+ char buf[] = {sm...};
+ s.write(buf, sizeof...(sm));
+ return Buffer<0, sn...>();
+ }
+ };
+
+ template <typename stream, char ... sn>
+ struct StreamWriter {
+ template<typename ... Pn>
+ static void write(stream & s, const Pn & ... pn)
+ {
+ next(s, Upto<'%', sn...>::stuff(s, Buffer<>()), pn...);
+ }
+ template<typename ... Pn, char... ssn, template <char...> class Buffer>
+ static void next(stream & s, const Buffer<ssn...>&, const Pn & ... pn)
+ {
+ StreamWriter<stream, ssn...>::write(s, pn...);
+ }
+ };
+
+ template<typename stream>
+ struct StreamWriter<stream> {
+ template<typename ... Pn>
+ static void write(stream &, const Pn & ...) { }
+ };
+
+ template<typename stream>
+ struct StreamWriter<stream, 0> {
+ template<typename ... Pn>
+ static void write(stream &, const Pn & ...) { }
+ };
+
+ // Default stream writer formatter
+ template<typename stream, char ... sn>
+ struct StreamWriter<stream, '%', '?', sn...> {
+ template<typename P, typename ... Pn>
+ static void write(stream & s, const P & p, const Pn & ... pn)
+ {
+ s << p;
+ StreamWriter<stream, sn...>::write(s, pn...);
+ }
+ };
+
+ // Unknown stream writer formatter
+ template<typename stream, char ... sn>
+ struct StreamWriter<stream, '%', sn...> {
+ template<typename ... Pn>
+ static void write(stream &, const Pn & ...)
+ {
+ BOOST_STATIC_ASSERT_MSG(!&err, "invalid format string/arguments");
+ }
+ static int err;
+ };
+
+ template <const char * const & S, char s0 = *S, int offset = 0, char ... sn>
+ struct Formatter {
+ template<typename stream, typename ... Pn>
+ static void write(stream & s, const Pn & ... pn)
+ {
+ Formatter<S, S[offset], offset + 1, sn..., S[offset]>::write(s, pn...);
+ }
+ };
+
+ template <const char * const & S, int offset, char ... sn>
+ struct Formatter<S, 0, offset, sn...> {
+ template<typename stream, typename ... Pn>
+ static void write(stream & s, const Pn & ... pn)
+ {
+ StreamWriter<stream, sn...>::write(s, pn...);
+ }
+ };
+}
+
diff --git a/libadhocutil/unittests/Jamfile.jam b/libadhocutil/unittests/Jamfile.jam
index 5072f39..2fb148b 100644
--- a/libadhocutil/unittests/Jamfile.jam
+++ b/libadhocutil/unittests/Jamfile.jam
@@ -9,6 +9,17 @@ lib boost_thread ;
lib dl ;
run
+ testCompileTimeFormatter.cpp
+ : : :
+ <define>ROOT=\"$(me)\"
+ <define>BOOST_TEST_DYN_LINK
+ <library>..//adhocutil
+ <library>boost_system
+ <library>boost_filesystem
+ <library>boost_utf
+ ;
+
+run
testUriParse.cpp
: : :
<define>ROOT=\"$(me)\"
diff --git a/libadhocutil/unittests/testCompileTimeFormatter.cpp b/libadhocutil/unittests/testCompileTimeFormatter.cpp
new file mode 100644
index 0000000..4248cb6
--- /dev/null
+++ b/libadhocutil/unittests/testCompileTimeFormatter.cpp
@@ -0,0 +1,138 @@
+#define BOOST_TEST_MODULE CompileTimeFormatter
+#include <boost/test/unit_test.hpp>
+
+#include <compileTimeFormatter.h>
+
+#include <boost/filesystem/path.hpp>
+
+using namespace AdHoc;
+
+extern constexpr const char * formatEdgeCaseEmpty = "";
+extern constexpr const char * formatEdgeCaseSingle = "1";
+extern constexpr const char * formatEdgeCaseFormatStart = "%? after";
+extern constexpr const char * formatEdgeCaseFormatEnd = "before %?";
+extern constexpr const char * formatEdgeCaseFormatLonely = "%?";
+
+extern constexpr const char * formatStringLiteral = "literal";
+extern constexpr const char * formatStringSingle = "single %?.";
+extern constexpr const char * formatStringMulti = "First %?, then %?.";
+
+BOOST_AUTO_TEST_CASE ( empty )
+{
+ std::stringstream buf;
+ Formatter<formatEdgeCaseEmpty>::write(buf);
+ BOOST_REQUIRE(buf.str().empty());
+}
+
+BOOST_AUTO_TEST_CASE ( single )
+{
+ std::stringstream buf;
+ Formatter<formatEdgeCaseSingle>::write(buf);
+ BOOST_REQUIRE_EQUAL(buf.str(), "1");
+}
+
+BOOST_AUTO_TEST_CASE ( start )
+{
+ std::stringstream buf;
+ Formatter<formatEdgeCaseFormatStart>::write(buf, 10);
+ BOOST_REQUIRE_EQUAL(buf.str(), "10 after");
+}
+
+BOOST_AUTO_TEST_CASE ( end )
+{
+ std::stringstream buf;
+ Formatter<formatEdgeCaseFormatEnd>::write(buf, 10);
+ BOOST_REQUIRE_EQUAL(buf.str(), "before 10");
+}
+
+BOOST_AUTO_TEST_CASE ( lonely )
+{
+ std::stringstream buf;
+ Formatter<formatEdgeCaseFormatLonely>::write(buf, 10);
+ BOOST_REQUIRE_EQUAL(buf.str(), "10");
+}
+
+BOOST_AUTO_TEST_CASE ( literal )
+{
+ std::stringstream buf;
+ Formatter<formatStringLiteral>::write(buf);
+ BOOST_REQUIRE_EQUAL(buf.str(), "literal");
+}
+
+BOOST_AUTO_TEST_CASE ( singleInt )
+{
+ std::stringstream buf;
+ Formatter<formatStringSingle>::write(buf, 32);
+ BOOST_REQUIRE_EQUAL(buf.str(), "single 32.");
+}
+
+BOOST_AUTO_TEST_CASE ( singleDouble )
+{
+ std::stringstream buf;
+ Formatter<formatStringSingle>::write(buf, 3.14);
+ BOOST_REQUIRE_EQUAL(buf.str(), "single 3.14.");
+}
+
+BOOST_AUTO_TEST_CASE ( singlePath )
+{
+ std::stringstream buf;
+ boost::filesystem::path p("/tmp/test/path");
+ Formatter<formatStringSingle>::write(buf, p);
+ BOOST_REQUIRE_EQUAL(buf.str(), R"(single "/tmp/test/path".)");
+}
+
+BOOST_AUTO_TEST_CASE ( multi )
+{
+ std::stringstream buf;
+ Formatter<formatStringMulti>::write(buf, "one", "two");
+ BOOST_REQUIRE_EQUAL(buf.str(), "First one, then two.");
+}
+
+namespace AdHoc {
+ // Custom stream writer formatter, formats as (bracketed expression)
+ template<typename stream, char ... sn>
+ struct StreamWriter<stream, '%', '(', ')', sn...> {
+ template<typename P, typename ... Pn>
+ static void write(stream & s, const P & p, const Pn & ... pn)
+ {
+ s << "-( " << p << " )-";
+ StreamWriter<stream, sn...>::write(s, pn...);
+ }
+ };
+ // Custom stream writer formatter, formats
+ // right-aligned by given width
+ template<typename stream, char ... sn>
+ struct StreamWriter<stream, '%', 'r', 'a', sn...> {
+ template<typename P, typename ... Pn>
+ static void write(stream & s, int width, const P & p, const Pn & ... pn)
+ {
+ std::stringstream buf;
+ buf << p;
+ std::string spaces(width - buf.str().length(), ' ');
+ s << spaces << buf.str();
+ StreamWriter<stream, sn...>::write(s, pn...);
+ }
+ };
+}
+
+extern constexpr const char * formatStringCustom = "custom %()";
+BOOST_AUTO_TEST_CASE ( customBracketted )
+{
+ std::stringstream buf;
+ Formatter<formatStringCustom>::write(buf, "expr");
+ BOOST_REQUIRE_EQUAL(buf.str(), "custom -( expr )-");
+}
+
+extern constexpr const char * formatStringMultiArg = "value%ra";
+BOOST_AUTO_TEST_CASE ( customMultiArgRightAlign )
+{
+ std::stringstream buf1, buf2, buf3;
+ const int width = 20;
+ Formatter<formatStringMultiArg>::write(buf1, width, "something");
+ Formatter<formatStringMultiArg>::write(buf2, width, "something else");
+ Formatter<formatStringMultiArg>::write(buf3, width, 123.45);
+ BOOST_REQUIRE_EQUAL(buf1.str(), "value something");
+ BOOST_REQUIRE_EQUAL(buf2.str(), "value something else");
+ BOOST_REQUIRE_EQUAL(buf3.str(), "value 123.45");
+}
+