summaryrefslogtreecommitdiff
path: root/utils/text
diff options
context:
space:
mode:
Diffstat (limited to 'utils/text')
-rw-r--r--utils/text/Kyuafile9
-rw-r--r--utils/text/Makefile.am.inc74
-rw-r--r--utils/text/exceptions.cpp91
-rw-r--r--utils/text/exceptions.hpp77
-rw-r--r--utils/text/exceptions_test.cpp76
-rw-r--r--utils/text/operations.cpp261
-rw-r--r--utils/text/operations.hpp68
-rw-r--r--utils/text/operations.ipp91
-rw-r--r--utils/text/operations_test.cpp435
-rw-r--r--utils/text/regex.cpp302
-rw-r--r--utils/text/regex.hpp92
-rw-r--r--utils/text/regex_fwd.hpp46
-rw-r--r--utils/text/regex_test.cpp177
-rw-r--r--utils/text/table.cpp428
-rw-r--r--utils/text/table.hpp125
-rw-r--r--utils/text/table_fwd.hpp58
-rw-r--r--utils/text/table_test.cpp413
-rw-r--r--utils/text/templates.cpp764
-rw-r--r--utils/text/templates.hpp122
-rw-r--r--utils/text/templates_fwd.hpp45
-rw-r--r--utils/text/templates_test.cpp1001
21 files changed, 4755 insertions, 0 deletions
diff --git a/utils/text/Kyuafile b/utils/text/Kyuafile
new file mode 100644
index 000000000000..e4e870e9c648
--- /dev/null
+++ b/utils/text/Kyuafile
@@ -0,0 +1,9 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="operations_test"}
+atf_test_program{name="regex_test"}
+atf_test_program{name="table_test"}
+atf_test_program{name="templates_test"}
diff --git a/utils/text/Makefile.am.inc b/utils/text/Makefile.am.inc
new file mode 100644
index 000000000000..d474ae191bf5
--- /dev/null
+++ b/utils/text/Makefile.am.inc
@@ -0,0 +1,74 @@
+# Copyright 2012 The Kyua Authors.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Google Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+libutils_a_SOURCES += utils/text/exceptions.cpp
+libutils_a_SOURCES += utils/text/exceptions.hpp
+libutils_a_SOURCES += utils/text/operations.cpp
+libutils_a_SOURCES += utils/text/operations.hpp
+libutils_a_SOURCES += utils/text/operations.ipp
+libutils_a_SOURCES += utils/text/regex.cpp
+libutils_a_SOURCES += utils/text/regex.hpp
+libutils_a_SOURCES += utils/text/regex_fwd.hpp
+libutils_a_SOURCES += utils/text/table.cpp
+libutils_a_SOURCES += utils/text/table.hpp
+libutils_a_SOURCES += utils/text/table_fwd.hpp
+libutils_a_SOURCES += utils/text/templates.cpp
+libutils_a_SOURCES += utils/text/templates.hpp
+libutils_a_SOURCES += utils/text/templates_fwd.hpp
+
+if WITH_ATF
+tests_utils_textdir = $(pkgtestsdir)/utils/text
+
+tests_utils_text_DATA = utils/text/Kyuafile
+EXTRA_DIST += $(tests_utils_text_DATA)
+
+tests_utils_text_PROGRAMS = utils/text/exceptions_test
+utils_text_exceptions_test_SOURCES = utils/text/exceptions_test.cpp
+utils_text_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/operations_test
+utils_text_operations_test_SOURCES = utils/text/operations_test.cpp
+utils_text_operations_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_operations_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/regex_test
+utils_text_regex_test_SOURCES = utils/text/regex_test.cpp
+utils_text_regex_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_regex_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/table_test
+utils_text_table_test_SOURCES = utils/text/table_test.cpp
+utils_text_table_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_table_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_text_PROGRAMS += utils/text/templates_test
+utils_text_templates_test_SOURCES = utils/text/templates_test.cpp
+utils_text_templates_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_text_templates_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/text/exceptions.cpp b/utils/text/exceptions.cpp
new file mode 100644
index 000000000000..1692cfea7edb
--- /dev/null
+++ b/utils/text/exceptions.cpp
@@ -0,0 +1,91 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::regex_error::regex_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::regex_error::~regex_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::syntax_error::syntax_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::syntax_error::~syntax_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+text::value_error::value_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+text::value_error::~value_error(void) throw()
+{
+}
diff --git a/utils/text/exceptions.hpp b/utils/text/exceptions.hpp
new file mode 100644
index 000000000000..da0cfd98fb88
--- /dev/null
+++ b/utils/text/exceptions.hpp
@@ -0,0 +1,77 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/exceptions.hpp
+/// Exception types raised by the text module.
+
+#if !defined(UTILS_TEXT_EXCEPTIONS_HPP)
+#define UTILS_TEXT_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+namespace utils {
+namespace text {
+
+
+/// Base exceptions for text errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exception denoting an error in a regular expression.
+class regex_error : public error {
+public:
+ explicit regex_error(const std::string&);
+ ~regex_error(void) throw();
+};
+
+
+/// Exception denoting an error while parsing templates.
+class syntax_error : public error {
+public:
+ explicit syntax_error(const std::string&);
+ ~syntax_error(void) throw();
+};
+
+
+/// Exception denoting an error in a text value format.
+class value_error : public error {
+public:
+ explicit value_error(const std::string&);
+ ~value_error(void) throw();
+};
+
+
+} // namespace text
+} // namespace utils
+
+
+#endif // !defined(UTILS_TEXT_EXCEPTIONS_HPP)
diff --git a/utils/text/exceptions_test.cpp b/utils/text/exceptions_test.cpp
new file mode 100644
index 000000000000..1d3c3910900a
--- /dev/null
+++ b/utils/text/exceptions_test.cpp
@@ -0,0 +1,76 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+namespace text = utils::text;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const text::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(regex_error);
+ATF_TEST_CASE_BODY(regex_error)
+{
+ const text::regex_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_error);
+ATF_TEST_CASE_BODY(syntax_error)
+{
+ const text::syntax_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ const text::value_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, regex_error);
+ ATF_ADD_TEST_CASE(tcs, syntax_error);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/text/operations.cpp b/utils/text/operations.cpp
new file mode 100644
index 000000000000..5a4345d979c7
--- /dev/null
+++ b/utils/text/operations.cpp
@@ -0,0 +1,261 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/operations.ipp"
+
+#include <sstream>
+
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace text = utils::text;
+
+
+/// Replaces XML special characters from an input string.
+///
+/// The list of XML special characters is specified here:
+/// http://www.w3.org/TR/xml11/#charsets
+///
+/// \param in The input to quote.
+///
+/// \return A quoted string without any XML special characters.
+std::string
+text::escape_xml(const std::string& in)
+{
+ std::ostringstream quoted;
+
+ for (std::string::const_iterator it = in.begin();
+ it != in.end(); ++it) {
+ unsigned char c = (unsigned char)*it;
+ if (c == '"') {
+ quoted << "&quot;";
+ } else if (c == '&') {
+ quoted << "&amp;";
+ } else if (c == '<') {
+ quoted << "&lt;";
+ } else if (c == '>') {
+ quoted << "&gt;";
+ } else if (c == '\'') {
+ quoted << "&apos;";
+ } else if ((c >= 0x01 && c <= 0x08) ||
+ (c >= 0x0B && c <= 0x0C) ||
+ (c >= 0x0E && c <= 0x1F) ||
+ (c >= 0x7F && c <= 0x84) ||
+ (c >= 0x86 && c <= 0x9F)) {
+ // for RestrictedChar characters, escape them
+ // as '&amp;#[decimal ASCII value];'
+ // so that in the XML file we will see the escaped
+ // character.
+ quoted << "&amp;#" << static_cast< std::string::size_type >(*it)
+ << ";";
+ } else {
+ quoted << *it;
+ }
+ }
+ return quoted.str();
+}
+
+
+/// Surrounds a string with quotes, escaping the quote itself if needed.
+///
+/// \param text The string to quote.
+/// \param quote The quote character to use.
+///
+/// \return The quoted string.
+std::string
+text::quote(const std::string& text, const char quote)
+{
+ std::ostringstream quoted;
+ quoted << quote;
+
+ std::string::size_type start_pos = 0;
+ std::string::size_type last_pos = text.find(quote);
+ while (last_pos != std::string::npos) {
+ quoted << text.substr(start_pos, last_pos - start_pos) << '\\';
+ start_pos = last_pos;
+ last_pos = text.find(quote, start_pos + 1);
+ }
+ quoted << text.substr(start_pos);
+
+ quoted << quote;
+ return quoted.str();
+}
+
+
+/// Fills a paragraph to the specified length.
+///
+/// This preserves any sequence of spaces in the input and any possible
+/// newlines. Sequences of spaces may be split in half (and thus one space is
+/// lost), but the rest of the spaces will be preserved as either trailing or
+/// leading spaces.
+///
+/// \param input The string to refill.
+/// \param target_width The width to refill the paragraph to.
+///
+/// \return The refilled paragraph as a sequence of independent lines.
+std::vector< std::string >
+text::refill(const std::string& input, const std::size_t target_width)
+{
+ std::vector< std::string > output;
+
+ std::string::size_type start = 0;
+ while (start < input.length()) {
+ std::string::size_type width;
+ if (start + target_width >= input.length())
+ width = input.length() - start;
+ else {
+ if (input[start + target_width] == ' ') {
+ width = target_width;
+ } else {
+ const std::string::size_type pos = input.find_last_of(
+ " ", start + target_width - 1);
+ if (pos == std::string::npos || pos < start + 1) {
+ width = input.find_first_of(" ", start + target_width);
+ if (width == std::string::npos)
+ width = input.length() - start;
+ else
+ width -= start;
+ } else {
+ width = pos - start;
+ }
+ }
+ }
+ INV(width != std::string::npos);
+ INV(start + width <= input.length());
+ INV(input[start + width] == ' ' || input[start + width] == '\0');
+ output.push_back(input.substr(start, width));
+
+ start += width + 1;
+ }
+
+ if (input.empty()) {
+ INV(output.empty());
+ output.push_back("");
+ }
+
+ return output;
+}
+
+
+/// Fills a paragraph to the specified length.
+///
+/// See the documentation for refill() for additional details.
+///
+/// \param input The string to refill.
+/// \param target_width The width to refill the paragraph to.
+///
+/// \return The refilled paragraph as a string with embedded newlines.
+std::string
+text::refill_as_string(const std::string& input, const std::size_t target_width)
+{
+ return join(refill(input, target_width), "\n");
+}
+
+
+/// Replaces all occurrences of a substring in a string.
+///
+/// \param input The string in which to perform the replacement.
+/// \param search The pattern to be replaced.
+/// \param replacement The substring to replace search with.
+///
+/// \return A copy of input with the replacements performed.
+std::string
+text::replace_all(const std::string& input, const std::string& search,
+ const std::string& replacement)
+{
+ std::string output;
+
+ std::string::size_type pos, lastpos = 0;
+ while ((pos = input.find(search, lastpos)) != std::string::npos) {
+ output += input.substr(lastpos, pos - lastpos);
+ output += replacement;
+ lastpos = pos + search.length();
+ }
+ output += input.substr(lastpos);
+
+ return output;
+}
+
+
+/// Splits a string into different components.
+///
+/// \param str The string to split.
+/// \param delimiter The separator to use to split the words.
+///
+/// \return The different words in the input string as split by the provided
+/// delimiter.
+std::vector< std::string >
+text::split(const std::string& str, const char delimiter)
+{
+ std::vector< std::string > words;
+ if (!str.empty()) {
+ std::string::size_type pos = str.find(delimiter);
+ words.push_back(str.substr(0, pos));
+ while (pos != std::string::npos) {
+ ++pos;
+ const std::string::size_type next = str.find(delimiter, pos);
+ words.push_back(str.substr(pos, next - pos));
+ pos = next;
+ }
+ }
+ return words;
+}
+
+
+/// Converts a string to a boolean.
+///
+/// \param str The string to convert.
+///
+/// \return The converted string, if the input string was valid.
+///
+/// \throw std::value_error If the input string does not represent a valid
+/// boolean value.
+template<>
+bool
+text::to_type(const std::string& str)
+{
+ if (str == "true")
+ return true;
+ else if (str == "false")
+ return false;
+ else
+ throw value_error(F("Invalid boolean value '%s'") % str);
+}
+
+
+/// Identity function for to_type, for genericity purposes.
+///
+/// \param str The string to convert.
+///
+/// \return The input string.
+template<>
+std::string
+text::to_type(const std::string& str)
+{
+ return str;
+}
diff --git a/utils/text/operations.hpp b/utils/text/operations.hpp
new file mode 100644
index 000000000000..6d15be553b06
--- /dev/null
+++ b/utils/text/operations.hpp
@@ -0,0 +1,68 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/operations.hpp
+/// Utilities to manipulate strings.
+
+#if !defined(UTILS_TEXT_OPERATIONS_HPP)
+#define UTILS_TEXT_OPERATIONS_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+std::string escape_xml(const std::string&);
+std::string quote(const std::string&, const char);
+
+
+std::vector< std::string > refill(const std::string&, const std::size_t);
+std::string refill_as_string(const std::string&, const std::size_t);
+
+std::string replace_all(const std::string&, const std::string&,
+ const std::string&);
+
+template< typename Collection >
+std::string join(const Collection&, const std::string&);
+std::vector< std::string > split(const std::string&, const char);
+
+template< typename Type >
+Type to_type(const std::string&);
+template<>
+bool to_type(const std::string&);
+template<>
+std::string to_type(const std::string&);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_OPERATIONS_HPP)
diff --git a/utils/text/operations.ipp b/utils/text/operations.ipp
new file mode 100644
index 000000000000..511cd6840a08
--- /dev/null
+++ b/utils/text/operations.ipp
@@ -0,0 +1,91 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#if !defined(UTILS_TEXT_OPERATIONS_IPP)
+#define UTILS_TEXT_OPERATIONS_IPP
+
+#include "utils/text/operations.hpp"
+
+#include <sstream>
+
+#include "utils/text/exceptions.hpp"
+
+
+/// Concatenates a collection of strings into a single string.
+///
+/// \param strings The collection of strings to concatenate. If the collection
+/// is unordered, the ordering in the output is undefined.
+/// \param delimiter The delimiter to use to separate the strings.
+///
+/// \return The concatenated strings.
+template< typename Collection >
+std::string
+utils::text::join(const Collection& strings, const std::string& delimiter)
+{
+ std::ostringstream output;
+ if (strings.size() > 1) {
+ for (typename Collection::const_iterator iter = strings.begin();
+ iter != --strings.end(); ++iter)
+ output << (*iter) << delimiter;
+ }
+ if (strings.size() > 0)
+ output << *(--strings.end());
+ return output.str();
+}
+
+
+/// Converts a string to a native type.
+///
+/// \tparam Type The type to convert the string to. An input stream operator
+/// must exist to extract such a type from an std::istream.
+/// \param str The string to convert.
+///
+/// \return The converted string, if the input string was valid.
+///
+/// \throw std::value_error If the input string does not represent a valid
+/// target type. This exception does not include any details, so the caller
+/// must take care to re-raise it with appropriate details.
+template< typename Type >
+Type
+utils::text::to_type(const std::string& str)
+{
+ if (str.empty())
+ throw text::value_error("Empty string");
+ if (str[0] == ' ')
+ throw text::value_error("Invalid value");
+
+ std::istringstream input(str);
+ Type value;
+ input >> value;
+ if (!input.eof() || input.bad() || input.fail())
+ throw text::value_error("Invalid value");
+ return value;
+}
+
+
+#endif // !defined(UTILS_TEXT_OPERATIONS_IPP)
diff --git a/utils/text/operations_test.cpp b/utils/text/operations_test.cpp
new file mode 100644
index 000000000000..2d5ab36c9090
--- /dev/null
+++ b/utils/text/operations_test.cpp
@@ -0,0 +1,435 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/operations.ipp"
+
+#include <iostream>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Tests text::refill() on an input string with a range of widths.
+///
+/// \param expected The expected refilled paragraph.
+/// \param input The input paragraph to be refilled.
+/// \param first_width The first width to validate.
+/// \param last_width The last width to validate (inclusive).
+static void
+refill_test(const char* expected, const char* input,
+ const std::size_t first_width, const std::size_t last_width)
+{
+ for (std::size_t width = first_width; width <= last_width; ++width) {
+ const std::vector< std::string > lines = text::split(expected, '\n');
+ std::cout << "Breaking at width " << width << '\n';
+ ATF_REQUIRE_EQ(expected, text::refill_as_string(input, width));
+ ATF_REQUIRE(lines == text::refill(input, width));
+ }
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__empty);
+ATF_TEST_CASE_BODY(escape_xml__empty)
+{
+ ATF_REQUIRE_EQ("", text::escape_xml(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__no_escaping);
+ATF_TEST_CASE_BODY(escape_xml__no_escaping)
+{
+ ATF_REQUIRE_EQ("a", text::escape_xml("a"));
+ ATF_REQUIRE_EQ("Some text!", text::escape_xml("Some text!"));
+ ATF_REQUIRE_EQ("\n\t\r", text::escape_xml("\n\t\r"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(escape_xml__some_escaping);
+ATF_TEST_CASE_BODY(escape_xml__some_escaping)
+{
+ ATF_REQUIRE_EQ("&apos;", text::escape_xml("'"));
+
+ ATF_REQUIRE_EQ("foo &quot;bar&amp; &lt;tag&gt; yay&apos; baz",
+ text::escape_xml("foo \"bar& <tag> yay' baz"));
+
+ ATF_REQUIRE_EQ("&quot;&amp;&lt;&gt;&apos;", text::escape_xml("\"&<>'"));
+ ATF_REQUIRE_EQ("&amp;&amp;&amp;", text::escape_xml("&&&"));
+ ATF_REQUIRE_EQ("&amp;#8;&amp;#11;", text::escape_xml("\b\v"));
+ ATF_REQUIRE_EQ("\t&amp;#127;BAR&amp;", text::escape_xml("\t\x7f""BAR&"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__empty);
+ATF_TEST_CASE_BODY(quote__empty)
+{
+ ATF_REQUIRE_EQ("''", text::quote("", '\''));
+ ATF_REQUIRE_EQ("##", text::quote("", '#'));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__no_escaping);
+ATF_TEST_CASE_BODY(quote__no_escaping)
+{
+ ATF_REQUIRE_EQ("'Some text\"'", text::quote("Some text\"", '\''));
+ ATF_REQUIRE_EQ("#Another'string#", text::quote("Another'string", '#'));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(quote__some_escaping);
+ATF_TEST_CASE_BODY(quote__some_escaping)
+{
+ ATF_REQUIRE_EQ("'Some\\'text'", text::quote("Some'text", '\''));
+ ATF_REQUIRE_EQ("#Some\\#text#", text::quote("Some#text", '#'));
+
+ ATF_REQUIRE_EQ("'More than one\\' quote\\''",
+ text::quote("More than one' quote'", '\''));
+ ATF_REQUIRE_EQ("'Multiple quotes \\'\\'\\' together'",
+ text::quote("Multiple quotes ''' together", '\''));
+
+ ATF_REQUIRE_EQ("'\\'escape at the beginning'",
+ text::quote("'escape at the beginning", '\''));
+ ATF_REQUIRE_EQ("'escape at the end\\''",
+ text::quote("escape at the end'", '\''));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__empty);
+ATF_TEST_CASE_BODY(refill__empty)
+{
+ ATF_REQUIRE_EQ(1, text::refill("", 0).size());
+ ATF_REQUIRE(text::refill("", 0)[0].empty());
+ ATF_REQUIRE_EQ("", text::refill_as_string("", 0));
+
+ ATF_REQUIRE_EQ(1, text::refill("", 10).size());
+ ATF_REQUIRE(text::refill("", 10)[0].empty());
+ ATF_REQUIRE_EQ("", text::refill_as_string("", 10));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__no_changes);
+ATF_TEST_CASE_BODY(refill__no_changes)
+{
+ std::vector< std::string > exp_lines;
+ exp_lines.push_back("foo bar\nbaz");
+
+ ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 12));
+ ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 12));
+
+ ATF_REQUIRE(exp_lines == text::refill("foo bar\nbaz", 18));
+ ATF_REQUIRE_EQ("foo bar\nbaz", text::refill_as_string("foo bar\nbaz", 80));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one);
+ATF_TEST_CASE_BODY(refill__break_one)
+{
+ refill_test("only break the\nfirst line", "only break the first line",
+ 14, 19);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_one__not_first_word);
+ATF_TEST_CASE_BODY(refill__break_one__not_first_word)
+{
+ refill_test("first-long-word\nother\nwords", "first-long-word other words",
+ 6, 10);
+ refill_test("first-long-word\nother words", "first-long-word other words",
+ 11, 20);
+ refill_test("first-long-word other\nwords", "first-long-word other words",
+ 21, 26);
+ refill_test("first-long-word other words", "first-long-word other words",
+ 27, 28);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__break_many);
+ATF_TEST_CASE_BODY(refill__break_many)
+{
+ refill_test("this is a long\nparagraph to be\nsplit into\npieces",
+ "this is a long paragraph to be split into pieces",
+ 15, 15);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__cannot_break);
+ATF_TEST_CASE_BODY(refill__cannot_break)
+{
+ refill_test("this-is-a-long-string", "this-is-a-long-string", 5, 5);
+
+ refill_test("this is\na-string-with-long-words",
+ "this is a-string-with-long-words", 10, 10);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(refill__preserve_whitespace);
+ATF_TEST_CASE_BODY(refill__preserve_whitespace)
+{
+ refill_test("foo bar baz ", "foo bar baz ", 80, 80);
+ refill_test("foo \n bar", "foo bar", 5, 5);
+
+ std::vector< std::string > exp_lines;
+ exp_lines.push_back("foo \n");
+ exp_lines.push_back(" bar");
+ ATF_REQUIRE(exp_lines == text::refill("foo \n bar", 5));
+ ATF_REQUIRE_EQ("foo \n\n bar", text::refill_as_string("foo \n bar", 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__empty);
+ATF_TEST_CASE_BODY(join__empty)
+{
+ std::vector< std::string > lines;
+ ATF_REQUIRE_EQ("", text::join(lines, " "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__one);
+ATF_TEST_CASE_BODY(join__one)
+{
+ std::vector< std::string > lines;
+ lines.push_back("first line");
+ ATF_REQUIRE_EQ("first line", text::join(lines, "*"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__several);
+ATF_TEST_CASE_BODY(join__several)
+{
+ std::vector< std::string > lines;
+ lines.push_back("first abc");
+ lines.push_back("second");
+ lines.push_back("and last line");
+ ATF_REQUIRE_EQ("first abc second and last line", text::join(lines, " "));
+ ATF_REQUIRE_EQ("first abc***second***and last line",
+ text::join(lines, "***"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(join__unordered);
+ATF_TEST_CASE_BODY(join__unordered)
+{
+ std::set< std::string > lines;
+ lines.insert("first");
+ lines.insert("second");
+ const std::string joined = text::join(lines, " ");
+ ATF_REQUIRE(joined == "first second" || joined == "second first");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__empty);
+ATF_TEST_CASE_BODY(split__empty)
+{
+ std::vector< std::string > words = text::split("", ' ');
+ std::vector< std::string > exp_words;
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__one);
+ATF_TEST_CASE_BODY(split__one)
+{
+ std::vector< std::string > words = text::split("foo", ' ');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("foo");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__several__simple);
+ATF_TEST_CASE_BODY(split__several__simple)
+{
+ std::vector< std::string > words = text::split("foo bar baz", ' ');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("foo");
+ exp_words.push_back("bar");
+ exp_words.push_back("baz");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(split__several__delimiters);
+ATF_TEST_CASE_BODY(split__several__delimiters)
+{
+ std::vector< std::string > words = text::split("XfooXXbarXXXbazXX", 'X');
+ std::vector< std::string > exp_words;
+ exp_words.push_back("");
+ exp_words.push_back("foo");
+ exp_words.push_back("");
+ exp_words.push_back("bar");
+ exp_words.push_back("");
+ exp_words.push_back("");
+ exp_words.push_back("baz");
+ exp_words.push_back("");
+ exp_words.push_back("");
+ ATF_REQUIRE(exp_words == words);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__empty);
+ATF_TEST_CASE_BODY(replace_all__empty)
+{
+ ATF_REQUIRE_EQ("", text::replace_all("", "search", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__none);
+ATF_TEST_CASE_BODY(replace_all__none)
+{
+ ATF_REQUIRE_EQ("string without matches",
+ text::replace_all("string without matches",
+ "WITHOUT", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__one);
+ATF_TEST_CASE_BODY(replace_all__one)
+{
+ ATF_REQUIRE_EQ("string replacement matches",
+ text::replace_all("string without matches",
+ "without", "replacement"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(replace_all__several);
+ATF_TEST_CASE_BODY(replace_all__several)
+{
+ ATF_REQUIRE_EQ("OO fOO bar OOf baz OO",
+ text::replace_all("oo foo bar oof baz oo",
+ "oo", "OO"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__bool);
+ATF_TEST_CASE_BODY(to_type__ok__bool)
+{
+ ATF_REQUIRE( text::to_type< bool >("true"));
+ ATF_REQUIRE(!text::to_type< bool >("false"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__numerical);
+ATF_TEST_CASE_BODY(to_type__ok__numerical)
+{
+ ATF_REQUIRE_EQ(12, text::to_type< int >("12"));
+ ATF_REQUIRE_EQ(18745, text::to_type< int >("18745"));
+ ATF_REQUIRE_EQ(-12345, text::to_type< int >("-12345"));
+
+ ATF_REQUIRE_EQ(12.0, text::to_type< double >("12"));
+ ATF_REQUIRE_EQ(12.5, text::to_type< double >("12.5"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__ok__string);
+ATF_TEST_CASE_BODY(to_type__ok__string)
+{
+ // While this seems redundant, having this particular specialization that
+ // does nothing allows callers to delegate work to to_type without worrying
+ // about the particular type being converted.
+ ATF_REQUIRE_EQ("", text::to_type< std::string >(""));
+ ATF_REQUIRE_EQ(" abcd ", text::to_type< std::string >(" abcd "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__empty);
+ATF_TEST_CASE_BODY(to_type__empty)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__bool);
+ATF_TEST_CASE_BODY(to_type__invalid__bool)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >(""));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("true "));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< bool >("foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(to_type__invalid__numerical);
+ATF_TEST_CASE_BODY(to_type__invalid__numerical)
+{
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >(" 3"));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3 "));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("3a"));
+ ATF_REQUIRE_THROW(text::value_error, text::to_type< int >("a3"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, escape_xml__empty);
+ ATF_ADD_TEST_CASE(tcs, escape_xml__no_escaping);
+ ATF_ADD_TEST_CASE(tcs, escape_xml__some_escaping);
+
+ ATF_ADD_TEST_CASE(tcs, quote__empty);
+ ATF_ADD_TEST_CASE(tcs, quote__no_escaping);
+ ATF_ADD_TEST_CASE(tcs, quote__some_escaping);
+
+ ATF_ADD_TEST_CASE(tcs, refill__empty);
+ ATF_ADD_TEST_CASE(tcs, refill__no_changes);
+ ATF_ADD_TEST_CASE(tcs, refill__break_one);
+ ATF_ADD_TEST_CASE(tcs, refill__break_one__not_first_word);
+ ATF_ADD_TEST_CASE(tcs, refill__break_many);
+ ATF_ADD_TEST_CASE(tcs, refill__cannot_break);
+ ATF_ADD_TEST_CASE(tcs, refill__preserve_whitespace);
+
+ ATF_ADD_TEST_CASE(tcs, join__empty);
+ ATF_ADD_TEST_CASE(tcs, join__one);
+ ATF_ADD_TEST_CASE(tcs, join__several);
+ ATF_ADD_TEST_CASE(tcs, join__unordered);
+
+ ATF_ADD_TEST_CASE(tcs, split__empty);
+ ATF_ADD_TEST_CASE(tcs, split__one);
+ ATF_ADD_TEST_CASE(tcs, split__several__simple);
+ ATF_ADD_TEST_CASE(tcs, split__several__delimiters);
+
+ ATF_ADD_TEST_CASE(tcs, replace_all__empty);
+ ATF_ADD_TEST_CASE(tcs, replace_all__none);
+ ATF_ADD_TEST_CASE(tcs, replace_all__one);
+ ATF_ADD_TEST_CASE(tcs, replace_all__several);
+
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__bool);
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__numerical);
+ ATF_ADD_TEST_CASE(tcs, to_type__ok__string);
+ ATF_ADD_TEST_CASE(tcs, to_type__empty);
+ ATF_ADD_TEST_CASE(tcs, to_type__invalid__bool);
+ ATF_ADD_TEST_CASE(tcs, to_type__invalid__numerical);
+}
diff --git a/utils/text/regex.cpp b/utils/text/regex.cpp
new file mode 100644
index 000000000000..b078ba88f6b4
--- /dev/null
+++ b/utils/text/regex.cpp
@@ -0,0 +1,302 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/regex.hpp"
+
+extern "C" {
+#include <sys/types.h>
+
+#include <regex.h>
+}
+
+#include "utils/auto_array.ipp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+static void throw_regex_error(const int, const ::regex_t*, const std::string&)
+ UTILS_NORETURN;
+
+
+/// Constructs and raises a regex_error.
+///
+/// \param error The error code returned by regcomp(3) or regexec(3).
+/// \param preg The native regex object that caused this error.
+/// \param prefix Error message prefix string.
+///
+/// \throw regex_error The constructed exception.
+static void
+throw_regex_error(const int error, const ::regex_t* preg,
+ const std::string& prefix)
+{
+ char buffer[1024];
+
+ // TODO(jmmv): Would be nice to handle the case where the message does
+ // not fit in the temporary buffer.
+ (void)::regerror(error, preg, buffer, sizeof(buffer));
+
+ throw text::regex_error(F("%s: %s") % prefix % buffer);
+}
+
+
+} // anonymous namespace
+
+
+/// Internal implementation for regex_matches.
+struct utils::text::regex_matches::impl : utils::noncopyable {
+ /// String on which we are matching.
+ ///
+ /// In theory, we could take a reference here instead of a copy, and make
+ /// it a requirement for the caller to ensure that the lifecycle of the
+ /// input string outlasts the lifecycle of the regex_matches. However, that
+ /// contract is very easy to break with hardcoded strings (as we do in
+ /// tests). Just go for the safer case here.
+ const std::string _string;
+
+ /// Maximum number of matching groups we expect, including the full match.
+ ///
+ /// In other words, this is the size of the _matches array.
+ const std::size_t _nmatches;
+
+ /// Native regular expression match representation.
+ utils::auto_array< ::regmatch_t > _matches;
+
+ /// Constructor.
+ ///
+ /// This executes the regex on the given string and sets up the internal
+ /// class state based on the results.
+ ///
+ /// \param preg The native regex object.
+ /// \param str The string on which to execute the regex.
+ /// \param ngroups Number of capture groups in the regex. This is an upper
+ /// bound and may be greater than the actual matches.
+ ///
+ /// \throw regex_error If the call to regexec(3) fails.
+ impl(const ::regex_t* preg, const std::string& str,
+ const std::size_t ngroups) :
+ _string(str),
+ _nmatches(ngroups + 1),
+ _matches(new ::regmatch_t[_nmatches])
+ {
+ const int error = ::regexec(preg, _string.c_str(), _nmatches,
+ _matches.get(), 0);
+ if (error == REG_NOMATCH) {
+ _matches.reset(NULL);
+ } else if (error != 0) {
+ throw_regex_error(error, preg,
+ F("regexec on '%s' failed") % _string);
+ }
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed implementation of the object.
+text::regex_matches::regex_matches(std::shared_ptr< impl > pimpl) :
+ _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+text::regex_matches::~regex_matches(void)
+{
+}
+
+
+/// Returns the number of matches in this object.
+///
+/// Note that this does not correspond to the number of groups provided at
+/// construction time. The returned value here accounts for only the returned
+/// valid matches.
+///
+/// \return Number of matches, including the full match.
+std::size_t
+text::regex_matches::count(void) const
+{
+ std::size_t total = 0;
+ if (_pimpl->_matches.get() != NULL) {
+ for (std::size_t i = 0; i < _pimpl->_nmatches; ++i) {
+ if (_pimpl->_matches[i].rm_so != -1)
+ ++total;
+ }
+ INV(total <= _pimpl->_nmatches);
+ }
+ return total;
+}
+
+
+/// Gets a match.
+///
+/// \param index Number of the match to get. Index 0 always contains the match
+/// of the whole regex.
+///
+/// \pre There regex must have matched the input string.
+/// \pre index must be lower than count().
+///
+/// \return The textual match.
+std::string
+text::regex_matches::get(const std::size_t index) const
+{
+ PRE(*this);
+ PRE(index < count());
+
+ const ::regmatch_t* match = &_pimpl->_matches[index];
+
+ return std::string(_pimpl->_string.c_str() + match->rm_so,
+ match->rm_eo - match->rm_so);
+}
+
+
+/// Checks if there are any matches.
+///
+/// \return True if the object contains one or more matches; false otherwise.
+text::regex_matches::operator bool(void) const
+{
+ return _pimpl->_matches.get() != NULL;
+}
+
+
+/// Internal implementation for regex.
+struct utils::text::regex::impl : utils::noncopyable {
+ /// Native regular expression representation.
+ ::regex_t _preg;
+
+ /// Number of capture groups in the regular expression. This is an upper
+ /// bound and does NOT include the default full string match.
+ std::size_t _ngroups;
+
+ /// Constructor.
+ ///
+ /// This compiles the given regular expression.
+ ///
+ /// \param regex_ The regular expression to compile.
+ /// \param ngroups Number of capture groups in the regular expression. This
+ /// is an upper bound and does NOT include the default full string
+ /// match.
+ /// \param ignore_case Whether to ignore case during matching.
+ ///
+ /// \throw regex_error If the call to regcomp(3) fails.
+ impl(const std::string& regex_, const std::size_t ngroups,
+ const bool ignore_case) :
+ _ngroups(ngroups)
+ {
+ const int flags = REG_EXTENDED | (ignore_case ? REG_ICASE : 0);
+ const int error = ::regcomp(&_preg, regex_.c_str(), flags);
+ if (error != 0)
+ throw_regex_error(error, &_preg, F("regcomp on '%s' failed")
+ % regex_);
+ }
+
+ /// Destructor.
+ ~impl(void)
+ {
+ ::regfree(&_preg);
+ }
+};
+
+
+/// Constructor.
+///
+/// \param pimpl Constructed implementation of the object.
+text::regex::regex(std::shared_ptr< impl > pimpl) : _pimpl(pimpl)
+{
+}
+
+
+/// Destructor.
+text::regex::~regex(void)
+{
+}
+
+
+/// Compiles a new regular expression.
+///
+/// \param regex_ The regular expression to compile.
+/// \param ngroups Number of capture groups in the regular expression. This is
+/// an upper bound and does NOT include the default full string match.
+/// \param ignore_case Whether to ignore case during matching.
+///
+/// \return A new regular expression, ready to match strings.
+///
+/// \throw regex_error If the regular expression is invalid and cannot be
+/// compiled.
+text::regex
+text::regex::compile(const std::string& regex_, const std::size_t ngroups,
+ const bool ignore_case)
+{
+ return regex(std::shared_ptr< impl >(new impl(regex_, ngroups,
+ ignore_case)));
+}
+
+
+/// Matches the regular expression against a string.
+///
+/// \param str String to match the regular expression against.
+///
+/// \return A new regex_matches object with the results of the match.
+text::regex_matches
+text::regex::match(const std::string& str) const
+{
+ std::shared_ptr< regex_matches::impl > pimpl(new regex_matches::impl(
+ &_pimpl->_preg, str, _pimpl->_ngroups));
+ return regex_matches(pimpl);
+}
+
+
+/// Compiles and matches a regular expression once.
+///
+/// This is syntactic sugar to simplify the instantiation of a new regex object
+/// and its subsequent match on a string.
+///
+/// \param regex_ The regular expression to compile and match.
+/// \param str String to match the regular expression against.
+/// \param ngroups Number of capture groups in the regular expression.
+/// \param ignore_case Whether to ignore case during matching.
+///
+/// \return A new regex_matches object with the results of the match.
+text::regex_matches
+text::match_regex(const std::string& regex_, const std::string& str,
+ const std::size_t ngroups, const bool ignore_case)
+{
+ return regex::compile(regex_, ngroups, ignore_case).match(str);
+}
diff --git a/utils/text/regex.hpp b/utils/text/regex.hpp
new file mode 100644
index 000000000000..b3d20c246735
--- /dev/null
+++ b/utils/text/regex.hpp
@@ -0,0 +1,92 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/regex.hpp
+/// Utilities to build and match regular expressions.
+
+#if !defined(UTILS_TEXT_REGEX_HPP)
+#define UTILS_TEXT_REGEX_HPP
+
+#include "utils/text/regex_fwd.hpp"
+
+#include <cstddef>
+#include <memory>
+
+
+namespace utils {
+namespace text {
+
+
+/// Container for regex match results.
+class regex_matches {
+ struct impl;
+
+ /// Pointer to shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ friend class regex;
+ regex_matches(std::shared_ptr< impl >);
+
+public:
+ ~regex_matches(void);
+
+ std::size_t count(void) const;
+ std::string get(const std::size_t) const;
+
+ operator bool(void) const;
+};
+
+
+/// Regular expression compiler and executor.
+///
+/// All regular expressions handled by this class are "extended".
+class regex {
+ struct impl;
+
+ /// Pointer to shared implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ regex(std::shared_ptr< impl >);
+
+public:
+ ~regex(void);
+
+ static regex compile(const std::string&, const std::size_t,
+ const bool = false);
+ regex_matches match(const std::string&) const;
+};
+
+
+regex_matches match_regex(const std::string&, const std::string&,
+ const std::size_t, const bool = false);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_REGEX_HPP)
diff --git a/utils/text/regex_fwd.hpp b/utils/text/regex_fwd.hpp
new file mode 100644
index 000000000000..e9010324c10d
--- /dev/null
+++ b/utils/text/regex_fwd.hpp
@@ -0,0 +1,46 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/regex_fwd.hpp
+/// Forward declarations for utils/text/regex.hpp
+
+#if !defined(UTILS_TEXT_REGEX_FWD_HPP)
+#define UTILS_TEXT_REGEX_FWD_HPP
+
+namespace utils {
+namespace text {
+
+
+class regex_matches;
+class regex;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_REGEX_FWD_HPP)
diff --git a/utils/text/regex_test.cpp b/utils/text/regex_test.cpp
new file mode 100644
index 000000000000..7ea5ee485aad
--- /dev/null
+++ b/utils/text/regex_test.cpp
@@ -0,0 +1,177 @@
+// Copyright 2014 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/regex.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/text/exceptions.hpp"
+
+namespace text = utils::text;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__no_matches);
+ATF_TEST_CASE_BODY(integration__no_matches)
+{
+ const text::regex_matches matches = text::match_regex(
+ "foo.*bar", "this is a string without the searched text", 0);
+ ATF_REQUIRE(!matches);
+ ATF_REQUIRE_EQ(0, matches.count());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__no_capture_groups);
+ATF_TEST_CASE_BODY(integration__no_capture_groups)
+{
+ const text::regex_matches matches = text::match_regex(
+ "foo.*bar", "this is a string with foo and bar embedded in it", 0);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(1, matches.count());
+ ATF_REQUIRE_EQ("foo and bar", matches.get(0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__one_capture_group);
+ATF_TEST_CASE_BODY(integration__one_capture_group)
+{
+ const text::regex_matches matches = text::match_regex(
+ "^([^ ]*) ", "the string", 1);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("the ", matches.get(0));
+ ATF_REQUIRE_EQ("the", matches.get(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__many_capture_groups);
+ATF_TEST_CASE_BODY(integration__many_capture_groups)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 2);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(3, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+ ATF_REQUIRE_EQ("string", matches.get(2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__capture_groups_underspecified);
+ATF_TEST_CASE_BODY(integration__capture_groups_underspecified)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 1);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__capture_groups_overspecified);
+ATF_TEST_CASE_BODY(integration__capture_groups_overspecified)
+{
+ const text::regex_matches matches = text::match_regex(
+ "is ([^ ]*) ([a-z]*) to", "this is another string to parse", 10);
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(3, matches.count());
+ ATF_REQUIRE_EQ("is another string to", matches.get(0));
+ ATF_REQUIRE_EQ("another", matches.get(1));
+ ATF_REQUIRE_EQ("string", matches.get(2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__reuse_regex_in_multiple_matches);
+ATF_TEST_CASE_BODY(integration__reuse_regex_in_multiple_matches)
+{
+ const text::regex regex = text::regex::compile("number is ([0-9]+)", 1);
+
+ {
+ const text::regex_matches matches = regex.match("my number is 581.");
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("number is 581", matches.get(0));
+ ATF_REQUIRE_EQ("581", matches.get(1));
+ }
+
+ {
+ const text::regex_matches matches = regex.match("your number is 6");
+ ATF_REQUIRE(matches);
+ ATF_REQUIRE_EQ(2, matches.count());
+ ATF_REQUIRE_EQ("number is 6", matches.get(0));
+ ATF_REQUIRE_EQ("6", matches.get(1));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__ignore_case);
+ATF_TEST_CASE_BODY(integration__ignore_case)
+{
+ const text::regex regex1 = text::regex::compile("foo", 0, false);
+ ATF_REQUIRE(!regex1.match("bar Foo bar"));
+ ATF_REQUIRE(!regex1.match("bar foO bar"));
+ ATF_REQUIRE(!regex1.match("bar FOO bar"));
+
+ ATF_REQUIRE(!text::match_regex("foo", "bar Foo bar", 0, false));
+ ATF_REQUIRE(!text::match_regex("foo", "bar foO bar", 0, false));
+ ATF_REQUIRE(!text::match_regex("foo", "bar FOO bar", 0, false));
+
+ const text::regex regex2 = text::regex::compile("foo", 0, true);
+ ATF_REQUIRE( regex2.match("bar foo bar"));
+ ATF_REQUIRE( regex2.match("bar Foo bar"));
+ ATF_REQUIRE( regex2.match("bar foO bar"));
+ ATF_REQUIRE( regex2.match("bar FOO bar"));
+
+ ATF_REQUIRE( text::match_regex("foo", "bar foo bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar Foo bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar foO bar", 0, true));
+ ATF_REQUIRE( text::match_regex("foo", "bar FOO bar", 0, true));
+}
+
+ATF_TEST_CASE_WITHOUT_HEAD(integration__invalid_regex);
+ATF_TEST_CASE_BODY(integration__invalid_regex)
+{
+ ATF_REQUIRE_THROW(text::regex_error,
+ text::regex::compile("this is (unbalanced", 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ // regex and regex_matches are so coupled that it makes no sense to test
+ // them independently. Just validate their integration.
+ ATF_ADD_TEST_CASE(tcs, integration__no_matches);
+ ATF_ADD_TEST_CASE(tcs, integration__no_capture_groups);
+ ATF_ADD_TEST_CASE(tcs, integration__one_capture_group);
+ ATF_ADD_TEST_CASE(tcs, integration__many_capture_groups);
+ ATF_ADD_TEST_CASE(tcs, integration__capture_groups_underspecified);
+ ATF_ADD_TEST_CASE(tcs, integration__capture_groups_overspecified);
+ ATF_ADD_TEST_CASE(tcs, integration__reuse_regex_in_multiple_matches);
+ ATF_ADD_TEST_CASE(tcs, integration__ignore_case);
+ ATF_ADD_TEST_CASE(tcs, integration__invalid_regex);
+}
diff --git a/utils/text/table.cpp b/utils/text/table.cpp
new file mode 100644
index 000000000000..4a2c72f8053f
--- /dev/null
+++ b/utils/text/table.cpp
@@ -0,0 +1,428 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/table.hpp"
+
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <sstream>
+
+#include "utils/sanity.hpp"
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Applies user overrides to the column widths of a table.
+///
+/// \param table The table from which to calculate the column widths.
+/// \param user_widths The column widths provided by the user. This vector must
+/// have less or the same number of elements as the columns of the table.
+/// Values of width_auto are ignored; any other explicit values are copied
+/// to the output widths vector, including width_refill.
+///
+/// \return A vector with the widths of the columns of the input table with any
+/// user overrides applied.
+static text::widths_vector
+override_column_widths(const text::table& table,
+ const text::widths_vector& user_widths)
+{
+ PRE(user_widths.size() <= table.ncolumns());
+ text::widths_vector widths = table.column_widths();
+
+ // Override the actual width of the columns based on user-specified widths.
+ for (text::widths_vector::size_type i = 0; i < user_widths.size(); ++i) {
+ const text::widths_vector::value_type& user_width = user_widths[i];
+ if (user_width != text::table_formatter::width_auto) {
+ PRE_MSG(user_width == text::table_formatter::width_refill ||
+ user_width >= widths[i],
+ "User-provided column widths must be larger than the "
+ "column contents (except for the width_refill column)");
+ widths[i] = user_width;
+ }
+ }
+
+ return widths;
+}
+
+
+/// Locates the refill column, if any.
+///
+/// \param widths The widths of the columns as returned by
+/// override_column_widths(). Note that one of the columns may or may not
+/// be width_refill, which is the column we are looking for.
+///
+/// \return The index of the refill column with a width_refill width if any, or
+/// otherwise the index of the last column (which is the default refill column).
+static text::widths_vector::size_type
+find_refill_column(const text::widths_vector& widths)
+{
+ text::widths_vector::size_type i = 0;
+ for (; i < widths.size(); ++i) {
+ if (widths[i] == text::table_formatter::width_refill)
+ return i;
+ }
+ return i - 1;
+}
+
+
+/// Pads the widths of the table to fit within a maximum width.
+///
+/// On output, a column of the widths vector is truncated to a shorter length
+/// than its current value, if the total width of the table would exceed the
+/// maximum table width.
+///
+/// \param [in,out] widths The widths of the columns as returned by
+/// override_column_widths(). One of these columns should have a value of
+/// width_refill; if not, a default column is refilled.
+/// \param user_max_width The target width of the table; must not be zero.
+/// \param column_padding The padding between the cells, if any. The target
+/// width should be larger than the padding times the number of columns; if
+/// that is not the case, we attempt a readjustment here.
+static void
+refill_widths(text::widths_vector& widths,
+ const text::widths_vector::value_type user_max_width,
+ const std::size_t column_padding)
+{
+ PRE(user_max_width != 0);
+
+ // widths.size() is a proxy for the number of columns of the table.
+ const std::size_t total_padding = column_padding * (widths.size() - 1);
+ const text::widths_vector::value_type max_width = std::max(
+ user_max_width, total_padding) - total_padding;
+
+ const text::widths_vector::size_type refill_column =
+ find_refill_column(widths);
+ INV(refill_column < widths.size());
+
+ text::widths_vector::value_type width = 0;
+ for (text::widths_vector::size_type i = 0; i < widths.size(); ++i) {
+ if (i != refill_column)
+ width += widths[i];
+ }
+ widths[refill_column] = max_width - width;
+}
+
+
+/// Pads an input text to a specified width with spaces.
+///
+/// \param input The text to add padding to (may be empty).
+/// \param length The desired length of the output.
+/// \param is_last Whether the text being processed belongs to the last column
+/// of a row or not. Values in the last column should not be padded to
+/// prevent trailing whitespace on the screen (which affects copy/pasting
+/// for example).
+///
+/// \return The padded cell. If the input string is longer than the desired
+/// length, the input string is returned verbatim. The padded table won't be
+/// correct, but we don't expect this to be a common case to worry about.
+static std::string
+pad_cell(const std::string& input, const std::size_t length, const bool is_last)
+{
+ if (is_last)
+ return input;
+ else {
+ if (input.length() < length)
+ return input + std::string(length - input.length(), ' ');
+ else
+ return input;
+ }
+}
+
+
+/// Refills a cell and adds it to the output lines.
+///
+/// \param row The row containing the cell to be refilled.
+/// \param widths The widths of the row.
+/// \param column The column being refilled.
+/// \param [in,out] textual_rows The output lines as processed so far. This is
+/// updated to accomodate for the contents of the refilled cell, extending
+/// the rows as necessary.
+static void
+refill_cell(const text::table_row& row, const text::widths_vector& widths,
+ const text::table_row::size_type column,
+ std::vector< text::table_row >& textual_rows)
+{
+ const std::vector< std::string > rows = text::refill(row[column],
+ widths[column]);
+
+ if (textual_rows.size() < rows.size())
+ textual_rows.resize(rows.size(), text::table_row(row.size()));
+
+ for (std::vector< std::string >::size_type i = 0; i < rows.size(); ++i) {
+ for (text::table_row::size_type j = 0; j < row.size(); ++j) {
+ const bool is_last = j == row.size() - 1;
+ if (j == column)
+ textual_rows[i][j] = pad_cell(rows[i], widths[j], is_last);
+ else {
+ if (textual_rows[i][j].empty())
+ textual_rows[i][j] = pad_cell("", widths[j], is_last);
+ }
+ }
+ }
+}
+
+
+/// Formats a single table row.
+///
+/// \param row The row to format.
+/// \param widths The widths of the columns to apply during formatting. Cells
+/// wider than the specified width are refilled to attempt to fit in the
+/// cell. Cells narrower than the width are right-padded with spaces.
+/// \param separator The column separator to use.
+///
+/// \return The textual lines that contain the formatted row.
+static std::vector< std::string >
+format_row(const text::table_row& row, const text::widths_vector& widths,
+ const std::string& separator)
+{
+ PRE(row.size() == widths.size());
+
+ std::vector< text::table_row > textual_rows(1, text::table_row(row.size()));
+
+ for (text::table_row::size_type column = 0; column < row.size(); ++column) {
+ if (widths[column] > row[column].length())
+ textual_rows[0][column] = pad_cell(row[column], widths[column],
+ column == row.size() - 1);
+ else
+ refill_cell(row, widths, column, textual_rows);
+ }
+
+ std::vector< std::string > lines;
+ for (std::vector< text::table_row >::const_iterator
+ iter = textual_rows.begin(); iter != textual_rows.end(); ++iter) {
+ lines.push_back(text::join(*iter, separator));
+ }
+ return lines;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new table.
+///
+/// \param ncolumns_ The number of columns that the table will have.
+text::table::table(const table_row::size_type ncolumns_)
+{
+ _column_widths.resize(ncolumns_, 0);
+}
+
+
+/// Gets the number of columns in the table.
+///
+/// \return The number of columns in the table. This value remains constant
+/// during the existence of the table.
+text::widths_vector::size_type
+text::table::ncolumns(void) const
+{
+ return _column_widths.size();
+}
+
+
+/// Gets the width of a column.
+///
+/// The returned value is not valid if add_row() is called again, as the column
+/// may have grown in width.
+///
+/// \param column The index of the column of which to get the width. Must be
+/// less than the total number of columns.
+///
+/// \return The width of a column.
+text::widths_vector::value_type
+text::table::column_width(const widths_vector::size_type column) const
+{
+ PRE(column < _column_widths.size());
+ return _column_widths[column];
+}
+
+
+/// Gets the widths of all columns.
+///
+/// The returned value is not valid if add_row() is called again, as the columns
+/// may have grown in width.
+///
+/// \return A vector with the width of all columns.
+const text::widths_vector&
+text::table::column_widths(void) const
+{
+ return _column_widths;
+}
+
+
+/// Checks whether the table is empty or not.
+///
+/// \return True if the table is empty; false otherwise.
+bool
+text::table::empty(void) const
+{
+ return _rows.empty();
+}
+
+
+/// Adds a row to the table.
+///
+/// \param row The row to be added. This row must have the same amount of
+/// columns as defined during the construction of the table.
+void
+text::table::add_row(const table_row& row)
+{
+ PRE(row.size() == _column_widths.size());
+ _rows.push_back(row);
+
+ for (table_row::size_type i = 0; i < row.size(); ++i)
+ if (_column_widths[i] < row[i].length())
+ _column_widths[i] = row[i].length();
+}
+
+
+/// Gets an iterator pointing to the beginning of the rows of the table.
+///
+/// \return An iterator on the rows.
+text::table::const_iterator
+text::table::begin(void) const
+{
+ return _rows.begin();
+}
+
+
+/// Gets an iterator pointing to the end of the rows of the table.
+///
+/// \return An iterator on the rows.
+text::table::const_iterator
+text::table::end(void) const
+{
+ return _rows.end();
+}
+
+
+/// Column width to denote that the column has to fit all of its cells.
+const std::size_t text::table_formatter::width_auto = 0;
+
+
+/// Column width to denote that the column can be refilled to fit the table.
+const std::size_t text::table_formatter::width_refill =
+ std::numeric_limits< std::size_t >::max();
+
+
+/// Constructs a new table formatter.
+text::table_formatter::table_formatter(void) :
+ _separator(""),
+ _table_width(0)
+{
+}
+
+
+/// Sets the width of a column.
+///
+/// All columns except one must have a width that is, at least, as wide as the
+/// widest cell in the column. One of the columns can have a width of
+/// width_refill, which indicates that the column will be refilled if the table
+/// does not fit in its maximum width.
+///
+/// \param column The index of the column to set the width for.
+/// \param width The width to set the column to.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_column_width(const table_row::size_type column,
+ const std::size_t width)
+{
+#if !defined(NDEBUG)
+ if (width == width_refill) {
+ for (widths_vector::size_type i = 0; i < _column_widths.size(); i++) {
+ if (i != column)
+ PRE_MSG(_column_widths[i] != width_refill,
+ "Only one column width can be set to width_refill");
+ }
+ }
+#endif
+
+ if (_column_widths.size() < column + 1)
+ _column_widths.resize(column + 1, width_auto);
+ _column_widths[column] = width;
+ return *this;
+}
+
+
+/// Sets the separator to use between the cells.
+///
+/// \param separator The separator to use.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_separator(const char* separator)
+{
+ _separator = separator;
+ return *this;
+}
+
+
+/// Sets the maximum width of the table.
+///
+/// \param table_width The maximum width of the table; cannot be zero.
+///
+/// \return A reference to this formatter to allow using the builder pattern.
+text::table_formatter&
+text::table_formatter::set_table_width(const std::size_t table_width)
+{
+ PRE(table_width > 0);
+ _table_width = table_width;
+ return *this;
+}
+
+
+/// Formats a table into a collection of textual lines.
+///
+/// \param t Table to format.
+///
+/// \return A collection of textual lines.
+std::vector< std::string >
+text::table_formatter::format(const table& t) const
+{
+ std::vector< std::string > lines;
+
+ if (!t.empty()) {
+ widths_vector widths = override_column_widths(t, _column_widths);
+ if (_table_width != 0)
+ refill_widths(widths, _table_width, _separator.length());
+
+ for (table::const_iterator iter = t.begin(); iter != t.end(); ++iter) {
+ const std::vector< std::string > sublines =
+ format_row(*iter, widths, _separator);
+ std::copy(sublines.begin(), sublines.end(),
+ std::back_inserter(lines));
+ }
+ }
+
+ return lines;
+}
diff --git a/utils/text/table.hpp b/utils/text/table.hpp
new file mode 100644
index 000000000000..5fd7c50c991c
--- /dev/null
+++ b/utils/text/table.hpp
@@ -0,0 +1,125 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/table.hpp
+/// Table construction and formatting.
+
+#if !defined(UTILS_TEXT_TABLE_HPP)
+#define UTILS_TEXT_TABLE_HPP
+
+#include "utils/text/table_fwd.hpp"
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+/// Representation of a table.
+///
+/// A table is nothing more than a matrix of rows by columns. The number of
+/// columns is hardcoded at construction times, and the rows can be accumulated
+/// at a later stage.
+///
+/// The only value of this class is a simpler and more natural mechanism of the
+/// construction of a table, with additional sanity checks. We could as well
+/// just expose the internal data representation to our users.
+class table {
+ /// Widths of the table columns so far.
+ widths_vector _column_widths;
+
+ /// Type defining the collection of rows in the table.
+ typedef std::vector< table_row > rows_vector;
+
+ /// The rows of the table.
+ ///
+ /// This is actually the matrix representing the table. Every element of
+ /// this vector (which are vectors themselves) must have _ncolumns items.
+ rows_vector _rows;
+
+public:
+ table(const table_row::size_type);
+
+ widths_vector::size_type ncolumns(void) const;
+ widths_vector::value_type column_width(const widths_vector::size_type)
+ const;
+ const widths_vector& column_widths(void) const;
+
+ void add_row(const table_row&);
+
+ bool empty(void) const;
+
+ /// Constant iterator on the rows of the table.
+ typedef rows_vector::const_iterator const_iterator;
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+};
+
+
+/// Settings to format a table.
+///
+/// This class implements a builder pattern to construct an object that contains
+/// all the knowledge to format a table. Once all the settings have been set,
+/// the format() method provides the algorithm to apply such formatting settings
+/// to any input table.
+class table_formatter {
+ /// Text to use as the separator between cells.
+ std::string _separator;
+
+ /// Colletion of widths of the columns of a table.
+ std::size_t _table_width;
+
+ /// Widths of the table columns.
+ ///
+ /// Note that this only includes widths for the column widths explicitly
+ /// overriden by the caller. In other words, this vector can be shorter
+ /// than the table passed to the format() method, which is just fine. Any
+ /// non-specified column widths are assumed to be width_auto.
+ widths_vector _column_widths;
+
+public:
+ table_formatter(void);
+
+ static const std::size_t width_auto;
+ static const std::size_t width_refill;
+ table_formatter& set_column_width(const table_row::size_type,
+ const std::size_t);
+ table_formatter& set_separator(const char*);
+ table_formatter& set_table_width(const std::size_t);
+
+ std::vector< std::string > format(const table&) const;
+};
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TABLE_HPP)
diff --git a/utils/text/table_fwd.hpp b/utils/text/table_fwd.hpp
new file mode 100644
index 000000000000..77c6b1fa8c78
--- /dev/null
+++ b/utils/text/table_fwd.hpp
@@ -0,0 +1,58 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/table_fwd.hpp
+/// Forward declarations for utils/text/table.hpp
+
+#if !defined(UTILS_TEXT_TABLE_FWD_HPP)
+#define UTILS_TEXT_TABLE_FWD_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace text {
+
+
+/// Values of the cells of a particular table row.
+typedef std::vector< std::string > table_row;
+
+
+/// Vector of column widths.
+typedef std::vector< std::size_t > widths_vector;
+
+
+class table;
+class table_formatter;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TABLE_FWD_HPP)
diff --git a/utils/text/table_test.cpp b/utils/text/table_test.cpp
new file mode 100644
index 000000000000..45928dae89c4
--- /dev/null
+++ b/utils/text/table_test.cpp
@@ -0,0 +1,413 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/table.hpp"
+
+#include <algorithm>
+
+#include <atf-c++.hpp>
+
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+/// Performs a check on text::table_formatter.
+///
+/// This is provided for test simplicity's sake. Having to match the result of
+/// the formatting on a line by line basis would result in too verbose tests
+/// (maybe not with C++11, but not using this yet).
+///
+/// Because of the flattening of the formatted table into a string, we risk
+/// misdetecting problems when the algorithm bundles newlines into the lines of
+/// a table. This should not happen, and not accounting for this little detail
+/// makes testing so much easier.
+///
+/// \param expected Textual representation of the table, as a collection of
+/// lines separated by newline characters.
+/// \param formatter The formatter to use.
+/// \param table The table to format.
+static void
+table_formatter_check(const std::string& expected,
+ const text::table_formatter& formatter,
+ const text::table& table)
+{
+ ATF_REQUIRE_EQ(expected, text::join(formatter.format(table), "\n") + "\n");
+}
+
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__ncolumns);
+ATF_TEST_CASE_BODY(table__ncolumns)
+{
+ ATF_REQUIRE_EQ(5, text::table(5).ncolumns());
+ ATF_REQUIRE_EQ(10, text::table(10).ncolumns());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__column_width);
+ATF_TEST_CASE_BODY(table__column_width)
+{
+ text::table_row row1;
+ row1.push_back("1234");
+ row1.push_back("123456");
+ text::table_row row2;
+ row2.push_back("12");
+ row2.push_back("12345678");
+
+ text::table table(2);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ ATF_REQUIRE_EQ(4, table.column_width(0));
+ ATF_REQUIRE_EQ(8, table.column_width(1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__column_widths);
+ATF_TEST_CASE_BODY(table__column_widths)
+{
+ text::table_row row1;
+ row1.push_back("1234");
+ row1.push_back("123456");
+ text::table_row row2;
+ row2.push_back("12");
+ row2.push_back("12345678");
+
+ text::table table(2);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ ATF_REQUIRE_EQ(4, table.column_widths()[0]);
+ ATF_REQUIRE_EQ(8, table.column_widths()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__empty);
+ATF_TEST_CASE_BODY(table__empty)
+{
+ text::table table(2);
+ ATF_REQUIRE(table.empty());
+ table.add_row(text::table_row(2));
+ ATF_REQUIRE(!table.empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table__iterate);
+ATF_TEST_CASE_BODY(table__iterate)
+{
+ text::table_row row1;
+ row1.push_back("foo");
+ text::table_row row2;
+ row2.push_back("bar");
+
+ text::table table(1);
+ table.add_row(row1);
+ table.add_row(row2);
+
+ text::table::const_iterator iter = table.begin();
+ ATF_REQUIRE(iter != table.end());
+ ATF_REQUIRE(row1 == *iter);
+ ++iter;
+ ATF_REQUIRE(iter != table.end());
+ ATF_REQUIRE(row2 == *iter);
+ ++iter;
+ ATF_REQUIRE(iter == table.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__empty);
+ATF_TEST_CASE_BODY(table_formatter__empty)
+{
+ ATF_REQUIRE(text::table_formatter().set_separator(" ")
+ .format(text::table(1)).empty());
+ ATF_REQUIRE(text::table_formatter().set_separator(" ")
+ .format(text::table(10)).empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__defaults);
+ATF_TEST_CASE_BODY(table_formatter__defaults)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First Second Third\n"
+ "Fourth with some textFifth with some more textSixth foo\n",
+ text::table_formatter(), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__no_max_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__no_max_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row with some words\n"
+ "Second row with some words\n",
+ text::table_formatter().set_separator(" | "), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__explicit_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__explicit_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row with some words\n"
+ "Second row with some words\n",
+ text::table_formatter().set_separator(" | ").set_column_width(0, 1024),
+ table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__one_column__max_width);
+ATF_TEST_CASE_BODY(table_formatter__one_column__max_width)
+{
+ text::table table(1);
+ {
+ text::table_row row;
+ row.push_back("First row with some words");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Second row with some words");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First row\nwith some\nwords\n"
+ "Second row\nwith some\nwords\n",
+ text::table_formatter().set_separator(" | ").set_table_width(11),
+ table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__no_max_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__no_max_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with some more text | Sixth foo\n",
+ text::table_formatter().set_separator(" | "), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__explicit_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__explicit_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with some more text | Sixth foo\n",
+ text::table_formatter().set_separator(" | ").set_column_width(0, 23)
+ .set_column_width(1, 28), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__many_columns__max_width);
+ATF_TEST_CASE_BODY(table_formatter__many_columns__max_width)
+{
+ text::table table(3);
+ {
+ text::table_row row;
+ row.push_back("First");
+ row.push_back("Second");
+ row.push_back("Third");
+ table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("Fourth with some text");
+ row.push_back("Fifth with some more text");
+ row.push_back("Sixth foo");
+ table.add_row(row);
+ }
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with | Sixth foo\n"
+ " | some more | \n"
+ " | text | \n",
+ text::table_formatter().set_separator(" | ").set_table_width(46)
+ .set_column_width(1, text::table_formatter::width_refill)
+ .set_column_width(0, text::table_formatter::width_auto), table);
+
+ table_formatter_check(
+ "First | Second | Third\n"
+ "Fourth with some text | Fifth with | Sixth foo\n"
+ " | some more | \n"
+ " | text | \n",
+ text::table_formatter().set_separator(" | ").set_table_width(48)
+ .set_column_width(1, text::table_formatter::width_refill)
+ .set_column_width(0, 23), table);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(table_formatter__use_case__cli_help);
+ATF_TEST_CASE_BODY(table_formatter__use_case__cli_help)
+{
+ text::table options_table(2);
+ {
+ text::table_row row;
+ row.push_back("-a a_value");
+ row.push_back("This is the description of the first flag");
+ options_table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("-b");
+ row.push_back("And this is the text for the second flag");
+ options_table.add_row(row);
+ }
+
+ text::table commands_table(2);
+ {
+ text::table_row row;
+ row.push_back("first");
+ row.push_back("This is the first command");
+ commands_table.add_row(row);
+ }
+ {
+ text::table_row row;
+ row.push_back("second");
+ row.push_back("And this is the second command");
+ commands_table.add_row(row);
+ }
+
+ const text::widths_vector::value_type first_width =
+ std::max(options_table.column_width(0), commands_table.column_width(0));
+
+ table_formatter_check(
+ "-a a_value This is the description\n"
+ " of the first flag\n"
+ "-b And this is the text for\n"
+ " the second flag\n",
+ text::table_formatter().set_separator(" ").set_table_width(36)
+ .set_column_width(0, first_width)
+ .set_column_width(1, text::table_formatter::width_refill),
+ options_table);
+
+ table_formatter_check(
+ "first This is the first\n"
+ " command\n"
+ "second And this is the second\n"
+ " command\n",
+ text::table_formatter().set_separator(" ").set_table_width(36)
+ .set_column_width(0, first_width)
+ .set_column_width(1, text::table_formatter::width_refill),
+ commands_table);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, table__ncolumns);
+ ATF_ADD_TEST_CASE(tcs, table__column_width);
+ ATF_ADD_TEST_CASE(tcs, table__column_widths);
+ ATF_ADD_TEST_CASE(tcs, table__empty);
+ ATF_ADD_TEST_CASE(tcs, table__iterate);
+
+ ATF_ADD_TEST_CASE(tcs, table_formatter__empty);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__defaults);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__no_max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__explicit_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__one_column__max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__no_max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__explicit_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__many_columns__max_width);
+ ATF_ADD_TEST_CASE(tcs, table_formatter__use_case__cli_help);
+}
diff --git a/utils/text/templates.cpp b/utils/text/templates.cpp
new file mode 100644
index 000000000000..13cb27b1cce2
--- /dev/null
+++ b/utils/text/templates.cpp
@@ -0,0 +1,764 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/templates.hpp"
+
+#include <algorithm>
+#include <fstream>
+#include <sstream>
+#include <stack>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Definition of a template statement.
+///
+/// A template statement is a particular line in the input file that is
+/// preceeded by a template marker. This class provides a high-level
+/// representation of the contents of such statement and a mechanism to parse
+/// the textual line into this high-level representation.
+class statement_def {
+public:
+ /// Types of the known statements.
+ enum statement_type {
+ /// Alternative clause of a conditional.
+ ///
+ /// Takes no arguments.
+ type_else,
+
+ /// End of conditional marker.
+ ///
+ /// Takes no arguments.
+ type_endif,
+
+ /// End of loop marker.
+ ///
+ /// Takes no arguments.
+ type_endloop,
+
+ /// Beginning of a conditional.
+ ///
+ /// Takes a single argument, which denotes the name of the variable or
+ /// vector to check for existence. This is the only expression
+ /// supported.
+ type_if,
+
+ /// Beginning of a loop over all the elements of a vector.
+ ///
+ /// Takes two arguments: the name of the vector over which to iterate
+ /// and the name of the iterator to later index this vector.
+ type_loop,
+ };
+
+private:
+ /// Internal data describing the structure of a particular statement type.
+ struct type_descriptor {
+ /// The native type of the statement.
+ statement_type type;
+
+ /// The expected number of arguments.
+ unsigned int n_arguments;
+
+ /// Constructs a new type descriptor.
+ ///
+ /// \param type_ The native type of the statement.
+ /// \param n_arguments_ The expected number of arguments.
+ type_descriptor(const statement_type type_,
+ const unsigned int n_arguments_)
+ : type(type_), n_arguments(n_arguments_)
+ {
+ }
+ };
+
+ /// Mapping of statement type names to their definitions.
+ typedef std::map< std::string, type_descriptor > types_map;
+
+ /// Description of the different statement types.
+ ///
+ /// This static map is initialized once and reused later for any statement
+ /// lookup. Unfortunately, we cannot perform this initialization in a
+ /// static manner without C++11.
+ static types_map _types;
+
+ /// Generates a new types definition map.
+ ///
+ /// \return A new types definition map, to be assigned to _types.
+ static types_map
+ generate_types_map(void)
+ {
+ // If you change this, please edit the comments in the enum above.
+ types_map types;
+ types.insert(types_map::value_type(
+ "else", type_descriptor(type_else, 0)));
+ types.insert(types_map::value_type(
+ "endif", type_descriptor(type_endif, 0)));
+ types.insert(types_map::value_type(
+ "endloop", type_descriptor(type_endloop, 0)));
+ types.insert(types_map::value_type(
+ "if", type_descriptor(type_if, 1)));
+ types.insert(types_map::value_type(
+ "loop", type_descriptor(type_loop, 2)));
+ return types;
+ }
+
+public:
+ /// The type of the statement.
+ statement_type type;
+
+ /// The arguments to the statement, in textual form.
+ const std::vector< std::string > arguments;
+
+ /// Creates a new statement.
+ ///
+ /// \param type_ The type of the statement.
+ /// \param arguments_ The arguments to the statement.
+ statement_def(const statement_type& type_,
+ const std::vector< std::string >& arguments_) :
+ type(type_), arguments(arguments_)
+ {
+#if !defined(NDEBUG)
+ for (types_map::const_iterator iter = _types.begin();
+ iter != _types.end(); ++iter) {
+ const type_descriptor& descriptor = (*iter).second;
+ if (descriptor.type == type_) {
+ PRE(descriptor.n_arguments == arguments_.size());
+ return;
+ }
+ }
+ UNREACHABLE;
+#endif
+ }
+
+ /// Parses a statement.
+ ///
+ /// \param line The textual representation of the statement without any
+ /// prefix.
+ ///
+ /// \return The parsed statement.
+ ///
+ /// \throw text::syntax_error If the statement is not correctly defined.
+ static statement_def
+ parse(const std::string& line)
+ {
+ if (_types.empty())
+ _types = generate_types_map();
+
+ const std::vector< std::string > words = text::split(line, ' ');
+ if (words.empty())
+ throw text::syntax_error("Empty statement");
+
+ const types_map::const_iterator iter = _types.find(words[0]);
+ if (iter == _types.end())
+ throw text::syntax_error(F("Unknown statement '%s'") % words[0]);
+ const type_descriptor& descriptor = (*iter).second;
+
+ if (words.size() - 1 != descriptor.n_arguments)
+ throw text::syntax_error(F("Invalid number of arguments for "
+ "statement '%s'") % words[0]);
+
+ std::vector< std::string > new_arguments;
+ new_arguments.resize(words.size() - 1);
+ std::copy(words.begin() + 1, words.end(), new_arguments.begin());
+
+ return statement_def(descriptor.type, new_arguments);
+ }
+};
+
+
+statement_def::types_map statement_def::_types;
+
+
+/// Definition of a loop.
+///
+/// This simple structure is used to keep track of the parameters of a loop.
+struct loop_def {
+ /// The name of the vector over which this loop is iterating.
+ std::string vector;
+
+ /// The name of the iterator defined by this loop.
+ std::string iterator;
+
+ /// Position in the input to which to rewind to on looping.
+ ///
+ /// This position points to the line after the loop statement, not the loop
+ /// itself. This is one of the reasons why we have this structure, so that
+ /// we can maintain the data about the loop without having to re-process it.
+ std::istream::pos_type position;
+
+ /// Constructs a new loop definition.
+ ///
+ /// \param vector_ The name of the vector (first argument).
+ /// \param iterator_ The name of the iterator (second argumnet).
+ /// \param position_ Position of the next line after the loop statement.
+ loop_def(const std::string& vector_, const std::string& iterator_,
+ const std::istream::pos_type position_) :
+ vector(vector_), iterator(iterator_), position(position_)
+ {
+ }
+};
+
+
+/// Stateful class to instantiate the templates in an input stream.
+///
+/// The goal of this parser is to scan the input once and not buffer anything in
+/// memory. The only exception are loops: loops are reinterpreted on every
+/// iteration from the same input file by rewidining the stream to the
+/// appropriate position.
+class templates_parser : utils::noncopyable {
+ /// The templates to apply.
+ ///
+ /// Note that this is not const because the parser has to have write access
+ /// to the templates. In particular, it needs to be able to define the
+ /// iterators as regular variables.
+ text::templates_def _templates;
+
+ /// Prefix that marks a line as a statement.
+ const std::string _prefix;
+
+ /// Delimiter to surround an expression instantiation.
+ const std::string _delimiter;
+
+ /// Whether to skip incoming lines or not.
+ ///
+ /// The top of the stack is true whenever we encounter a conditional that
+ /// evaluates to false or a loop that does not have any iterations left.
+ /// Under these circumstances, we need to continue scanning the input stream
+ /// until we find the matching closing endif or endloop construct.
+ ///
+ /// This is a stack rather than a plain boolean to allow us deal with
+ /// if-else clauses.
+ std::stack< bool > _skip;
+
+ /// Current count of nested conditionals.
+ unsigned int _if_level;
+
+ /// Level of the top-most conditional that evaluated to false.
+ unsigned int _exit_if_level;
+
+ /// Current count of nested loops.
+ unsigned int _loop_level;
+
+ /// Level of the top-most loop that does not have any iterations left.
+ unsigned int _exit_loop_level;
+
+ /// Information about all the nested loops up to the current point.
+ std::stack< loop_def > _loops;
+
+ /// Checks if a line is a statement or not.
+ ///
+ /// \param line The line to validate.
+ ///
+ /// \return True if the line looks like a statement, which is determined by
+ /// checking if the line starts by the predefined prefix.
+ bool
+ is_statement(const std::string& line)
+ {
+ return ((line.length() >= _prefix.length() &&
+ line.substr(0, _prefix.length()) == _prefix) &&
+ (line.length() < _delimiter.length() ||
+ line.substr(0, _delimiter.length()) != _delimiter));
+ }
+
+ /// Parses a given statement line into a statement definition.
+ ///
+ /// \param line The line to validate; it must be a valid statement.
+ ///
+ /// \return The parsed statement.
+ ///
+ /// \throw text::syntax_error If the input is not a valid statement.
+ statement_def
+ parse_statement(const std::string& line)
+ {
+ PRE(is_statement(line));
+ return statement_def::parse(line.substr(_prefix.length()));
+ }
+
+ /// Processes a line from the input when not in skip mode.
+ ///
+ /// \param line The line to be processed.
+ /// \param input The input stream from which the line was read. The current
+ /// position in the stream must be after the line being processed.
+ /// \param output The output stream into which to write the results.
+ ///
+ /// \throw text::syntax_error If the input is not valid.
+ void
+ handle_normal(const std::string& line, std::istream& input,
+ std::ostream& output)
+ {
+ if (!is_statement(line)) {
+ // Fast path. Mostly to avoid an indentation level for the big
+ // chunk of code below.
+ output << line << '\n';
+ return;
+ }
+
+ const statement_def statement = parse_statement(line);
+
+ switch (statement.type) {
+ case statement_def::type_else:
+ _skip.top() = !_skip.top();
+ break;
+
+ case statement_def::type_endif:
+ _if_level--;
+ break;
+
+ case statement_def::type_endloop: {
+ PRE(_loops.size() == _loop_level);
+ loop_def& loop = _loops.top();
+
+ const std::size_t next_index = 1 + text::to_type< std::size_t >(
+ _templates.get_variable(loop.iterator));
+
+ if (next_index < _templates.get_vector(loop.vector).size()) {
+ _templates.add_variable(loop.iterator, F("%s") % next_index);
+ input.seekg(loop.position);
+ } else {
+ _loop_level--;
+ _loops.pop();
+ _templates.remove_variable(loop.iterator);
+ }
+ } break;
+
+ case statement_def::type_if: {
+ _if_level++;
+ const std::string value = _templates.evaluate(
+ statement.arguments[0]);
+ if (value.empty() || value == "0" || value == "false") {
+ _exit_if_level = _if_level;
+ _skip.push(true);
+ } else {
+ _skip.push(false);
+ }
+ } break;
+
+ case statement_def::type_loop: {
+ _loop_level++;
+
+ const loop_def loop(statement.arguments[0], statement.arguments[1],
+ input.tellg());
+ if (_templates.get_vector(loop.vector).empty()) {
+ _exit_loop_level = _loop_level;
+ _skip.push(true);
+ } else {
+ _templates.add_variable(loop.iterator, "0");
+ _loops.push(loop);
+ _skip.push(false);
+ }
+ } break;
+ }
+ }
+
+ /// Processes a line from the input when in skip mode.
+ ///
+ /// \param line The line to be processed.
+ ///
+ /// \throw text::syntax_error If the input is not valid.
+ void
+ handle_skip(const std::string& line)
+ {
+ PRE(_skip.top());
+
+ if (!is_statement(line))
+ return;
+
+ const statement_def statement = parse_statement(line);
+ switch (statement.type) {
+ case statement_def::type_else:
+ if (_exit_if_level == _if_level)
+ _skip.top() = !_skip.top();
+ break;
+
+ case statement_def::type_endif:
+ INV(_if_level >= _exit_if_level);
+ if (_if_level == _exit_if_level)
+ _skip.top() = false;
+ _if_level--;
+ _skip.pop();
+ break;
+
+ case statement_def::type_endloop:
+ INV(_loop_level >= _exit_loop_level);
+ if (_loop_level == _exit_loop_level)
+ _skip.top() = false;
+ _loop_level--;
+ _skip.pop();
+ break;
+
+ case statement_def::type_if:
+ _if_level++;
+ _skip.push(true);
+ break;
+
+ case statement_def::type_loop:
+ _loop_level++;
+ _skip.push(true);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ /// Evaluates expressions on a given input line.
+ ///
+ /// An expression is surrounded by _delimiter on both sides. We scan the
+ /// string from left to right finding any expressions that may appear, yank
+ /// them out and call templates_def::evaluate() to get their value.
+ ///
+ /// Lonely or unbalanced appearances of _delimiter on the input line are
+ /// not considered an error, given that the user may actually want to supply
+ /// that character sequence without being interpreted as a template.
+ ///
+ /// \param in_line The input line from which to evaluate expressions.
+ ///
+ /// \return The evaluated line.
+ ///
+ /// \throw text::syntax_error If the expressions in the line are malformed.
+ std::string
+ evaluate(const std::string& in_line)
+ {
+ std::string out_line;
+
+ std::string::size_type last_pos = 0;
+ while (last_pos != std::string::npos) {
+ const std::string::size_type open_pos = in_line.find(
+ _delimiter, last_pos);
+ if (open_pos == std::string::npos) {
+ out_line += in_line.substr(last_pos);
+ last_pos = std::string::npos;
+ } else {
+ const std::string::size_type close_pos = in_line.find(
+ _delimiter, open_pos + _delimiter.length());
+ if (close_pos == std::string::npos) {
+ out_line += in_line.substr(last_pos);
+ last_pos = std::string::npos;
+ } else {
+ out_line += in_line.substr(last_pos, open_pos - last_pos);
+ out_line += _templates.evaluate(in_line.substr(
+ open_pos + _delimiter.length(),
+ close_pos - open_pos - _delimiter.length()));
+ last_pos = close_pos + _delimiter.length();
+ }
+ }
+ }
+
+ return out_line;
+ }
+
+public:
+ /// Constructs a new template parser.
+ ///
+ /// \param templates_ The templates to apply to the processed file.
+ /// \param prefix_ The prefix that identifies lines as statements.
+ /// \param delimiter_ Delimiter to surround a variable instantiation.
+ templates_parser(const text::templates_def& templates_,
+ const std::string& prefix_,
+ const std::string& delimiter_) :
+ _templates(templates_),
+ _prefix(prefix_),
+ _delimiter(delimiter_),
+ _if_level(0),
+ _exit_if_level(0),
+ _loop_level(0),
+ _exit_loop_level(0)
+ {
+ }
+
+ /// Applies the templates to a given input.
+ ///
+ /// \param input The stream to which to apply the templates.
+ /// \param output The stream into which to write the results.
+ ///
+ /// \throw text::syntax_error If the input is not valid. Note that the
+ /// is not guaranteed to be unmodified on exit if an error is
+ /// encountered.
+ void
+ instantiate(std::istream& input, std::ostream& output)
+ {
+ std::string line;
+ while (std::getline(input, line).good()) {
+ if (!_skip.empty() && _skip.top())
+ handle_skip(line);
+ else
+ handle_normal(evaluate(line), input, output);
+ }
+ }
+};
+
+
+} // anonymous namespace
+
+
+/// Constructs an empty templates definition.
+text::templates_def::templates_def(void)
+{
+}
+
+
+/// Sets a string variable in the templates.
+///
+/// If the variable already exists, its value is replaced. This behavior is
+/// required to implement iterators, but client code should really not be
+/// redefining variables.
+///
+/// \pre The variable must not already exist as a vector.
+///
+/// \param name The name of the variable to set.
+/// \param value The value to set the given variable to.
+void
+text::templates_def::add_variable(const std::string& name,
+ const std::string& value)
+{
+ PRE(_vectors.find(name) == _vectors.end());
+ _variables[name] = value;
+}
+
+
+/// Unsets a string variable from the templates.
+///
+/// Client code has no reason to use this. This is only required to implement
+/// proper scoping of loop iterators.
+///
+/// \pre The variable must exist.
+///
+/// \param name The name of the variable to remove from the templates.
+void
+text::templates_def::remove_variable(const std::string& name)
+{
+ PRE(_variables.find(name) != _variables.end());
+ _variables.erase(_variables.find(name));
+}
+
+
+/// Creates a new vector in the templates.
+///
+/// If the vector already exists, it is cleared. Client code should really not
+/// be redefining variables.
+///
+/// \pre The vector must not already exist as a variable.
+///
+/// \param name The name of the vector to set.
+void
+text::templates_def::add_vector(const std::string& name)
+{
+ PRE(_variables.find(name) == _variables.end());
+ _vectors[name] = strings_vector();
+}
+
+
+/// Adds a value to an existing vector in the templates.
+///
+/// \pre name The vector must exist.
+///
+/// \param name The name of the vector to append the value to.
+/// \param value The textual value to append to the vector.
+void
+text::templates_def::add_to_vector(const std::string& name,
+ const std::string& value)
+{
+ PRE(_variables.find(name) == _variables.end());
+ PRE(_vectors.find(name) != _vectors.end());
+ _vectors[name].push_back(value);
+}
+
+
+/// Checks whether a given identifier exists as a variable or a vector.
+///
+/// This is used to implement the evaluation of conditions in if clauses.
+///
+/// \param name The name of the variable or vector.
+///
+/// \return True if the given name exists as a variable or a vector; false
+/// otherwise.
+bool
+text::templates_def::exists(const std::string& name) const
+{
+ return (_variables.find(name) != _variables.end() ||
+ _vectors.find(name) != _vectors.end());
+}
+
+
+/// Gets the value of a variable.
+///
+/// \param name The name of the variable.
+///
+/// \return The value of the requested variable.
+///
+/// \throw text::syntax_error If the variable does not exist.
+const std::string&
+text::templates_def::get_variable(const std::string& name) const
+{
+ const variables_map::const_iterator iter = _variables.find(name);
+ if (iter == _variables.end())
+ throw text::syntax_error(F("Unknown variable '%s'") % name);
+ return (*iter).second;
+}
+
+
+/// Gets a vector.
+///
+/// \param name The name of the vector.
+///
+/// \return A reference to the requested vector.
+///
+/// \throw text::syntax_error If the vector does not exist.
+const text::templates_def::strings_vector&
+text::templates_def::get_vector(const std::string& name) const
+{
+ const vectors_map::const_iterator iter = _vectors.find(name);
+ if (iter == _vectors.end())
+ throw text::syntax_error(F("Unknown vector '%s'") % name);
+ return (*iter).second;
+}
+
+
+/// Indexes a vector and gets the value.
+///
+/// \param name The name of the vector to index.
+/// \param index_name The name of a variable representing the index to use.
+/// This must be convertible to a natural.
+///
+/// \return The value of the vector at the given index.
+///
+/// \throw text::syntax_error If the vector does not existor if the index is out
+/// of range.
+const std::string&
+text::templates_def::get_vector(const std::string& name,
+ const std::string& index_name) const
+{
+ const strings_vector& vector = get_vector(name);
+ const std::string& index_str = get_variable(index_name);
+
+ std::size_t index;
+ try {
+ index = text::to_type< std::size_t >(index_str);
+ } catch (const text::syntax_error& e) {
+ throw text::syntax_error(F("Index '%s' not an integer, value '%s'") %
+ index_name % index_str);
+ }
+ if (index >= vector.size())
+ throw text::syntax_error(F("Index '%s' out of range at position '%s'") %
+ index_name % index);
+
+ return vector[index];
+}
+
+
+/// Evaluates a expression using these templates.
+///
+/// An expression is a query on the current templates to fetch a particular
+/// value. The value is always returned as a string, as this is how templates
+/// are internally stored.
+///
+/// \param expression The expression to evaluate. This should not include any
+/// of the delimiters used in the user input, as otherwise the expression
+/// will not be evaluated properly.
+///
+/// \return The result of the expression evaluation as a string.
+///
+/// \throw text::syntax_error If there is any problem while evaluating the
+/// expression.
+std::string
+text::templates_def::evaluate(const std::string& expression) const
+{
+ const std::string::size_type paren_open = expression.find('(');
+ if (paren_open == std::string::npos) {
+ return get_variable(expression);
+ } else {
+ const std::string::size_type paren_close = expression.find(
+ ')', paren_open);
+ if (paren_close == std::string::npos)
+ throw text::syntax_error(F("Expected ')' in expression '%s')") %
+ expression);
+ if (paren_close != expression.length() - 1)
+ throw text::syntax_error(F("Unexpected text found after ')' in "
+ "expression '%s'") % expression);
+
+ const std::string arg0 = expression.substr(0, paren_open);
+ const std::string arg1 = expression.substr(
+ paren_open + 1, paren_close - paren_open - 1);
+ if (arg0 == "defined") {
+ return exists(arg1) ? "true" : "false";
+ } else if (arg0 == "length") {
+ return F("%s") % get_vector(arg1).size();
+ } else {
+ return get_vector(arg0, arg1);
+ }
+ }
+}
+
+
+/// Applies a set of templates to an input stream.
+///
+/// \param templates The templates to use.
+/// \param input The input to process.
+/// \param output The stream to which to write the processed text.
+///
+/// \throw text::syntax_error If there is any problem processing the input.
+void
+text::instantiate(const templates_def& templates,
+ std::istream& input, std::ostream& output)
+{
+ templates_parser parser(templates, "%", "%%");
+ parser.instantiate(input, output);
+}
+
+
+/// Applies a set of templates to an input file and writes an output file.
+///
+/// \param templates The templates to use.
+/// \param input_file The path to the input to process.
+/// \param output_file The path to the file into which to write the output.
+///
+/// \throw text::error If the input or output files cannot be opened.
+/// \throw text::syntax_error If there is any problem processing the input.
+void
+text::instantiate(const templates_def& templates,
+ const fs::path& input_file, const fs::path& output_file)
+{
+ std::ifstream input(input_file.c_str());
+ if (!input)
+ throw text::error(F("Failed to open %s for read") % input_file);
+
+ std::ofstream output(output_file.c_str());
+ if (!output)
+ throw text::error(F("Failed to open %s for write") % output_file);
+
+ instantiate(templates, input, output);
+}
diff --git a/utils/text/templates.hpp b/utils/text/templates.hpp
new file mode 100644
index 000000000000..ffbf28512d0d
--- /dev/null
+++ b/utils/text/templates.hpp
@@ -0,0 +1,122 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/templates.hpp
+/// Custom templating engine for text documents.
+///
+/// This module provides a simple mechanism to generate text documents based on
+/// templates. The templates are just text files that contain template
+/// statements that instruct this processor to perform transformations on the
+/// input.
+///
+/// While this was originally written to handle HTML templates, it is actually
+/// generic enough to handle any kind of text document, hence why it lives
+/// within the utils::text library.
+///
+/// An example of how the templates look like:
+///
+/// %if names
+/// List of names
+/// -------------
+/// Amount of names: %%length(names)%%
+/// Most preferred name: %%preferred_name%%
+/// Full list:
+/// %loop names iter
+/// * %%last_names(iter)%%, %%names(iter)%%
+/// %endloop
+/// %endif names
+
+#if !defined(UTILS_TEXT_TEMPLATES_HPP)
+#define UTILS_TEXT_TEMPLATES_HPP
+
+#include "utils/text/templates_fwd.hpp"
+
+#include <istream>
+#include <map>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace text {
+
+
+/// Definitions of the templates to apply to a file.
+///
+/// This class provides the environment (e.g. the list of variables) that the
+/// templating system has to use when generating the output files. This
+/// definition is static in the sense that this is what the caller program
+/// specifies.
+class templates_def {
+ /// Mapping of variable names to their values.
+ typedef std::map< std::string, std::string > variables_map;
+
+ /// Collection of global variables available to the templates.
+ variables_map _variables;
+
+ /// Convenience name for a vector of strings.
+ typedef std::vector< std::string > strings_vector;
+
+ /// Mapping of vector names to their contents.
+ ///
+ /// Ideally, these would be represented as part of the _variables, but we
+ /// would need a complex mechanism to identify whether a variable is a
+ /// string or a vector.
+ typedef std::map< std::string, strings_vector > vectors_map;
+
+ /// Collection of vectors available to the templates.
+ vectors_map _vectors;
+
+ const std::string& get_vector(const std::string&, const std::string&) const;
+
+public:
+ templates_def(void);
+
+ void add_variable(const std::string&, const std::string&);
+ void remove_variable(const std::string&);
+ void add_vector(const std::string&);
+ void add_to_vector(const std::string&, const std::string&);
+
+ bool exists(const std::string&) const;
+ const std::string& get_variable(const std::string&) const;
+ const strings_vector& get_vector(const std::string&) const;
+
+ std::string evaluate(const std::string&) const;
+};
+
+
+void instantiate(const templates_def&, std::istream&, std::ostream&);
+void instantiate(const templates_def&, const fs::path&, const fs::path&);
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TEMPLATES_HPP)
diff --git a/utils/text/templates_fwd.hpp b/utils/text/templates_fwd.hpp
new file mode 100644
index 000000000000..c806be0cf497
--- /dev/null
+++ b/utils/text/templates_fwd.hpp
@@ -0,0 +1,45 @@
+// Copyright 2015 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+/// \file utils/text/templates_fwd.hpp
+/// Forward declarations for utils/text/templates.hpp
+
+#if !defined(UTILS_TEXT_TEMPLATES_FWD_HPP)
+#define UTILS_TEXT_TEMPLATES_FWD_HPP
+
+namespace utils {
+namespace text {
+
+
+class templates_def;
+
+
+} // namespace text
+} // namespace utils
+
+#endif // !defined(UTILS_TEXT_TEMPLATES_FWD_HPP)
diff --git a/utils/text/templates_test.cpp b/utils/text/templates_test.cpp
new file mode 100644
index 000000000000..4524dc61a416
--- /dev/null
+++ b/utils/text/templates_test.cpp
@@ -0,0 +1,1001 @@
+// Copyright 2012 The Kyua Authors.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+// * Neither the name of Google Inc. nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "utils/text/templates.hpp"
+
+#include <fstream>
+#include <sstream>
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/operations.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/text/exceptions.hpp"
+
+namespace fs = utils::fs;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Applies a set of templates to an input string and validates the output.
+///
+/// This fails the test case if exp_output does not match the document generated
+/// by the application of the templates.
+///
+/// \param templates The templates to apply.
+/// \param input_str The input document to which to apply the templates.
+/// \param exp_output The expected output document.
+static void
+do_test_ok(const text::templates_def& templates, const std::string& input_str,
+ const std::string& exp_output)
+{
+ std::istringstream input(input_str);
+ std::ostringstream output;
+
+ text::instantiate(templates, input, output);
+ ATF_REQUIRE_EQ(exp_output, output.str());
+}
+
+
+/// Applies a set of templates to an input string and checks for an error.
+///
+/// This fails the test case if the exception raised by the template processing
+/// does not match the expected message.
+///
+/// \param templates The templates to apply.
+/// \param input_str The input document to which to apply the templates.
+/// \param exp_message The expected error message in the raised exception.
+static void
+do_test_fail(const text::templates_def& templates, const std::string& input_str,
+ const std::string& exp_message)
+{
+ std::istringstream input(input_str);
+ std::ostringstream output;
+
+ ATF_REQUIRE_THROW_RE(text::syntax_error, exp_message,
+ text::instantiate(templates, input, output));
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__first);
+ATF_TEST_CASE_BODY(templates_def__add_variable__first)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "first-value");
+ ATF_REQUIRE_EQ("first-value", templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_variable__replace);
+ATF_TEST_CASE_BODY(templates_def__add_variable__replace)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "first-value");
+ templates.add_variable("the-name", "second-value");
+ ATF_REQUIRE_EQ("second-value", templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__remove_variable);
+ATF_TEST_CASE_BODY(templates_def__remove_variable)
+{
+ text::templates_def templates;
+ templates.add_variable("the-name", "the-value");
+ templates.get_variable("the-name"); // Should not throw.
+ templates.remove_variable("the-name");
+ ATF_REQUIRE_THROW(text::syntax_error, templates.get_variable("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__first);
+ATF_TEST_CASE_BODY(templates_def__add_vector__first)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ ATF_REQUIRE(templates.get_vector("the-name").empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_vector__replace);
+ATF_TEST_CASE_BODY(templates_def__add_vector__replace)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ templates.add_to_vector("the-name", "foo");
+ ATF_REQUIRE(!templates.get_vector("the-name").empty());
+ templates.add_vector("the-name");
+ ATF_REQUIRE(templates.get_vector("the-name").empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__add_to_vector);
+ATF_TEST_CASE_BODY(templates_def__add_to_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("the-name");
+ ATF_REQUIRE_EQ(0, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "first");
+ ATF_REQUIRE_EQ(1, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "second");
+ ATF_REQUIRE_EQ(2, templates.get_vector("the-name").size());
+ templates.add_to_vector("the-name", "third");
+ ATF_REQUIRE_EQ(3, templates.get_vector("the-name").size());
+
+ std::vector< std::string > expected;
+ expected.push_back("first");
+ expected.push_back("second");
+ expected.push_back("third");
+ ATF_REQUIRE(expected == templates.get_vector("the-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__variable);
+ATF_TEST_CASE_BODY(templates_def__exists__variable)
+{
+ text::templates_def templates;
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_variable("some-name ", "foo");
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_variable("some-name", "foo");
+ ATF_REQUIRE(templates.exists("some-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__exists__vector);
+ATF_TEST_CASE_BODY(templates_def__exists__vector)
+{
+ text::templates_def templates;
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_vector("some-name ");
+ ATF_REQUIRE(!templates.exists("some-name"));
+ templates.add_vector("some-name");
+ ATF_REQUIRE(templates.exists("some-name"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__ok);
+ATF_TEST_CASE_BODY(templates_def__get_variable__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ templates.add_variable("bar", " baz ");
+ ATF_REQUIRE_EQ("", templates.get_variable("foo"));
+ ATF_REQUIRE_EQ(" baz ", templates.get_variable("bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_variable__unknown);
+ATF_TEST_CASE_BODY(templates_def__get_variable__unknown)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo '",
+ templates.get_variable("foo "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__ok);
+ATF_TEST_CASE_BODY(templates_def__get_vector__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("foo");
+ templates.add_vector("bar");
+ templates.add_to_vector("bar", "baz");
+ ATF_REQUIRE_EQ(0, templates.get_vector("foo").size());
+ ATF_REQUIRE_EQ(1, templates.get_vector("bar").size());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__get_vector__unknown);
+ATF_TEST_CASE_BODY(templates_def__get_vector__unknown)
+{
+ text::templates_def templates;
+ templates.add_vector("foo");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo '",
+ templates.get_vector("foo "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__variable__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ templates.add_variable("bar", " baz ");
+ ATF_REQUIRE_EQ("", templates.evaluate("foo"));
+ ATF_REQUIRE_EQ(" baz ", templates.evaluate("bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__variable__unknown);
+ATF_TEST_CASE_BODY(templates_def__evaluate__variable__unknown)
+{
+ text::templates_def templates;
+ templates.add_variable("foo", "");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'foo1'",
+ templates.evaluate("foo1"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_to_vector("v", "bar");
+ templates.add_to_vector("v", "baz");
+
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_EQ("foo", templates.evaluate("v(index)"));
+ templates.add_variable("index", "1");
+ ATF_REQUIRE_EQ("bar", templates.evaluate("v(index)"));
+ templates.add_variable("index", "2");
+ ATF_REQUIRE_EQ("baz", templates.evaluate("v(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_vector);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'fooz'",
+ templates.evaluate("fooz(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__unknown_index);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__unknown_index)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "0");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown variable 'indexz'",
+ templates.evaluate("v(indexz)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__vector__out_of_range);
+ATF_TEST_CASE_BODY(templates_def__evaluate__vector__out_of_range)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ templates.add_to_vector("v", "foo");
+ templates.add_variable("index", "1");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Index 'index' out of range "
+ "at position '1'", templates.evaluate("v(index)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__defined);
+ATF_TEST_CASE_BODY(templates_def__evaluate__defined)
+{
+ text::templates_def templates;
+ templates.add_vector("the-variable");
+ templates.add_vector("the-vector");
+ ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-variabl)"));
+ ATF_REQUIRE_EQ("false", templates.evaluate("defined(the-vecto)"));
+ ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-variable)"));
+ ATF_REQUIRE_EQ("true", templates.evaluate("defined(the-vector)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__ok);
+ATF_TEST_CASE_BODY(templates_def__evaluate__length__ok)
+{
+ text::templates_def templates;
+ templates.add_vector("v");
+ ATF_REQUIRE_EQ("0", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "foo");
+ ATF_REQUIRE_EQ("1", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "bar");
+ ATF_REQUIRE_EQ("2", templates.evaluate("length(v)"));
+ templates.add_to_vector("v", "baz");
+ ATF_REQUIRE_EQ("3", templates.evaluate("length(v)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__length__unknown_vector);
+ATF_TEST_CASE_BODY(templates_def__evaluate__length__unknown_vector)
+{
+ text::templates_def templates;
+ templates.add_vector("foo1");
+ ATF_REQUIRE_THROW_RE(text::syntax_error, "Unknown vector 'foo'",
+ templates.evaluate("length(foo)"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(templates_def__evaluate__parenthesis_error);
+ATF_TEST_CASE_BODY(templates_def__evaluate__parenthesis_error)
+{
+ text::templates_def templates;
+ ATF_REQUIRE_THROW_RE(text::syntax_error,
+ "Expected '\\)' in.*'foo\\(abc'",
+ templates.evaluate("foo(abc"));
+ ATF_REQUIRE_THROW_RE(text::syntax_error,
+ "Unexpected text.*'\\)' in.*'a\\(b\\)c'",
+ templates.evaluate("a(b)c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_input);
+ATF_TEST_CASE_BODY(instantiate__empty_input)
+{
+ const text::templates_def templates;
+ do_test_ok(templates, "", "");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__ok);
+ATF_TEST_CASE_BODY(instantiate__value__ok)
+{
+ const std::string input =
+ "first line\n"
+ "%%testvar1%%\n"
+ "third line\n"
+ "%%testvar2%% %%testvar3%%%%testvar4%%\n"
+ "fifth line\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "second line\n"
+ "third line\n"
+ "fourth line.\n"
+ "fifth line\n";
+
+ text::templates_def templates;
+ templates.add_variable("testvar1", "second line");
+ templates.add_variable("testvar2", "fourth");
+ templates.add_variable("testvar3", "line");
+ templates.add_variable("testvar4", ".");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__value__unknown_variable);
+ATF_TEST_CASE_BODY(instantiate__value__unknown_variable)
+{
+ const std::string input =
+ "%%testvar1%%\n";
+
+ text::templates_def templates;
+ templates.add_variable("testvar2", "fourth line");
+
+ do_test_fail(templates, input, "Unknown variable 'testvar1'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__ok);
+ATF_TEST_CASE_BODY(instantiate__vector_length__ok)
+{
+ const std::string input =
+ "%%length(testvector1)%%\n"
+ "%%length(testvector2)%% - %%length(testvector3)%%\n";
+
+ const std::string exp_output =
+ "4\n"
+ "0 - 1\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector1");
+ templates.add_to_vector("testvector1", "000");
+ templates.add_to_vector("testvector1", "111");
+ templates.add_to_vector("testvector1", "543");
+ templates.add_to_vector("testvector1", "999");
+ templates.add_vector("testvector2");
+ templates.add_vector("testvector3");
+ templates.add_to_vector("testvector3", "123");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_length__unknown_vector);
+ATF_TEST_CASE_BODY(instantiate__vector_length__unknown_vector)
+{
+ const std::string input =
+ "%%length(testvector)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector2");
+
+ do_test_fail(templates, input, "Unknown vector 'testvector'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__ok);
+ATF_TEST_CASE_BODY(instantiate__vector_value__ok)
+{
+ const std::string input =
+ "first line\n"
+ "%%testvector1(i)%%\n"
+ "third line\n"
+ "%%testvector2(j)%%\n"
+ "fifth line\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "543\n"
+ "third line\n"
+ "123\n"
+ "fifth line\n";
+
+ text::templates_def templates;
+ templates.add_variable("i", "2");
+ templates.add_variable("j", "0");
+ templates.add_vector("testvector1");
+ templates.add_to_vector("testvector1", "000");
+ templates.add_to_vector("testvector1", "111");
+ templates.add_to_vector("testvector1", "543");
+ templates.add_to_vector("testvector1", "999");
+ templates.add_vector("testvector2");
+ templates.add_to_vector("testvector2", "123");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__unknown_vector);
+ATF_TEST_CASE_BODY(instantiate__vector_value__unknown_vector)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector2");
+
+ do_test_fail(templates, input, "Unknown vector 'testvector'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__empty);
+ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__empty)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector");
+ templates.add_variable("j", "0");
+
+ do_test_fail(templates, input, "Index 'j' out of range at position '0'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__vector_value__out_of_range__not_empty);
+ATF_TEST_CASE_BODY(instantiate__vector_value__out_of_range__not_empty)
+{
+ const std::string input =
+ "%%testvector(j)%%\n";
+
+ text::templates_def templates;
+ templates.add_vector("testvector");
+ templates.add_to_vector("testvector", "a");
+ templates.add_to_vector("testvector", "b");
+ templates.add_variable("j", "2");
+
+ do_test_fail(templates, input, "Index 'j' out of range at position '2'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__taken);
+ATF_TEST_CASE_BODY(instantiate__if__one_level__taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(some_var)\n"
+ "hello from within the variable conditional\n"
+ "%endif\n"
+ "%if defined(some_vector)\n"
+ "hello from within the vector conditional\n"
+ "%else\n"
+ "bye from within the vector conditional\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "hello from within the variable conditional\n"
+ "hello from within the vector conditional\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("some_var", "zzz");
+ templates.add_vector("some_vector");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__one_level__not_taken);
+ATF_TEST_CASE_BODY(instantiate__if__one_level__not_taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(some_var)\n"
+ "hello from within the variable conditional\n"
+ "%endif\n"
+ "%if defined(some_vector)\n"
+ "hello from within the vector conditional\n"
+ "%else\n"
+ "bye from within the vector conditional\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "bye from within the vector conditional\n"
+ "some more\n";
+
+ text::templates_def templates;
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__taken);
+ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(var1)\n"
+ "first before\n"
+ "%if length(var2)\n"
+ "second before\n"
+ "%if defined(var3)\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "%endif\n"
+ "second after\n"
+ "%else\n"
+ "second after not shown\n"
+ "%endif\n"
+ "first after\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "first before\n"
+ "second before\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "second after\n"
+ "first after\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "false");
+ templates.add_vector("var2");
+ templates.add_to_vector("var2", "not-empty");
+ templates.add_variable("var3", "foobar");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__if__multiple_levels__not_taken);
+ATF_TEST_CASE_BODY(instantiate__if__multiple_levels__not_taken)
+{
+ const std::string input =
+ "first line\n"
+ "%if defined(var1)\n"
+ "first before\n"
+ "%if length(var2)\n"
+ "second before\n"
+ "%if defined(var3)\n"
+ "third before\n"
+ "hello from within the conditional\n"
+ "third after\n"
+ "%else\n"
+ "will not be shown either\n"
+ "%endif\n"
+ "second after\n"
+ "%else\n"
+ "second after shown\n"
+ "%endif\n"
+ "first after\n"
+ "%endif\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "first before\n"
+ "second after shown\n"
+ "first after\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "false");
+ templates.add_vector("var2");
+ templates.add_vector("var3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__no_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__no_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "hello\n"
+ "value in vector: %%table1(i)%%\n"
+ "%if defined(var1)\n" "some other text\n" "%endif\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_variable("var1", "defined");
+ templates.add_vector("table1");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__multiple_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__multiple_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "hello %%table1(i)%% %%table2(i)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "hello foo1 foo2\n"
+ "hello bar1 bar2\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "foo1");
+ templates.add_to_vector("table1", "bar1");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "foo2");
+ templates.add_to_vector("table2", "bar2");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__no_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__nested__no_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "before: %%table1(i)%%\n"
+ "%loop table2 j\n"
+ "before: %%table2(j)%%\n"
+ "%loop table3 k\n"
+ "%%table3(k)%%\n"
+ "%endloop\n"
+ "after: %%table2(i)%%\n"
+ "%endloop\n"
+ "after: %%table1(i)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "before: a\n"
+ "after: a\n"
+ "before: b\n"
+ "after: b\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_vector("table3");
+ templates.add_to_vector("table3", "1");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__nested__multiple_iterations);
+ATF_TEST_CASE_BODY(instantiate__loop__nested__multiple_iterations)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 i\n"
+ "%loop table2 j\n"
+ "%%table1(i)%% %%table2(j)%%\n"
+ "%endloop\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "a 1\n"
+ "a 2\n"
+ "a 3\n"
+ "b 1\n"
+ "b 2\n"
+ "b 3\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "1");
+ templates.add_to_vector("table2", "2");
+ templates.add_to_vector("table2", "3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__sequential);
+ATF_TEST_CASE_BODY(instantiate__loop__sequential)
+{
+ const std::string input =
+ "first line\n"
+ "%loop table1 iter\n"
+ "1: %%table1(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table2 iter\n"
+ "2: %%table2(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table3 iter\n"
+ "3: %%table3(iter)%%\n"
+ "%endloop\n"
+ "divider\n"
+ "%loop table4 iter\n"
+ "4: %%table4(iter)%%\n"
+ "%endloop\n"
+ "some more\n";
+
+ const std::string exp_output =
+ "first line\n"
+ "1: a\n"
+ "1: b\n"
+ "divider\n"
+ "divider\n"
+ "divider\n"
+ "4: 1\n"
+ "4: 2\n"
+ "4: 3\n"
+ "some more\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "a");
+ templates.add_to_vector("table1", "b");
+ templates.add_vector("table2");
+ templates.add_vector("table3");
+ templates.add_vector("table4");
+ templates.add_to_vector("table4", "1");
+ templates.add_to_vector("table4", "2");
+ templates.add_to_vector("table4", "3");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__loop__scoping);
+ATF_TEST_CASE_BODY(instantiate__loop__scoping)
+{
+ const std::string input =
+ "%loop table1 i\n"
+ "%if defined(i)\n" "i defined inside scope 1\n" "%endif\n"
+ "%loop table2 j\n"
+ "%if defined(i)\n" "i defined inside scope 2\n" "%endif\n"
+ "%if defined(j)\n" "j defined inside scope 2\n" "%endif\n"
+ "%endloop\n"
+ "%if defined(j)\n" "j defined inside scope 1\n" "%endif\n"
+ "%endloop\n"
+ "%if defined(i)\n" "i defined outside\n" "%endif\n"
+ "%if defined(j)\n" "j defined outside\n" "%endif\n";
+
+ const std::string exp_output =
+ "i defined inside scope 1\n"
+ "i defined inside scope 2\n"
+ "j defined inside scope 2\n"
+ "i defined inside scope 1\n"
+ "i defined inside scope 2\n"
+ "j defined inside scope 2\n";
+
+ text::templates_def templates;
+ templates.add_vector("table1");
+ templates.add_to_vector("table1", "first");
+ templates.add_to_vector("table1", "second");
+ templates.add_vector("table2");
+ templates.add_to_vector("table2", "first");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__mismatched_delimiters);
+ATF_TEST_CASE_BODY(instantiate__mismatched_delimiters)
+{
+ const std::string input =
+ "this is some %% text\n"
+ "and this is %%var%% text%%\n";
+
+ const std::string exp_output =
+ "this is some %% text\n"
+ "and this is some more text%%\n";
+
+ text::templates_def templates;
+ templates.add_variable("var", "some more");
+
+ do_test_ok(templates, input, exp_output);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__empty_statement);
+ATF_TEST_CASE_BODY(instantiate__empty_statement)
+{
+ do_test_fail(text::templates_def(), "%\n", "Empty statement");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__unknown_statement);
+ATF_TEST_CASE_BODY(instantiate__unknown_statement)
+{
+ do_test_fail(text::templates_def(), "%if2\n", "Unknown statement 'if2'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__invalid_narguments);
+ATF_TEST_CASE_BODY(instantiate__invalid_narguments)
+{
+ do_test_fail(text::templates_def(), "%if a b\n",
+ "Invalid number of arguments for statement 'if'");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__ok);
+ATF_TEST_CASE_BODY(instantiate__files__ok)
+{
+ text::templates_def templates;
+ templates.add_variable("string", "Hello, world!");
+
+ atf::utils::create_file("input.txt", "The string is: %%string%%\n");
+
+ text::instantiate(templates, fs::path("input.txt"), fs::path("output.txt"));
+
+ std::ifstream output("output.txt");
+ std::string line;
+ ATF_REQUIRE(std::getline(output, line).good());
+ ATF_REQUIRE_EQ(line, "The string is: Hello, world!");
+ ATF_REQUIRE(std::getline(output, line).eof());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(instantiate__files__input_error);
+ATF_TEST_CASE_BODY(instantiate__files__input_error)
+{
+ text::templates_def templates;
+ ATF_REQUIRE_THROW_RE(text::error, "Failed to open input.txt for read",
+ text::instantiate(templates, fs::path("input.txt"),
+ fs::path("output.txt")));
+}
+
+
+ATF_TEST_CASE(instantiate__files__output_error);
+ATF_TEST_CASE_HEAD(instantiate__files__output_error)
+{
+ set_md_var("require.user", "unprivileged");
+}
+ATF_TEST_CASE_BODY(instantiate__files__output_error)
+{
+ text::templates_def templates;
+
+ atf::utils::create_file("input.txt", "");
+
+ fs::mkdir(fs::path("dir"), 0444);
+
+ ATF_REQUIRE_THROW_RE(text::error, "Failed to open dir/output.txt for write",
+ text::instantiate(templates, fs::path("input.txt"),
+ fs::path("dir/output.txt")));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__first);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_variable__replace);
+ ATF_ADD_TEST_CASE(tcs, templates_def__remove_variable);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__first);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_vector__replace);
+ ATF_ADD_TEST_CASE(tcs, templates_def__add_to_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__exists__variable);
+ ATF_ADD_TEST_CASE(tcs, templates_def__exists__vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_variable__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__get_vector__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__variable__unknown);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__unknown_index);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__vector__out_of_range);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__defined);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__ok);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__length__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, templates_def__evaluate__parenthesis_error);
+
+ ATF_ADD_TEST_CASE(tcs, instantiate__empty_input);
+ ATF_ADD_TEST_CASE(tcs, instantiate__value__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__value__unknown_variable);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_length__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__unknown_vector);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__empty);
+ ATF_ADD_TEST_CASE(tcs, instantiate__vector_value__out_of_range__not_empty);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__one_level__not_taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__if__multiple_levels__not_taken);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__no_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__multiple_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__no_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__nested__multiple_iterations);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__sequential);
+ ATF_ADD_TEST_CASE(tcs, instantiate__loop__scoping);
+ ATF_ADD_TEST_CASE(tcs, instantiate__mismatched_delimiters);
+ ATF_ADD_TEST_CASE(tcs, instantiate__empty_statement);
+ ATF_ADD_TEST_CASE(tcs, instantiate__unknown_statement);
+ ATF_ADD_TEST_CASE(tcs, instantiate__invalid_narguments);
+
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__ok);
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__input_error);
+ ATF_ADD_TEST_CASE(tcs, instantiate__files__output_error);
+}