diff --git a/Makefile.am b/Makefile.am index 8e0067cb..d83d31c7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,6 +51,9 @@ UTIL_FILES = \ fixed.hpp \ float.cpp \ float.hpp \ + format.cpp \ + format.hpp \ + format.ipp \ fourcc.cpp \ fourcc.hpp \ guid.cpp \ @@ -315,6 +318,7 @@ TEST_BIN = \ test/extent \ test/fixed \ test/float \ + test/format \ test/hash/murmur \ test/hash/fasthash \ test/hmac \ diff --git a/format.cpp b/format.cpp new file mode 100644 index 00000000..284e3d3a --- /dev/null +++ b/format.cpp @@ -0,0 +1,26 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2015 Danny Robson + */ + +#include "format.hpp" + +#include + + +//std::string +//util::format (std::string &&fmt) +//{ +// return std::move (fmt); +//} diff --git a/format.hpp b/format.hpp new file mode 100644 index 00000000..5f7ebb86 --- /dev/null +++ b/format.hpp @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2015 Danny Robson + */ + +#ifndef __UTIL_FORMAT_HPP +#define __UTIL_FORMAT_HPP + +#include +#include + +namespace util { + namespace format { + template + std::string render (const std::string &fmt, Args&&...); + + class error : public std::runtime_error + { using runtime_error::runtime_error; }; + + // value-specifier mismatch + class value_error : public error + { using error::error; }; + + // malformed format specifier + class format_error : public error + { using error::error; }; + + // missing format specifier + class missing_error : public error + { using error::error; }; + } +} + +#include "format.ipp" + +#endif diff --git a/format.ipp b/format.ipp new file mode 100644 index 00000000..042502b9 --- /dev/null +++ b/format.ipp @@ -0,0 +1,94 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2015 Danny Robson + */ + +#if defined(__UTIL_FORMAT_IPP) +#error +#endif +#define __UTIL_FORMAT_IPP + +#include "debug.hpp" + +#include +#include +#include +#include + +namespace util { + namespace detail { namespace format { + template + void + render (InputIt first, + InputIt last, + std::ostringstream &dest) + { + static const char DELIMITER = '%'; + if (std::find (first, last, DELIMITER) != last) + throw util::format::missing_error ("format specifier without value"); + + std::copy (first, last, std::ostream_iterator (dest)); + } + + + template + void + render (InputIt first, + InputIt last, + std::ostringstream &dest, + const ValueT& val, + Args&& ...args) + { + CHECK (first <= last); + + static const char DELIMITER = '%'; + auto cursor = std::find (first, last, DELIMITER); + std::copy (first, cursor, std::ostream_iterator (dest)); + + if (cursor == last) + return; + + auto spec = cursor + 1; + if (spec == last) + throw util::format::format_error ("missing format specifier"); + + if (*spec != 's') + throw util::format::format_error ("unhandled format specifier"); + + dest << val; + + render (spec + 1, last, dest, std::forward (args)...); + } + } } + + namespace format { + template + std::string + render (const std::string &fmt, Args&&... args) + { + std::ostringstream out; + + util::detail::format::render ( + fmt.begin (), + fmt.end (), + out, + std::forward (args)... + ); + + return out.str (); + } + } +} diff --git a/test/format.cpp b/test/format.cpp new file mode 100644 index 00000000..0e12d856 --- /dev/null +++ b/test/format.cpp @@ -0,0 +1,20 @@ +#include "format.hpp" + +#include "tap.hpp" + +int +main (void) +{ + using namespace std::string_literals; + + util::TAP::logger tap; + + tap.expect_eq (util::format::render ("identity"), "identity"s, "identity literal"); + tap.expect_eq (util::format::render ("%s", "identity"s), "identity"s, "identity substitution"); + + tap.expect_throw ([] (void) { util::format::render ("%s"); }); + tap.expect_throw ([] (void) { util::format::render ("%!", 42); }); + tap.expect_throw ([] (void) { util::format::render ("%", 42); }); + + return tap.status (); +}