aboutsummaryrefslogtreecommitdiffstats
path: root/utils
diff options
context:
space:
mode:
authorBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
committerBrooks Davis <brooks@FreeBSD.org>2020-03-17 16:56:50 +0000
commit08334c51dbb99d9ecd2bb86a2d94ed06da9e167a (patch)
treec43eb24d59bd5c963583a5190caef80fc8387322 /utils
downloadsrc-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.tar.gz
src-08334c51dbb99d9ecd2bb86a2d94ed06da9e167a.zip
Import the kyua testing framework for infrastructure softwarevendor/kyua/0.13-a685f91vendor/kyua
Imported at 0.13 plus assumulated changes to git hash a685f91. Obtained from: https://github.com/jmmv/kyua Sponsored by: DARPA
Notes
Notes: svn path=/vendor/kyua/dist/; revision=359042 svn path=/vendor/kyua/0.13-a685f91/; revision=359043; tag=vendor/kyua/0.13-a685f91
Diffstat (limited to 'utils')
-rw-r--r--utils/.gitignore2
-rw-r--r--utils/Kyuafile24
-rw-r--r--utils/Makefile.am.inc133
-rw-r--r--utils/auto_array.hpp102
-rw-r--r--utils/auto_array.ipp227
-rw-r--r--utils/auto_array_fwd.hpp43
-rw-r--r--utils/auto_array_test.cpp312
-rw-r--r--utils/cmdline/Kyuafile11
-rw-r--r--utils/cmdline/Makefile.am.inc96
-rw-r--r--utils/cmdline/base_command.cpp201
-rw-r--r--utils/cmdline/base_command.hpp162
-rw-r--r--utils/cmdline/base_command.ipp104
-rw-r--r--utils/cmdline/base_command_fwd.hpp47
-rw-r--r--utils/cmdline/base_command_test.cpp295
-rw-r--r--utils/cmdline/commands_map.hpp96
-rw-r--r--utils/cmdline/commands_map.ipp161
-rw-r--r--utils/cmdline/commands_map_fwd.hpp45
-rw-r--r--utils/cmdline/commands_map_test.cpp140
-rw-r--r--utils/cmdline/exceptions.cpp175
-rw-r--r--utils/cmdline/exceptions.hpp109
-rw-r--r--utils/cmdline/exceptions_test.cpp83
-rw-r--r--utils/cmdline/globals.cpp78
-rw-r--r--utils/cmdline/globals.hpp48
-rw-r--r--utils/cmdline/globals_test.cpp77
-rw-r--r--utils/cmdline/options.cpp605
-rw-r--r--utils/cmdline/options.hpp237
-rw-r--r--utils/cmdline/options_fwd.hpp51
-rw-r--r--utils/cmdline/options_test.cpp526
-rw-r--r--utils/cmdline/parser.cpp385
-rw-r--r--utils/cmdline/parser.hpp85
-rw-r--r--utils/cmdline/parser.ipp83
-rw-r--r--utils/cmdline/parser_fwd.hpp58
-rw-r--r--utils/cmdline/parser_test.cpp688
-rw-r--r--utils/cmdline/ui.cpp276
-rw-r--r--utils/cmdline/ui.hpp79
-rw-r--r--utils/cmdline/ui_fwd.hpp45
-rw-r--r--utils/cmdline/ui_mock.cpp114
-rw-r--r--utils/cmdline/ui_mock.hpp78
-rw-r--r--utils/cmdline/ui_test.cpp424
-rw-r--r--utils/config/Kyuafile10
-rw-r--r--utils/config/Makefile.am.inc87
-rw-r--r--utils/config/exceptions.cpp149
-rw-r--r--utils/config/exceptions.hpp106
-rw-r--r--utils/config/exceptions_test.cpp133
-rw-r--r--utils/config/keys.cpp70
-rw-r--r--utils/config/keys.hpp52
-rw-r--r--utils/config/keys_fwd.hpp51
-rw-r--r--utils/config/keys_test.cpp114
-rw-r--r--utils/config/lua_module.cpp282
-rw-r--r--utils/config/lua_module.hpp50
-rw-r--r--utils/config/lua_module_test.cpp474
-rw-r--r--utils/config/nodes.cpp589
-rw-r--r--utils/config/nodes.hpp272
-rw-r--r--utils/config/nodes.ipp408
-rw-r--r--utils/config/nodes_fwd.hpp70
-rw-r--r--utils/config/nodes_test.cpp695
-rw-r--r--utils/config/parser.cpp181
-rw-r--r--utils/config/parser.hpp95
-rw-r--r--utils/config/parser_fwd.hpp45
-rw-r--r--utils/config/parser_test.cpp252
-rw-r--r--utils/config/tree.cpp338
-rw-r--r--utils/config/tree.hpp128
-rw-r--r--utils/config/tree.ipp156
-rw-r--r--utils/config/tree_fwd.hpp52
-rw-r--r--utils/config/tree_test.cpp1086
-rw-r--r--utils/datetime.cpp613
-rw-r--r--utils/datetime.hpp140
-rw-r--r--utils/datetime_fwd.hpp46
-rw-r--r--utils/datetime_test.cpp593
-rw-r--r--utils/defs.hpp.in57
-rw-r--r--utils/env.cpp200
-rw-r--r--utils/env.hpp58
-rw-r--r--utils/env_test.cpp167
-rw-r--r--utils/format/Kyuafile7
-rw-r--r--utils/format/Makefile.am.inc59
-rw-r--r--utils/format/containers.hpp66
-rw-r--r--utils/format/containers.ipp138
-rw-r--r--utils/format/containers_test.cpp190
-rw-r--r--utils/format/exceptions.cpp110
-rw-r--r--utils/format/exceptions.hpp84
-rw-r--r--utils/format/exceptions_test.cpp74
-rw-r--r--utils/format/formatter.cpp293
-rw-r--r--utils/format/formatter.hpp123
-rw-r--r--utils/format/formatter.ipp76
-rw-r--r--utils/format/formatter_fwd.hpp45
-rw-r--r--utils/format/formatter_test.cpp265
-rw-r--r--utils/format/macros.hpp58
-rw-r--r--utils/fs/Kyuafile10
-rw-r--r--utils/fs/Makefile.am.inc84
-rw-r--r--utils/fs/auto_cleaners.cpp261
-rw-r--r--utils/fs/auto_cleaners.hpp89
-rw-r--r--utils/fs/auto_cleaners_fwd.hpp46
-rw-r--r--utils/fs/auto_cleaners_test.cpp167
-rw-r--r--utils/fs/directory.cpp360
-rw-r--r--utils/fs/directory.hpp120
-rw-r--r--utils/fs/directory_fwd.hpp55
-rw-r--r--utils/fs/directory_test.cpp190
-rw-r--r--utils/fs/exceptions.cpp162
-rw-r--r--utils/fs/exceptions.hpp110
-rw-r--r--utils/fs/exceptions_test.cpp95
-rw-r--r--utils/fs/lua_module.cpp340
-rw-r--r--utils/fs/lua_module.hpp54
-rw-r--r--utils/fs/lua_module_test.cpp376
-rw-r--r--utils/fs/operations.cpp803
-rw-r--r--utils/fs/operations.hpp72
-rw-r--r--utils/fs/operations_test.cpp826
-rw-r--r--utils/fs/path.cpp303
-rw-r--r--utils/fs/path.hpp87
-rw-r--r--utils/fs/path_fwd.hpp45
-rw-r--r--utils/fs/path_test.cpp277
-rw-r--r--utils/logging/Kyuafile6
-rw-r--r--utils/logging/Makefile.am.inc53
-rw-r--r--utils/logging/macros.hpp68
-rw-r--r--utils/logging/macros_test.cpp115
-rw-r--r--utils/logging/operations.cpp303
-rw-r--r--utils/logging/operations.hpp54
-rw-r--r--utils/logging/operations_fwd.hpp54
-rw-r--r--utils/logging/operations_test.cpp354
-rw-r--r--utils/memory.cpp158
-rw-r--r--utils/memory.hpp45
-rw-r--r--utils/memory_test.cpp63
-rw-r--r--utils/noncopyable.hpp75
-rw-r--r--utils/optional.hpp90
-rw-r--r--utils/optional.ipp252
-rw-r--r--utils/optional_fwd.hpp61
-rw-r--r--utils/optional_test.cpp285
-rw-r--r--utils/passwd.cpp194
-rw-r--r--utils/passwd.hpp72
-rw-r--r--utils/passwd_fwd.hpp45
-rw-r--r--utils/passwd_test.cpp179
-rw-r--r--utils/process/.gitignore1
-rw-r--r--utils/process/Kyuafile13
-rw-r--r--utils/process/Makefile.am.inc113
-rw-r--r--utils/process/child.cpp385
-rw-r--r--utils/process/child.hpp113
-rw-r--r--utils/process/child.ipp110
-rw-r--r--utils/process/child_fwd.hpp45
-rw-r--r--utils/process/child_test.cpp846
-rw-r--r--utils/process/deadline_killer.cpp54
-rw-r--r--utils/process/deadline_killer.hpp58
-rw-r--r--utils/process/deadline_killer_fwd.hpp45
-rw-r--r--utils/process/deadline_killer_test.cpp108
-rw-r--r--utils/process/exceptions.cpp91
-rw-r--r--utils/process/exceptions.hpp78
-rw-r--r--utils/process/exceptions_test.cpp63
-rw-r--r--utils/process/executor.cpp869
-rw-r--r--utils/process/executor.hpp231
-rw-r--r--utils/process/executor.ipp182
-rw-r--r--utils/process/executor_fwd.hpp49
-rw-r--r--utils/process/executor_test.cpp940
-rw-r--r--utils/process/fdstream.cpp76
-rw-r--r--utils/process/fdstream.hpp66
-rw-r--r--utils/process/fdstream_fwd.hpp45
-rw-r--r--utils/process/fdstream_test.cpp73
-rw-r--r--utils/process/helpers.cpp74
-rw-r--r--utils/process/isolation.cpp207
-rw-r--r--utils/process/isolation.hpp60
-rw-r--r--utils/process/isolation_test.cpp622
-rw-r--r--utils/process/operations.cpp273
-rw-r--r--utils/process/operations.hpp56
-rw-r--r--utils/process/operations_fwd.hpp49
-rw-r--r--utils/process/operations_test.cpp471
-rw-r--r--utils/process/status.cpp200
-rw-r--r--utils/process/status.hpp84
-rw-r--r--utils/process/status_fwd.hpp45
-rw-r--r--utils/process/status_test.cpp209
-rw-r--r--utils/process/system.cpp59
-rw-r--r--utils/process/system.hpp66
-rw-r--r--utils/process/systembuf.cpp152
-rw-r--r--utils/process/systembuf.hpp71
-rw-r--r--utils/process/systembuf_fwd.hpp45
-rw-r--r--utils/process/systembuf_test.cpp166
-rw-r--r--utils/sanity.cpp194
-rw-r--r--utils/sanity.hpp183
-rw-r--r--utils/sanity_fwd.hpp52
-rw-r--r--utils/sanity_test.cpp322
-rw-r--r--utils/signals/Kyuafile9
-rw-r--r--utils/signals/Makefile.am.inc73
-rw-r--r--utils/signals/exceptions.cpp102
-rw-r--r--utils/signals/exceptions.hpp83
-rw-r--r--utils/signals/exceptions_test.cpp73
-rw-r--r--utils/signals/interrupts.cpp309
-rw-r--r--utils/signals/interrupts.hpp83
-rw-r--r--utils/signals/interrupts_fwd.hpp46
-rw-r--r--utils/signals/interrupts_test.cpp266
-rw-r--r--utils/signals/misc.cpp106
-rw-r--r--utils/signals/misc.hpp49
-rw-r--r--utils/signals/misc_test.cpp133
-rw-r--r--utils/signals/programmer.cpp138
-rw-r--r--utils/signals/programmer.hpp63
-rw-r--r--utils/signals/programmer_fwd.hpp49
-rw-r--r--utils/signals/programmer_test.cpp140
-rw-r--r--utils/signals/timer.cpp547
-rw-r--r--utils/signals/timer.hpp86
-rw-r--r--utils/signals/timer_fwd.hpp45
-rw-r--r--utils/signals/timer_test.cpp426
-rw-r--r--utils/sqlite/Kyuafile9
-rw-r--r--utils/sqlite/Makefile.am.inc82
-rw-r--r--utils/sqlite/c_gate.cpp83
-rw-r--r--utils/sqlite/c_gate.hpp74
-rw-r--r--utils/sqlite/c_gate_fwd.hpp45
-rw-r--r--utils/sqlite/c_gate_test.cpp96
-rw-r--r--utils/sqlite/database.cpp328
-rw-r--r--utils/sqlite/database.hpp111
-rw-r--r--utils/sqlite/database_fwd.hpp45
-rw-r--r--utils/sqlite/database_test.cpp287
-rw-r--r--utils/sqlite/exceptions.cpp175
-rw-r--r--utils/sqlite/exceptions.hpp94
-rw-r--r--utils/sqlite/exceptions_test.cpp129
-rw-r--r--utils/sqlite/statement.cpp621
-rw-r--r--utils/sqlite/statement.hpp137
-rw-r--r--utils/sqlite/statement.ipp52
-rw-r--r--utils/sqlite/statement_fwd.hpp57
-rw-r--r--utils/sqlite/statement_test.cpp784
-rw-r--r--utils/sqlite/test_utils.hpp151
-rw-r--r--utils/sqlite/transaction.cpp142
-rw-r--r--utils/sqlite/transaction.hpp69
-rw-r--r--utils/sqlite/transaction_fwd.hpp45
-rw-r--r--utils/sqlite/transaction_test.cpp135
-rw-r--r--utils/stacktrace.cpp370
-rw-r--r--utils/stacktrace.hpp68
-rw-r--r--utils/stacktrace_helper.cpp36
-rw-r--r--utils/stacktrace_test.cpp620
-rw-r--r--utils/stream.cpp149
-rw-r--r--utils/stream.hpp57
-rw-r--r--utils/stream_test.cpp157
-rw-r--r--utils/test_utils.ipp113
-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
-rw-r--r--utils/units.cpp172
-rw-r--r--utils/units.hpp96
-rw-r--r--utils/units_fwd.hpp45
-rw-r--r--utils/units_test.cpp248
252 files changed, 45147 insertions, 0 deletions
diff --git a/utils/.gitignore b/utils/.gitignore
new file mode 100644
index 000000000000..b33d720f27a4
--- /dev/null
+++ b/utils/.gitignore
@@ -0,0 +1,2 @@
+defs.hpp
+stacktrace_helper
diff --git a/utils/Kyuafile b/utils/Kyuafile
new file mode 100644
index 000000000000..042ad77a3fe4
--- /dev/null
+++ b/utils/Kyuafile
@@ -0,0 +1,24 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="auto_array_test"}
+atf_test_program{name="datetime_test"}
+atf_test_program{name="env_test"}
+atf_test_program{name="memory_test"}
+atf_test_program{name="optional_test"}
+atf_test_program{name="passwd_test"}
+atf_test_program{name="sanity_test"}
+atf_test_program{name="stacktrace_test"}
+atf_test_program{name="stream_test"}
+atf_test_program{name="units_test"}
+
+include("cmdline/Kyuafile")
+include("config/Kyuafile")
+include("format/Kyuafile")
+include("fs/Kyuafile")
+include("logging/Kyuafile")
+include("process/Kyuafile")
+include("signals/Kyuafile")
+include("sqlite/Kyuafile")
+include("text/Kyuafile")
diff --git a/utils/Makefile.am.inc b/utils/Makefile.am.inc
new file mode 100644
index 000000000000..d6690bdbecde
--- /dev/null
+++ b/utils/Makefile.am.inc
@@ -0,0 +1,133 @@
+# Copyright 2010 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.
+
+UTILS_CFLAGS =
+UTILS_LIBS = libutils.a
+
+noinst_LIBRARIES += libutils.a
+libutils_a_CPPFLAGS = -DGDB=\"$(GDB)\"
+libutils_a_SOURCES = utils/auto_array.hpp
+libutils_a_SOURCES += utils/auto_array.ipp
+libutils_a_SOURCES += utils/auto_array_fwd.hpp
+libutils_a_SOURCES += utils/datetime.cpp
+libutils_a_SOURCES += utils/datetime.hpp
+libutils_a_SOURCES += utils/datetime_fwd.hpp
+libutils_a_SOURCES += utils/env.hpp
+libutils_a_SOURCES += utils/env.cpp
+libutils_a_SOURCES += utils/memory.hpp
+libutils_a_SOURCES += utils/memory.cpp
+libutils_a_SOURCES += utils/noncopyable.hpp
+libutils_a_SOURCES += utils/optional.hpp
+libutils_a_SOURCES += utils/optional_fwd.hpp
+libutils_a_SOURCES += utils/optional.ipp
+libutils_a_SOURCES += utils/passwd.cpp
+libutils_a_SOURCES += utils/passwd.hpp
+libutils_a_SOURCES += utils/passwd_fwd.hpp
+libutils_a_SOURCES += utils/sanity.cpp
+libutils_a_SOURCES += utils/sanity.hpp
+libutils_a_SOURCES += utils/sanity_fwd.hpp
+libutils_a_SOURCES += utils/stacktrace.cpp
+libutils_a_SOURCES += utils/stacktrace.hpp
+libutils_a_SOURCES += utils/stream.cpp
+libutils_a_SOURCES += utils/stream.hpp
+libutils_a_SOURCES += utils/units.cpp
+libutils_a_SOURCES += utils/units.hpp
+libutils_a_SOURCES += utils/units_fwd.hpp
+nodist_libutils_a_SOURCES = utils/defs.hpp
+
+EXTRA_DIST += utils/test_utils.ipp
+
+if WITH_ATF
+tests_utilsdir = $(pkgtestsdir)/utils
+
+tests_utils_DATA = utils/Kyuafile
+EXTRA_DIST += $(tests_utils_DATA)
+
+tests_utils_PROGRAMS = utils/auto_array_test
+utils_auto_array_test_SOURCES = utils/auto_array_test.cpp
+utils_auto_array_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_auto_array_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/datetime_test
+utils_datetime_test_SOURCES = utils/datetime_test.cpp
+utils_datetime_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_datetime_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/env_test
+utils_env_test_SOURCES = utils/env_test.cpp
+utils_env_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_env_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/memory_test
+utils_memory_test_SOURCES = utils/memory_test.cpp
+utils_memory_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_memory_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/optional_test
+utils_optional_test_SOURCES = utils/optional_test.cpp
+utils_optional_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_optional_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/passwd_test
+utils_passwd_test_SOURCES = utils/passwd_test.cpp
+utils_passwd_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_passwd_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/sanity_test
+utils_sanity_test_SOURCES = utils/sanity_test.cpp
+utils_sanity_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_sanity_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/stacktrace_helper
+utils_stacktrace_helper_SOURCES = utils/stacktrace_helper.cpp
+
+tests_utils_PROGRAMS += utils/stacktrace_test
+utils_stacktrace_test_SOURCES = utils/stacktrace_test.cpp
+utils_stacktrace_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_stacktrace_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/stream_test
+utils_stream_test_SOURCES = utils/stream_test.cpp
+utils_stream_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_stream_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_PROGRAMS += utils/units_test
+utils_units_test_SOURCES = utils/units_test.cpp
+utils_units_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_units_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
+
+include utils/cmdline/Makefile.am.inc
+include utils/config/Makefile.am.inc
+include utils/format/Makefile.am.inc
+include utils/fs/Makefile.am.inc
+include utils/logging/Makefile.am.inc
+include utils/process/Makefile.am.inc
+include utils/signals/Makefile.am.inc
+include utils/sqlite/Makefile.am.inc
+include utils/text/Makefile.am.inc
diff --git a/utils/auto_array.hpp b/utils/auto_array.hpp
new file mode 100644
index 000000000000..0cc3d0e0afd5
--- /dev/null
+++ b/utils/auto_array.hpp
@@ -0,0 +1,102 @@
+// Copyright 2010 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/auto_array.hpp
+/// Provides the utils::auto_array class.
+///
+/// The class is provided as a separate module on its own to minimize
+/// header-inclusion side-effects.
+
+#if !defined(UTILS_AUTO_ARRAY_HPP)
+#define UTILS_AUTO_ARRAY_HPP
+
+#include "utils/auto_array_fwd.hpp"
+
+#include <cstddef>
+
+namespace utils {
+
+
+namespace detail {
+
+
+/// Wrapper class to provide reference semantics for utils::auto_array.
+///
+/// This class is internally used, for example, to allow returning a
+/// utils::auto_array from a function.
+template< class T >
+class auto_array_ref {
+ /// Internal pointer to the dynamically-allocated array.
+ T* _ptr;
+
+ template< class > friend class utils::auto_array;
+
+public:
+ explicit auto_array_ref(T*);
+};
+
+
+} // namespace detail
+
+
+/// A simple smart pointer for arrays providing strict ownership semantics.
+///
+/// This class is the counterpart of std::auto_ptr for arrays. The semantics of
+/// the API of this class are the same as those of std::auto_ptr.
+///
+/// The wrapped pointer must be NULL or must have been allocated using operator
+/// new[].
+template< class T >
+class auto_array {
+ /// Internal pointer to the dynamically-allocated array.
+ T* _ptr;
+
+public:
+ auto_array(T* = NULL) throw();
+ auto_array(auto_array< T >&) throw();
+ auto_array(detail::auto_array_ref< T >) throw();
+ ~auto_array(void) throw();
+
+ T* get(void) throw();
+ const T* get(void) const throw();
+
+ T* release(void) throw();
+ void reset(T* = NULL) throw();
+
+ auto_array< T >& operator=(auto_array< T >&) throw();
+ auto_array< T >& operator=(detail::auto_array_ref< T >) throw();
+ T& operator[](int) throw();
+ const T& operator[](int) const throw();
+ operator detail::auto_array_ref< T >(void) throw();
+};
+
+
+} // namespace utils
+
+
+#endif // !defined(UTILS_AUTO_ARRAY_HPP)
diff --git a/utils/auto_array.ipp b/utils/auto_array.ipp
new file mode 100644
index 000000000000..fd29311def8c
--- /dev/null
+++ b/utils/auto_array.ipp
@@ -0,0 +1,227 @@
+// Copyright 2010 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_AUTO_ARRAY_IPP)
+#define UTILS_AUTO_ARRAY_IPP
+
+#include "utils/auto_array.hpp"
+
+namespace utils {
+
+
+namespace detail {
+
+
+/// Constructs a new auto_array_ref from a pointer.
+///
+/// \param ptr The pointer to wrap.
+template< class T > inline
+auto_array_ref< T >::auto_array_ref(T* ptr) :
+ _ptr(ptr)
+{
+}
+
+
+} // namespace detail
+
+
+/// Constructs a new auto_array from a given pointer.
+///
+/// This grabs ownership of the pointer unless it is NULL.
+///
+/// \param ptr The pointer to wrap. If not NULL, the memory pointed to must
+/// have been allocated with operator new[].
+template< class T > inline
+auto_array< T >::auto_array(T* ptr) throw() :
+ _ptr(ptr)
+{
+}
+
+
+/// Constructs a copy of an auto_array.
+///
+/// \param ptr The pointer to copy from. This pointer is invalidated and the
+/// new copy grabs ownership of the object pointed to.
+template< class T > inline
+auto_array< T >::auto_array(auto_array< T >& ptr) throw() :
+ _ptr(ptr.release())
+{
+}
+
+
+/// Constructs a new auto_array form a reference.
+///
+/// Internal function used to construct a new auto_array from an object
+/// returned, for example, from a function.
+///
+/// \param ref The reference.
+template< class T > inline
+auto_array< T >::auto_array(detail::auto_array_ref< T > ref) throw() :
+ _ptr(ref._ptr)
+{
+}
+
+
+/// Destructor for auto_array objects.
+template< class T > inline
+auto_array< T >::~auto_array(void) throw()
+{
+ if (_ptr != NULL)
+ delete [] _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer without releasing ownership.
+///
+/// \return The raw mutable pointer.
+template< class T > inline
+T*
+auto_array< T >::get(void) throw()
+{
+ return _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer without releasing ownership.
+///
+/// \return The raw immutable pointer.
+template< class T > inline
+const T*
+auto_array< T >::get(void) const throw()
+{
+ return _ptr;
+}
+
+
+/// Gets the value of the wrapped pointer and releases ownership.
+///
+/// \return The raw mutable pointer.
+template< class T > inline
+T*
+auto_array< T >::release(void) throw()
+{
+ T* ptr = _ptr;
+ _ptr = NULL;
+ return ptr;
+}
+
+
+/// Changes the value of the wrapped pointer.
+///
+/// If the auto_array was pointing to an array, such array is released and the
+/// wrapped pointer is replaced with the new pointer provided.
+///
+/// \param ptr The pointer to use as a replacement; may be NULL.
+template< class T > inline
+void
+auto_array< T >::reset(T* ptr) throw()
+{
+ if (_ptr != NULL)
+ delete [] _ptr;
+ _ptr = ptr;
+}
+
+
+/// Assignment operator.
+///
+/// \param ptr The object to copy from. This is invalidated after the copy.
+/// \return A reference to the auto_array object itself.
+template< class T > inline
+auto_array< T >&
+auto_array< T >::operator=(auto_array< T >& ptr) throw()
+{
+ reset(ptr.release());
+ return *this;
+}
+
+
+/// Internal assignment operator for function returns.
+///
+/// \param ref The reference object to copy from.
+/// \return A reference to the auto_array object itself.
+template< class T > inline
+auto_array< T >&
+auto_array< T >::operator=(detail::auto_array_ref< T > ref) throw()
+{
+ if (_ptr != ref._ptr) {
+ delete [] _ptr;
+ _ptr = ref._ptr;
+ }
+ return *this;
+}
+
+
+/// Subscript operator to access the array by position.
+///
+/// This does not perform any bounds checking, in particular because auto_array
+/// does not know the size of the arrays pointed to by it.
+///
+/// \param pos The position to access, indexed from zero.
+///
+/// \return A mutable reference to the element at the specified position.
+template< class T > inline
+T&
+auto_array< T >::operator[](int pos) throw()
+{
+ return _ptr[pos];
+}
+
+
+/// Subscript operator to access the array by position.
+///
+/// This does not perform any bounds checking, in particular because auto_array
+/// does not know the size of the arrays pointed to by it.
+///
+/// \param pos The position to access, indexed from zero.
+///
+/// \return An immutable reference to the element at the specified position.
+template< class T > inline
+const T&
+auto_array< T >::operator[](int pos) const throw()
+{
+ return _ptr[pos];
+}
+
+
+/// Internal conversion to a reference wrapper.
+///
+/// This is used internally to support returning auto_array objects from
+/// functions. The auto_array is invalidated when used.
+///
+/// \return A new detail::auto_array_ref object holding the pointer.
+template< class T > inline
+auto_array< T >::operator detail::auto_array_ref< T >(void) throw()
+{
+ return detail::auto_array_ref< T >(release());
+}
+
+
+} // namespace utils
+
+
+#endif // !defined(UTILS_AUTO_ARRAY_IPP)
diff --git a/utils/auto_array_fwd.hpp b/utils/auto_array_fwd.hpp
new file mode 100644
index 000000000000..e1522a25bf7d
--- /dev/null
+++ b/utils/auto_array_fwd.hpp
@@ -0,0 +1,43 @@
+// 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/auto_array_fwd.hpp
+/// Forward declarations for utils/auto_array.hpp
+
+#if !defined(UTILS_AUTO_ARRAY_FWD_HPP)
+#define UTILS_AUTO_ARRAY_FWD_HPP
+
+namespace utils {
+
+
+template< class > class auto_array;
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_AUTO_ARRAY_FWD_HPP)
diff --git a/utils/auto_array_test.cpp b/utils/auto_array_test.cpp
new file mode 100644
index 000000000000..041eb65863ba
--- /dev/null
+++ b/utils/auto_array_test.cpp
@@ -0,0 +1,312 @@
+// Copyright 2010 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/auto_array.ipp"
+
+extern "C" {
+#include <sys/types.h>
+}
+
+#include <iostream>
+
+#include <atf-c++.hpp>
+
+#include "utils/defs.hpp"
+
+using utils::auto_array;
+
+
+namespace {
+
+
+/// Mock class to capture calls to the new and delete operators.
+class test_array {
+public:
+ /// User-settable cookie to disambiguate instances of this class.
+ int m_value;
+
+ /// The current balance of existing test_array instances.
+ static ssize_t m_nblocks;
+
+ /// Captures invalid calls to new on an array.
+ ///
+ /// \return Nothing; this always fails the test case.
+ void*
+ operator new(const size_t /* size */)
+ {
+ ATF_FAIL("New called but should have been new[]");
+ return new int(5);
+ }
+
+ /// Obtains memory for a new instance and increments m_nblocks.
+ ///
+ /// \param size The amount of memory to allocate, in bytes.
+ ///
+ /// \return A pointer to the allocated memory.
+ ///
+ /// \throw std::bad_alloc If the memory cannot be allocated.
+ void*
+ operator new[](const size_t size)
+ {
+ void* mem = ::operator new(size);
+ m_nblocks++;
+ std::cout << "Allocated 'test_array' object " << mem << "\n";
+ return mem;
+ }
+
+ /// Captures invalid calls to delete on an array.
+ ///
+ /// \return Nothing; this always fails the test case.
+ void
+ operator delete(void* /* mem */)
+ {
+ ATF_FAIL("Delete called but should have been delete[]");
+ }
+
+ /// Deletes a previously allocated array and decrements m_nblocks.
+ ///
+ /// \param mem The pointer to the memory to be deleted.
+ void
+ operator delete[](void* mem)
+ {
+ std::cout << "Releasing 'test_array' object " << mem << "\n";
+ if (m_nblocks == 0)
+ ATF_FAIL("Unbalanced delete[]");
+ m_nblocks--;
+ ::operator delete(mem);
+ }
+};
+
+
+ssize_t test_array::m_nblocks = 0;
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE(scope);
+ATF_TEST_CASE_HEAD(scope)
+{
+ set_md_var("descr", "Tests the automatic scope handling in the "
+ "auto_array smart pointer class");
+}
+ATF_TEST_CASE_BODY(scope)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(copy);
+ATF_TEST_CASE_HEAD(copy)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor");
+}
+ATF_TEST_CASE_BODY(copy)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2(t1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(copy_ref);
+ATF_TEST_CASE_HEAD(copy_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' copy "
+ "constructor through the auxiliary ref object");
+}
+ATF_TEST_CASE_BODY(copy_ref)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(get);
+ATF_TEST_CASE_HEAD(get)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' get "
+ "method");
+}
+ATF_TEST_CASE_BODY(get)
+{
+ test_array* ta = new test_array[10];
+ auto_array< test_array > t(ta);
+ ATF_REQUIRE_EQ(t.get(), ta);
+}
+
+
+ATF_TEST_CASE(release);
+ATF_TEST_CASE_HEAD(release)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' release "
+ "method");
+}
+ATF_TEST_CASE_BODY(release)
+{
+ test_array* ta1 = new test_array[10];
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ test_array* ta2 = t.release();
+ ATF_REQUIRE_EQ(ta2, ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ delete [] ta1;
+}
+
+
+ATF_TEST_CASE(reset);
+ATF_TEST_CASE_HEAD(reset)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' reset "
+ "method");
+}
+ATF_TEST_CASE_BODY(reset)
+{
+ test_array* ta1 = new test_array[10];
+ test_array* ta2 = new test_array[10];
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+
+ {
+ auto_array< test_array > t(ta1);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 2);
+ t.reset(ta2);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ t.reset();
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(assign);
+ATF_TEST_CASE_HEAD(assign)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator");
+}
+ATF_TEST_CASE_BODY(assign)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(assign_ref);
+ATF_TEST_CASE_HEAD(assign_ref)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' "
+ "assignment operator through the auxiliary ref "
+ "object");
+}
+ATF_TEST_CASE_BODY(assign_ref)
+{
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ {
+ auto_array< test_array > t1(new test_array[10]);
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+
+ {
+ auto_array< test_array > t2;
+ t2 = t1;
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 1);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+ }
+ ATF_REQUIRE_EQ(test_array::m_nblocks, 0);
+}
+
+
+ATF_TEST_CASE(access);
+ATF_TEST_CASE_HEAD(access)
+{
+ set_md_var("descr", "Tests the auto_array smart pointer class' access "
+ "operator");
+}
+ATF_TEST_CASE_BODY(access)
+{
+ auto_array< test_array > t(new test_array[10]);
+
+ for (int i = 0; i < 10; i++)
+ t[i].m_value = i * 2;
+
+ for (int i = 0; i < 10; i++)
+ ATF_REQUIRE_EQ(t[i].m_value, i * 2);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, scope);
+ ATF_ADD_TEST_CASE(tcs, copy);
+ ATF_ADD_TEST_CASE(tcs, copy_ref);
+ ATF_ADD_TEST_CASE(tcs, get);
+ ATF_ADD_TEST_CASE(tcs, release);
+ ATF_ADD_TEST_CASE(tcs, reset);
+ ATF_ADD_TEST_CASE(tcs, assign);
+ ATF_ADD_TEST_CASE(tcs, assign_ref);
+ ATF_ADD_TEST_CASE(tcs, access);
+}
diff --git a/utils/cmdline/Kyuafile b/utils/cmdline/Kyuafile
new file mode 100644
index 000000000000..d5e6f7122b07
--- /dev/null
+++ b/utils/cmdline/Kyuafile
@@ -0,0 +1,11 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="base_command_test"}
+atf_test_program{name="commands_map_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="globals_test"}
+atf_test_program{name="options_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="ui_test"}
diff --git a/utils/cmdline/Makefile.am.inc b/utils/cmdline/Makefile.am.inc
new file mode 100644
index 000000000000..65081cbeafee
--- /dev/null
+++ b/utils/cmdline/Makefile.am.inc
@@ -0,0 +1,96 @@
+# Copyright 2010 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/cmdline/base_command.cpp
+libutils_a_SOURCES += utils/cmdline/base_command.hpp
+libutils_a_SOURCES += utils/cmdline/base_command_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/base_command.ipp
+libutils_a_SOURCES += utils/cmdline/commands_map.hpp
+libutils_a_SOURCES += utils/cmdline/commands_map_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/commands_map.ipp
+libutils_a_SOURCES += utils/cmdline/exceptions.cpp
+libutils_a_SOURCES += utils/cmdline/exceptions.hpp
+libutils_a_SOURCES += utils/cmdline/globals.cpp
+libutils_a_SOURCES += utils/cmdline/globals.hpp
+libutils_a_SOURCES += utils/cmdline/options.cpp
+libutils_a_SOURCES += utils/cmdline/options.hpp
+libutils_a_SOURCES += utils/cmdline/options_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/parser.cpp
+libutils_a_SOURCES += utils/cmdline/parser.hpp
+libutils_a_SOURCES += utils/cmdline/parser_fwd.hpp
+libutils_a_SOURCES += utils/cmdline/parser.ipp
+libutils_a_SOURCES += utils/cmdline/ui.cpp
+libutils_a_SOURCES += utils/cmdline/ui.hpp
+libutils_a_SOURCES += utils/cmdline/ui_fwd.hpp
+# The following two files are only supposed to be used from test code. They
+# should not be bundled into libutils.a, but doing so simplifies the build
+# significantly.
+libutils_a_SOURCES += utils/cmdline/ui_mock.hpp
+libutils_a_SOURCES += utils/cmdline/ui_mock.cpp
+
+if WITH_ATF
+tests_utils_cmdlinedir = $(pkgtestsdir)/utils/cmdline
+
+tests_utils_cmdline_DATA = utils/cmdline/Kyuafile
+EXTRA_DIST += $(tests_utils_cmdline_DATA)
+
+tests_utils_cmdline_PROGRAMS = utils/cmdline/base_command_test
+utils_cmdline_base_command_test_SOURCES = utils/cmdline/base_command_test.cpp
+utils_cmdline_base_command_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_base_command_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/commands_map_test
+utils_cmdline_commands_map_test_SOURCES = utils/cmdline/commands_map_test.cpp
+utils_cmdline_commands_map_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_commands_map_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/exceptions_test
+utils_cmdline_exceptions_test_SOURCES = utils/cmdline/exceptions_test.cpp
+utils_cmdline_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/globals_test
+utils_cmdline_globals_test_SOURCES = utils/cmdline/globals_test.cpp
+utils_cmdline_globals_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_globals_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/options_test
+utils_cmdline_options_test_SOURCES = utils/cmdline/options_test.cpp
+utils_cmdline_options_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_options_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/parser_test
+utils_cmdline_parser_test_SOURCES = utils/cmdline/parser_test.cpp
+utils_cmdline_parser_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_parser_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_cmdline_PROGRAMS += utils/cmdline/ui_test
+utils_cmdline_ui_test_SOURCES = utils/cmdline/ui_test.cpp
+utils_cmdline_ui_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_cmdline_ui_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/cmdline/base_command.cpp b/utils/cmdline/base_command.cpp
new file mode 100644
index 000000000000..837ded9cffab
--- /dev/null
+++ b/utils/cmdline/base_command.cpp
@@ -0,0 +1,201 @@
+// Copyright 2010 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/cmdline/base_command.hpp"
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/cmdline/parser.ipp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+cmdline::command_proto::command_proto(const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ _name(name_),
+ _arg_list(arg_list_),
+ _min_args(min_args_),
+ _max_args(max_args_),
+ _short_description(short_description_)
+{
+ PRE(name_.find(' ') == std::string::npos);
+ PRE(max_args_ == -1 || min_args_ <= max_args_);
+}
+
+
+/// Destructor for a command.
+cmdline::command_proto::~command_proto(void)
+{
+ for (options_vector::const_iterator iter = _options.begin();
+ iter != _options.end(); iter++)
+ delete *iter;
+}
+
+
+/// Internal method to register a dynamically-allocated option.
+///
+/// Always use add_option() from subclasses to add options.
+///
+/// \param option_ The option to add. Must have been dynamically allocated.
+/// This grabs ownership of the pointer, which is released when the command
+/// is destroyed.
+void
+cmdline::command_proto::add_option_ptr(const cmdline::base_option* option_)
+{
+ try {
+ _options.push_back(option_);
+ } catch (...) {
+ delete option_;
+ throw;
+ }
+}
+
+
+/// Processes the command line based on the command description.
+///
+/// \param args The raw command line to be processed.
+///
+/// \return An object containing the list of options and free arguments found in
+/// args.
+///
+/// \throw cmdline::usage_error If there is a problem processing the command
+/// line. This error is caused by invalid input from the user.
+cmdline::parsed_cmdline
+cmdline::command_proto::parse_cmdline(const cmdline::args_vector& args) const
+{
+ PRE(name() == args[0]);
+ const parsed_cmdline cmdline = cmdline::parse(args, options());
+
+ const int argc = cmdline.arguments().size();
+ if (argc < _min_args)
+ throw usage_error("Not enough arguments");
+ if (_max_args != -1 && argc > _max_args)
+ throw usage_error("Too many arguments");
+
+ return cmdline;
+}
+
+
+/// Gets the name of the command.
+///
+/// \return The command name.
+const std::string&
+cmdline::command_proto::name(void) const
+{
+ return _name;
+}
+
+
+/// Gets the textual representation of the arguments list.
+///
+/// \return The description of the arguments list.
+const std::string&
+cmdline::command_proto::arg_list(void) const
+{
+ return _arg_list;
+}
+
+
+/// Gets the description of the purpose of the command.
+///
+/// \return The description of the command.
+const std::string&
+cmdline::command_proto::short_description(void) const
+{
+ return _short_description;
+}
+
+
+/// Gets the definition of the options accepted by the command.
+///
+/// \return The list of options.
+const cmdline::options_vector&
+cmdline::command_proto::options(void) const
+{
+ return _options;
+}
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+cmdline::base_command_no_data::base_command_no_data(
+ const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ command_proto(name_, arg_list_, min_args_, max_args_, short_description_)
+{
+}
+
+
+/// Entry point for the command.
+///
+/// This delegates execution to the run() abstract function after the command
+/// line provided in args has been parsed.
+///
+/// If this function returns, the command is assumed to have been executed
+/// successfully. Any error must be reported by means of exceptions.
+///
+/// \param ui Object to interact with the I/O of the command. The command must
+/// always use this object to write to stdout and stderr.
+/// \param args The command line passed to the command broken by word, which
+/// includes options and arguments.
+///
+/// \return The exit code that the program has to return. 0 on success, some
+/// other value on error.
+/// \throw usage_error If args is invalid (i.e. if the options are mispecified
+/// or if the arguments are invalid).
+int
+cmdline::base_command_no_data::main(cmdline::ui* ui,
+ const cmdline::args_vector& args)
+{
+ return run(ui, parse_cmdline(args));
+}
diff --git a/utils/cmdline/base_command.hpp b/utils/cmdline/base_command.hpp
new file mode 100644
index 000000000000..819dfe98dad3
--- /dev/null
+++ b/utils/cmdline/base_command.hpp
@@ -0,0 +1,162 @@
+// Copyright 2010 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/cmdline/base_command.hpp
+/// Provides the utils::cmdline::base_command class.
+
+#if !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
+#define UTILS_CMDLINE_BASE_COMMAND_HPP
+
+#include "utils/cmdline/base_command_fwd.hpp"
+
+#include <string>
+
+#include "utils/cmdline/options_fwd.hpp"
+#include "utils/cmdline/parser_fwd.hpp"
+#include "utils/cmdline/ui_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Prototype class for the implementation of subcommands of a program.
+///
+/// Use the subclasses of command_proto defined in this module instead of
+/// command_proto itself as base classes for your application-specific
+/// commands.
+class command_proto : noncopyable {
+ /// The user-visible name of the command.
+ const std::string _name;
+
+ /// Textual description of the command arguments.
+ const std::string _arg_list;
+
+ /// The minimum number of required arguments.
+ const int _min_args;
+
+ /// The maximum number of allowed arguments; -1 for infinity.
+ const int _max_args;
+
+ /// A textual description of the command.
+ const std::string _short_description;
+
+ /// Collection of command-specific options.
+ options_vector _options;
+
+ void add_option_ptr(const base_option*);
+
+protected:
+ template< typename Option > void add_option(const Option&);
+ parsed_cmdline parse_cmdline(const args_vector&) const;
+
+public:
+ command_proto(const std::string&, const std::string&, const int, const int,
+ const std::string&);
+ virtual ~command_proto(void);
+
+ const std::string& name(void) const;
+ const std::string& arg_list(void) const;
+ const std::string& short_description(void) const;
+ const options_vector& options(void) const;
+};
+
+
+/// Unparametrized base subcommand for a program.
+///
+/// Use this class to define subcommands for your program that do not need any
+/// information passed in from the main command-line dispatcher other than the
+/// command-line arguments.
+class base_command_no_data : public command_proto {
+ /// Main code of the command.
+ ///
+ /// This is called from main() after the command line has been processed and
+ /// validated.
+ ///
+ /// \param ui Object to interact with the I/O of the command. The command
+ /// must always use this object to write to stdout and stderr.
+ /// \param cmdline The parsed command line, containing the values of any
+ /// given options and arguments.
+ ///
+ /// \return The exit code that the program has to return. 0 on success,
+ /// some other value on error.
+ ///
+ /// \throw std::runtime_error Any errors detected during the execution of
+ /// the command are reported by means of exceptions.
+ virtual int run(ui* ui, const parsed_cmdline& cmdline) = 0;
+
+public:
+ base_command_no_data(const std::string&, const std::string&, const int,
+ const int, const std::string&);
+
+ int main(ui*, const args_vector&);
+};
+
+
+/// Parametrized base subcommand for a program.
+///
+/// Use this class to define subcommands for your program that need some kind of
+/// runtime information passed in from the main command-line dispatcher.
+///
+/// \param Data The type of the object passed to the subcommand at runtime.
+/// This is useful, for example, to pass around the runtime configuration of the
+/// program.
+template< typename Data >
+class base_command : public command_proto {
+ /// Main code of the command.
+ ///
+ /// This is called from main() after the command line has been processed and
+ /// validated.
+ ///
+ /// \param ui Object to interact with the I/O of the command. The command
+ /// must always use this object to write to stdout and stderr.
+ /// \param cmdline The parsed command line, containing the values of any
+ /// given options and arguments.
+ /// \param data An instance of the runtime data passed from main().
+ ///
+ /// \return The exit code that the program has to return. 0 on success,
+ /// some other value on error.
+ ///
+ /// \throw std::runtime_error Any errors detected during the execution of
+ /// the command are reported by means of exceptions.
+ virtual int run(ui* ui, const parsed_cmdline& cmdline,
+ const Data& data) = 0;
+
+public:
+ base_command(const std::string&, const std::string&, const int, const int,
+ const std::string&);
+
+ int main(ui*, const args_vector&, const Data&);
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
diff --git a/utils/cmdline/base_command.ipp b/utils/cmdline/base_command.ipp
new file mode 100644
index 000000000000..5696637085d7
--- /dev/null
+++ b/utils/cmdline/base_command.ipp
@@ -0,0 +1,104 @@
+// Copyright 2010 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_CMDLINE_BASE_COMMAND_IPP)
+#define UTILS_CMDLINE_BASE_COMMAND_IPP
+
+#include "utils/cmdline/base_command.hpp"
+
+
+namespace utils {
+namespace cmdline {
+
+
+/// Adds an option to the command.
+///
+/// This is to be called from the constructor of the subclass that implements
+/// the command.
+///
+/// \param option_ The option to add.
+template< typename Option >
+void
+command_proto::add_option(const Option& option_)
+{
+ add_option_ptr(new Option(option_));
+}
+
+
+/// Creates a new command.
+///
+/// \param name_ The name of the command. Must be unique within the context of
+/// a program and have no spaces.
+/// \param arg_list_ A textual description of the arguments received by the
+/// command. May be empty.
+/// \param min_args_ The minimum number of arguments required by the command.
+/// \param max_args_ The maximum number of arguments required by the command.
+/// -1 means infinity.
+/// \param short_description_ A description of the purpose of the command.
+template< typename Data >
+base_command< Data >::base_command(const std::string& name_,
+ const std::string& arg_list_,
+ const int min_args_,
+ const int max_args_,
+ const std::string& short_description_) :
+ command_proto(name_, arg_list_, min_args_, max_args_, short_description_)
+{
+}
+
+
+/// Entry point for the command.
+///
+/// This delegates execution to the run() abstract function after the command
+/// line provided in args has been parsed.
+///
+/// If this function returns, the command is assumed to have been executed
+/// successfully. Any error must be reported by means of exceptions.
+///
+/// \param ui Object to interact with the I/O of the command. The command must
+/// always use this object to write to stdout and stderr.
+/// \param args The command line passed to the command broken by word, which
+/// includes options and arguments.
+/// \param data An opaque data structure to pass to the run method.
+///
+/// \return The exit code that the program has to return. 0 on success, some
+/// other value on error.
+/// \throw usage_error If args is invalid (i.e. if the options are mispecified
+/// or if the arguments are invalid).
+template< typename Data >
+int
+base_command< Data >::main(ui* ui, const args_vector& args, const Data& data)
+{
+ return run(ui, parse_cmdline(args), data);
+}
+
+
+} // namespace cli
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_IPP)
diff --git a/utils/cmdline/base_command_fwd.hpp b/utils/cmdline/base_command_fwd.hpp
new file mode 100644
index 000000000000..c94db1ae2d05
--- /dev/null
+++ b/utils/cmdline/base_command_fwd.hpp
@@ -0,0 +1,47 @@
+// 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/cmdline/base_command_fwd.hpp
+/// Forward declarations for utils/cmdline/base_command.hpp
+
+#if !defined(UTILS_CMDLINE_BASE_COMMAND_FWD_HPP)
+#define UTILS_CMDLINE_BASE_COMMAND_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class command_proto;
+class base_command_no_data;
+template< typename > class base_command;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_FWD_HPP)
diff --git a/utils/cmdline/base_command_test.cpp b/utils/cmdline/base_command_test.cpp
new file mode 100644
index 000000000000..20df8ea49512
--- /dev/null
+++ b/utils/cmdline/base_command_test.cpp
@@ -0,0 +1,295 @@
+// Copyright 2010 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/cmdline/base_command.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/cmdline/parser.ipp"
+#include "utils/cmdline/ui_mock.hpp"
+#include "utils/defs.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+namespace {
+
+
+/// Mock command to test the cmdline::base_command base class.
+///
+/// \param Data The type of the opaque data object passed to main().
+/// \param ExpectedData The value run() will expect to find in the Data object
+/// passed to main().
+template< typename Data, Data ExpectedData >
+class mock_cmd : public cmdline::base_command< Data > {
+public:
+ /// Indicates if run() has been called already and executed correctly.
+ bool executed;
+
+ /// Contains the argument of --the_string after run() is executed.
+ std::string optvalue;
+
+ /// Constructs a new mock command.
+ mock_cmd(void) :
+ cmdline::base_command< Data >("mock", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing."),
+ executed(false)
+ {
+ this->add_option(cmdline::string_option("the_string", "Test option",
+ "arg"));
+ }
+
+ /// Executes the command.
+ ///
+ /// \param cmdline Representation of the command line to the subcommand.
+ /// \param data Arbitrary data cookie passed to the command.
+ ///
+ /// \return A hardcoded number for testing purposes.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& cmdline, const Data& data)
+ {
+ if (cmdline.has_option("the_string"))
+ optvalue = cmdline.get_option< cmdline::string_option >(
+ "the_string");
+ ATF_REQUIRE_EQ(ExpectedData, data);
+ executed = true;
+ return 1234;
+ }
+};
+
+
+/// Mock command to test the cmdline::base_command_no_data base class.
+class mock_cmd_no_data : public cmdline::base_command_no_data {
+public:
+ /// Indicates if run() has been called already and executed correctly.
+ bool executed;
+
+ /// Contains the argument of --the_string after run() is executed.
+ std::string optvalue;
+
+ /// Constructs a new mock command.
+ mock_cmd_no_data(void) :
+ cmdline::base_command_no_data("mock", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing."),
+ executed(false)
+ {
+ add_option(cmdline::string_option("the_string", "Test option", "arg"));
+ }
+
+ /// Executes the command.
+ ///
+ /// \param cmdline Representation of the command line to the subcommand.
+ ///
+ /// \return A hardcoded number for testing purposes.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& cmdline)
+ {
+ if (cmdline.has_option("the_string"))
+ optvalue = cmdline.get_option< cmdline::string_option >(
+ "the_string");
+ executed = true;
+ return 1234;
+ }
+};
+
+
+/// Implementation of a command to get access to parse_cmdline().
+class parse_cmdline_portal : public cmdline::command_proto {
+public:
+ /// Constructs a new mock command.
+ parse_cmdline_portal(void) :
+ cmdline::command_proto("portal", "arg1 [arg2 [arg3]]", 1, 3,
+ "Command for testing.")
+ {
+ this->add_option(cmdline::string_option("the_string", "Test option",
+ "arg"));
+ }
+
+ /// Delegator for the internal parse_cmdline() method.
+ ///
+ /// \param args The input arguments to be parsed.
+ ///
+ /// \return The parsed command line, split in options and arguments.
+ cmdline::parsed_cmdline
+ operator()(const cmdline::args_vector& args) const
+ {
+ return parse_cmdline(args);
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__ok);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__ok)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ (void)parse_cmdline_portal()(args);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__parse_fail);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__parse_fail)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ (void)parse_cmdline_portal()(args));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(command_proto__parse_cmdline__args_invalid);
+ATF_TEST_CASE_BODY(command_proto__parse_cmdline__args_invalid)
+{
+ cmdline::args_vector args;
+ args.push_back("portal");
+
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Not enough arguments",
+ (void)parse_cmdline_portal()(args));
+
+ args.push_back("1");
+ args.push_back("2");
+ args.push_back("3");
+ args.push_back("4");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Too many arguments",
+ (void)parse_cmdline_portal()(args));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__getters);
+ATF_TEST_CASE_BODY(base_command__getters)
+{
+ mock_cmd< int, 584 > cmd;
+ ATF_REQUIRE_EQ("mock", cmd.name());
+ ATF_REQUIRE_EQ("arg1 [arg2 [arg3]]", cmd.arg_list());
+ ATF_REQUIRE_EQ("Command for testing.", cmd.short_description());
+ ATF_REQUIRE_EQ(1, cmd.options().size());
+ ATF_REQUIRE_EQ("the_string", cmd.options()[0]->long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__main__ok)
+ATF_TEST_CASE_BODY(base_command__main__ok)
+{
+ mock_cmd< int, 584 > cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ ATF_REQUIRE_EQ(1234, cmd.main(&ui, args, 584));
+ ATF_REQUIRE(cmd.executed);
+ ATF_REQUIRE_EQ("foo bar", cmd.optvalue);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command__main__parse_cmdline_fail)
+ATF_TEST_CASE_BODY(base_command__main__parse_cmdline_fail)
+{
+ mock_cmd< int, 584 > cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ cmd.main(&ui, args, 584));
+ ATF_REQUIRE(!cmd.executed);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__getters);
+ATF_TEST_CASE_BODY(base_command_no_data__getters)
+{
+ mock_cmd_no_data cmd;
+ ATF_REQUIRE_EQ("mock", cmd.name());
+ ATF_REQUIRE_EQ("arg1 [arg2 [arg3]]", cmd.arg_list());
+ ATF_REQUIRE_EQ("Command for testing.", cmd.short_description());
+ ATF_REQUIRE_EQ(1, cmd.options().size());
+ ATF_REQUIRE_EQ("the_string", cmd.options()[0]->long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__main__ok)
+ATF_TEST_CASE_BODY(base_command_no_data__main__ok)
+{
+ mock_cmd_no_data cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--the_string=foo bar");
+ args.push_back("one arg");
+ args.push_back("another arg");
+ ATF_REQUIRE_EQ(1234, cmd.main(&ui, args));
+ ATF_REQUIRE(cmd.executed);
+ ATF_REQUIRE_EQ("foo bar", cmd.optvalue);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_command_no_data__main__parse_cmdline_fail)
+ATF_TEST_CASE_BODY(base_command_no_data__main__parse_cmdline_fail)
+{
+ mock_cmd_no_data cmd;
+
+ cmdline::ui_mock ui;
+ cmdline::args_vector args;
+ args.push_back("mock");
+ args.push_back("--foo-bar");
+ ATF_REQUIRE_THROW_RE(cmdline::usage_error, "Unknown.*foo-bar",
+ cmd.main(&ui, args));
+ ATF_REQUIRE(!cmd.executed);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__ok);
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__parse_fail);
+ ATF_ADD_TEST_CASE(tcs, command_proto__parse_cmdline__args_invalid);
+
+ ATF_ADD_TEST_CASE(tcs, base_command__getters);
+ ATF_ADD_TEST_CASE(tcs, base_command__main__ok);
+ ATF_ADD_TEST_CASE(tcs, base_command__main__parse_cmdline_fail);
+
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__getters);
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__main__ok);
+ ATF_ADD_TEST_CASE(tcs, base_command_no_data__main__parse_cmdline_fail);
+}
diff --git a/utils/cmdline/commands_map.hpp b/utils/cmdline/commands_map.hpp
new file mode 100644
index 000000000000..5378a6f2c471
--- /dev/null
+++ b/utils/cmdline/commands_map.hpp
@@ -0,0 +1,96 @@
+// Copyright 2011 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/cmdline/commands_map.hpp
+/// Maintains a collection of dynamically-instantiated commands.
+///
+/// Commands need to be dynamically-instantiated because they are often
+/// complex data structures. Instantiating them as static variables causes
+/// problems with the order of construction of globals. The commands_map class
+/// provided by this module provides a mechanism to maintain these instantiated
+/// objects.
+
+#if !defined(UTILS_CMDLINE_COMMANDS_MAP_HPP)
+#define UTILS_CMDLINE_COMMANDS_MAP_HPP
+
+#include "utils/cmdline/commands_map_fwd.hpp"
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "utils/noncopyable.hpp"
+
+
+namespace utils {
+namespace cmdline {
+
+
+/// Collection of dynamically-instantiated commands.
+template< typename BaseCommand >
+class commands_map : noncopyable {
+ /// Map of command names to their implementations.
+ typedef std::map< std::string, BaseCommand* > impl_map;
+
+ /// Map of category names to the command names they contain.
+ typedef std::map< std::string, std::set< std::string > > categories_map;
+
+ /// Collection of all available commands.
+ impl_map _commands;
+
+ /// Collection of defined categories and their commands.
+ categories_map _categories;
+
+public:
+ commands_map(void);
+ ~commands_map(void);
+
+ /// Scoped, strictly-owned pointer to a command from this map.
+ typedef typename std::auto_ptr< BaseCommand > command_ptr;
+ void insert(command_ptr, const std::string& = "");
+ void insert(BaseCommand*, const std::string& = "");
+
+ /// Type for a constant iterator.
+ typedef typename categories_map::const_iterator const_iterator;
+
+ bool empty(void) const;
+
+ const_iterator begin(void) const;
+ const_iterator end(void) const;
+
+ BaseCommand* find(const std::string&);
+ const BaseCommand* find(const std::string&) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_BASE_COMMAND_HPP)
diff --git a/utils/cmdline/commands_map.ipp b/utils/cmdline/commands_map.ipp
new file mode 100644
index 000000000000..8be87ab3b5cc
--- /dev/null
+++ b/utils/cmdline/commands_map.ipp
@@ -0,0 +1,161 @@
+// Copyright 2011 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/cmdline/commands_map.hpp"
+#include "utils/sanity.hpp"
+
+
+namespace utils {
+
+
+/// Constructs an empty set of commands.
+template< typename BaseCommand >
+cmdline::commands_map< BaseCommand >::commands_map(void)
+{
+}
+
+
+/// Destroys a set of commands.
+///
+/// This releases the dynamically-instantiated objects.
+template< typename BaseCommand >
+cmdline::commands_map< BaseCommand >::~commands_map(void)
+{
+ for (typename impl_map::iterator iter = _commands.begin();
+ iter != _commands.end(); iter++)
+ delete (*iter).second;
+}
+
+
+/// Inserts a new command into the map.
+///
+/// \param command The command to insert. This must have been dynamically
+/// allocated with new. The call grabs ownership of the command, or the
+/// command is freed if the call fails.
+/// \param category The category this command belongs to. Defaults to the empty
+/// string, which indicates that the command has not be categorized.
+template< typename BaseCommand >
+void
+cmdline::commands_map< BaseCommand >::insert(command_ptr command,
+ const std::string& category)
+{
+ INV(_commands.find(command->name()) == _commands.end());
+ BaseCommand* ptr = command.release();
+ INV(ptr != NULL);
+ _commands[ptr->name()] = ptr;
+ _categories[category].insert(ptr->name());
+}
+
+
+/// Inserts a new command into the map.
+///
+/// This grabs ownership of the pointer, so it is ONLY safe to use with the
+/// following idiom: insert(new foo()).
+///
+/// \param command The command to insert. This must have been dynamically
+/// allocated with new. The call grabs ownership of the command, or the
+/// command is freed if the call fails.
+/// \param category The category this command belongs to. Defaults to the empty
+/// string, which indicates that the command has not be categorized.
+template< typename BaseCommand >
+void
+cmdline::commands_map< BaseCommand >::insert(BaseCommand* command,
+ const std::string& category)
+{
+ insert(command_ptr(command), category);
+}
+
+
+/// Checks whether the list of commands is empty.
+///
+/// \return True if there are no commands in this map.
+template< typename BaseCommand >
+bool
+cmdline::commands_map< BaseCommand >::empty(void) const
+{
+ return _commands.empty();
+}
+
+
+/// Returns a constant iterator to the beginning of the categories mapping.
+///
+/// \return A map (string -> BaseCommand*) iterator.
+template< typename BaseCommand >
+typename cmdline::commands_map< BaseCommand >::const_iterator
+cmdline::commands_map< BaseCommand >::begin(void) const
+{
+ return _categories.begin();
+}
+
+
+/// Returns a constant iterator to the end of the categories mapping.
+///
+/// \return A map (string -> BaseCommand*) iterator.
+template< typename BaseCommand >
+typename cmdline::commands_map< BaseCommand >::const_iterator
+cmdline::commands_map< BaseCommand >::end(void) const
+{
+ return _categories.end();
+}
+
+
+/// Finds a command by name; mutable version.
+///
+/// \param name The name of the command to locate.
+///
+/// \return The command itself or NULL if it does not exist.
+template< typename BaseCommand >
+BaseCommand*
+cmdline::commands_map< BaseCommand >::find(const std::string& name)
+{
+ typename impl_map::iterator iter = _commands.find(name);
+ if (iter == _commands.end())
+ return NULL;
+ else
+ return (*iter).second;
+}
+
+
+/// Finds a command by name; constant version.
+///
+/// \param name The name of the command to locate.
+///
+/// \return The command itself or NULL if it does not exist.
+template< typename BaseCommand >
+const BaseCommand*
+cmdline::commands_map< BaseCommand >::find(const std::string& name) const
+{
+ typename impl_map::const_iterator iter = _commands.find(name);
+ if (iter == _commands.end())
+ return NULL;
+ else
+ return (*iter).second;
+}
+
+
+} // namespace utils
diff --git a/utils/cmdline/commands_map_fwd.hpp b/utils/cmdline/commands_map_fwd.hpp
new file mode 100644
index 000000000000..a81a852790da
--- /dev/null
+++ b/utils/cmdline/commands_map_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/cmdline/commands_map_fwd.hpp
+/// Forward declarations for utils/cmdline/commands_map.hpp
+
+#if !defined(UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP)
+#define UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+template< typename > class commands_map;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_COMMANDS_MAP_FWD_HPP)
diff --git a/utils/cmdline/commands_map_test.cpp b/utils/cmdline/commands_map_test.cpp
new file mode 100644
index 000000000000..47a7404f64fb
--- /dev/null
+++ b/utils/cmdline/commands_map_test.cpp
@@ -0,0 +1,140 @@
+// Copyright 2011 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/cmdline/commands_map.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/base_command.hpp"
+#include "utils/defs.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+namespace {
+
+
+/// Fake command to validate the behavior of commands_map.
+///
+/// Note that this command does not do anything. It is only intended to provide
+/// a specific class that can be inserted into commands_map instances and check
+/// that it can be located properly.
+class mock_cmd : public cmdline::base_command_no_data {
+public:
+ /// Constructor for the mock command.
+ ///
+ /// \param mock_name The name of the command. All other settings are set to
+ /// irrelevant values.
+ mock_cmd(const char* mock_name) :
+ cmdline::base_command_no_data(mock_name, "", 0, 0,
+ "Command for testing.")
+ {
+ }
+
+ /// Runs the mock command.
+ ///
+ /// \return Nothing because this function is never called.
+ int
+ run(cmdline::ui* /* ui */,
+ const cmdline::parsed_cmdline& /* cmdline */)
+ {
+ UNREACHABLE;
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(empty);
+ATF_TEST_CASE_BODY(empty)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ ATF_REQUIRE(commands.empty());
+ ATF_REQUIRE(commands.begin() == commands.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some);
+ATF_TEST_CASE_BODY(some)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ cmdline::base_command_no_data* cmd1 = new mock_cmd("cmd1");
+ commands.insert(cmd1);
+ cmdline::base_command_no_data* cmd2 = new mock_cmd("cmd2");
+ commands.insert(cmd2, "foo");
+
+ ATF_REQUIRE(!commands.empty());
+
+ cmdline::commands_map< cmdline::base_command_no_data >::const_iterator
+ iter = commands.begin();
+ ATF_REQUIRE_EQ("", (*iter).first);
+ ATF_REQUIRE_EQ(1, (*iter).second.size());
+ ATF_REQUIRE_EQ("cmd1", *(*iter).second.begin());
+
+ ++iter;
+ ATF_REQUIRE_EQ("foo", (*iter).first);
+ ATF_REQUIRE_EQ(1, (*iter).second.size());
+ ATF_REQUIRE_EQ("cmd2", *(*iter).second.begin());
+
+ ATF_REQUIRE(++iter == commands.end());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find__match);
+ATF_TEST_CASE_BODY(find__match)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ cmdline::base_command_no_data* cmd1 = new mock_cmd("cmd1");
+ commands.insert(cmd1);
+ cmdline::base_command_no_data* cmd2 = new mock_cmd("cmd2");
+ commands.insert(cmd2);
+
+ ATF_REQUIRE(cmd1 == commands.find("cmd1"));
+ ATF_REQUIRE(cmd2 == commands.find("cmd2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(find__nomatch);
+ATF_TEST_CASE_BODY(find__nomatch)
+{
+ cmdline::commands_map< cmdline::base_command_no_data > commands;
+ commands.insert(new mock_cmd("cmd1"));
+
+ ATF_REQUIRE(NULL == commands.find("cmd2"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, empty);
+ ATF_ADD_TEST_CASE(tcs, some);
+ ATF_ADD_TEST_CASE(tcs, find__match);
+ ATF_ADD_TEST_CASE(tcs, find__nomatch);
+}
diff --git a/utils/cmdline/exceptions.cpp b/utils/cmdline/exceptions.cpp
new file mode 100644
index 000000000000..fa9ba2218a7f
--- /dev/null
+++ b/utils/cmdline/exceptions.cpp
@@ -0,0 +1,175 @@
+// Copyright 2010 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/cmdline/exceptions.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+
+#define VALIDATE_OPTION_NAME(option) PRE_MSG( \
+ (option.length() == 2 && (option[0] == '-' && option[1] != '-')) || \
+ (option.length() > 2 && (option[0] == '-' && option[1] == '-')), \
+ F("The option name %s must be fully specified") % option);
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+cmdline::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+cmdline::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new usage_error.
+///
+/// \param message The reason behind the usage error.
+cmdline::usage_error::usage_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+cmdline::usage_error::~usage_error(void) throw()
+{
+}
+
+
+/// Constructs a new missing_option_argument_error.
+///
+/// \param option_ The option for which no argument was provided. The option
+/// name must be fully specified (with - or -- in front).
+cmdline::missing_option_argument_error::missing_option_argument_error(
+ const std::string& option_) :
+ usage_error(F("Missing required argument for option %s") % option_),
+ _option(option_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::missing_option_argument_error::~missing_option_argument_error(void)
+ throw()
+{
+}
+
+
+/// Returns the option name for which no argument was provided.
+///
+/// \return The option name.
+const std::string&
+cmdline::missing_option_argument_error::option(void) const
+{
+ return _option;
+}
+
+
+/// Constructs a new option_argument_value_error.
+///
+/// \param option_ The option to which an invalid argument was passed. The
+/// option name must be fully specified (with - or -- in front).
+/// \param argument_ The invalid argument.
+/// \param reason_ The reason describing why the argument is invalid.
+cmdline::option_argument_value_error::option_argument_value_error(
+ const std::string& option_, const std::string& argument_,
+ const std::string& reason_) :
+ usage_error(F("Invalid argument '%s' for option %s: %s") % argument_ %
+ option_ % reason_),
+ _option(option_),
+ _argument(argument_),
+ _reason(reason_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::option_argument_value_error::~option_argument_value_error(void)
+ throw()
+{
+}
+
+
+/// Returns the option to which the invalid argument was passed.
+///
+/// \return The option name.
+const std::string&
+cmdline::option_argument_value_error::option(void) const
+{
+ return _option;
+}
+
+
+/// Returns the invalid argument value.
+///
+/// \return The invalid argument.
+const std::string&
+cmdline::option_argument_value_error::argument(void) const
+{
+ return _argument;
+}
+
+
+/// Constructs a new unknown_option_error.
+///
+/// \param option_ The unknown option. The option name must be fully specified
+/// (with - or -- in front).
+cmdline::unknown_option_error::unknown_option_error(
+ const std::string& option_) :
+ usage_error(F("Unknown option %s") % option_),
+ _option(option_)
+{
+ VALIDATE_OPTION_NAME(option_);
+}
+
+
+/// Destructor for the error.
+cmdline::unknown_option_error::~unknown_option_error(void) throw()
+{
+}
+
+
+/// Returns the unknown option name.
+///
+/// \return The unknown option.
+const std::string&
+cmdline::unknown_option_error::option(void) const
+{
+ return _option;
+}
diff --git a/utils/cmdline/exceptions.hpp b/utils/cmdline/exceptions.hpp
new file mode 100644
index 000000000000..59f99e835ce1
--- /dev/null
+++ b/utils/cmdline/exceptions.hpp
@@ -0,0 +1,109 @@
+// Copyright 2010 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/cmdline/exceptions.hpp
+/// Exception types raised by the cmdline module.
+
+#if !defined(UTILS_CMDLINE_EXCEPTIONS_HPP)
+#define UTILS_CMDLINE_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace utils {
+namespace cmdline {
+
+
+/// Base exception for cmdline errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Generic error to describe problems caused by the user.
+class usage_error : public error {
+public:
+ explicit usage_error(const std::string&);
+ ~usage_error(void) throw();
+};
+
+
+/// Error denoting that no argument was provided to an option that required one.
+class missing_option_argument_error : public usage_error {
+ /// Name of the option for which no required argument was specified.
+ std::string _option;
+
+public:
+ explicit missing_option_argument_error(const std::string&);
+ ~missing_option_argument_error(void) throw();
+
+ const std::string& option(void) const;
+};
+
+
+/// Error denoting that the argument provided to an option is invalid.
+class option_argument_value_error : public usage_error {
+ /// Name of the option for which the argument was invalid.
+ std::string _option;
+
+ /// Raw value of the invalid user-provided argument.
+ std::string _argument;
+
+ /// Reason describing why the argument is invalid.
+ std::string _reason;
+
+public:
+ explicit option_argument_value_error(const std::string&, const std::string&,
+ const std::string&);
+ ~option_argument_value_error(void) throw();
+
+ const std::string& option(void) const;
+ const std::string& argument(void) const;
+};
+
+
+/// Error denoting that the user specified an unknown option.
+class unknown_option_error : public usage_error {
+ /// Name of the option that was not known.
+ std::string _option;
+
+public:
+ explicit unknown_option_error(const std::string&);
+ ~unknown_option_error(void) throw();
+
+ const std::string& option(void) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_EXCEPTIONS_HPP)
diff --git a/utils/cmdline/exceptions_test.cpp b/utils/cmdline/exceptions_test.cpp
new file mode 100644
index 000000000000..b541e08f6995
--- /dev/null
+++ b/utils/cmdline/exceptions_test.cpp
@@ -0,0 +1,83 @@
+// Copyright 2010 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/cmdline/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+namespace cmdline = utils::cmdline;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const cmdline::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error);
+ATF_TEST_CASE_BODY(missing_option_argument_error)
+{
+ const cmdline::missing_option_argument_error e("-o");
+ ATF_REQUIRE(std::strcmp("Missing required argument for option -o",
+ e.what()) == 0);
+ ATF_REQUIRE_EQ("-o", e.option());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_argument_value_error);
+ATF_TEST_CASE_BODY(option_argument_value_error)
+{
+ const cmdline::option_argument_value_error e("--the_option", "the value",
+ "the reason");
+ ATF_REQUIRE(std::strcmp("Invalid argument 'the value' for option "
+ "--the_option: the reason", e.what()) == 0);
+ ATF_REQUIRE_EQ("--the_option", e.option());
+ ATF_REQUIRE_EQ("the value", e.argument());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error);
+ATF_TEST_CASE_BODY(unknown_option_error)
+{
+ const cmdline::unknown_option_error e("--foo");
+ ATF_REQUIRE(std::strcmp("Unknown option --foo", e.what()) == 0);
+ ATF_REQUIRE_EQ("--foo", e.option());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error);
+ ATF_ADD_TEST_CASE(tcs, option_argument_value_error);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error);
+}
diff --git a/utils/cmdline/globals.cpp b/utils/cmdline/globals.cpp
new file mode 100644
index 000000000000..76e0231fa36b
--- /dev/null
+++ b/utils/cmdline/globals.cpp
@@ -0,0 +1,78 @@
+// Copyright 2010 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/cmdline/globals.hpp"
+
+#include "utils/format/macros.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// The name of the binary used to execute the program.
+static std::string Progname;
+
+
+} // anonymous namespace
+
+
+/// Initializes the global state of the CLI.
+///
+/// This function can only be called once during the execution of a program,
+/// unless override_for_testing is set to true.
+///
+/// \param argv0 The value of argv[0]; i.e. the program name.
+/// \param override_for_testing Should always be set to false unless for tests
+/// of this functionality, which may set this to true to redefine internal
+/// state.
+void
+cmdline::init(const char* argv0, const bool override_for_testing)
+{
+ if (!override_for_testing)
+ PRE_MSG(Progname.empty(), "cmdline::init called more than once");
+ Progname = utils::fs::path(argv0).leaf_name();
+ LD(F("Program name: %s") % Progname);
+ POST(!Progname.empty());
+}
+
+
+/// Gets the program name.
+///
+/// \pre init() must have been called in advance.
+///
+/// \return The program name.
+const std::string&
+cmdline::progname(void)
+{
+ PRE_MSG(!Progname.empty(), "cmdline::init not called yet");
+ return Progname;
+}
diff --git a/utils/cmdline/globals.hpp b/utils/cmdline/globals.hpp
new file mode 100644
index 000000000000..ab7904d69520
--- /dev/null
+++ b/utils/cmdline/globals.hpp
@@ -0,0 +1,48 @@
+// Copyright 2010 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/cmdline/globals.hpp
+/// Representation of global, immutable state for a CLI.
+
+#if !defined(UTILS_CMDLINE_GLOBALS_HPP)
+#define UTILS_CMDLINE_GLOBALS_HPP
+
+#include <string>
+
+namespace utils {
+namespace cmdline {
+
+
+void init(const char*, const bool = false);
+const std::string& progname(void);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_GLOBALS_HPP)
diff --git a/utils/cmdline/globals_test.cpp b/utils/cmdline/globals_test.cpp
new file mode 100644
index 000000000000..5c2ac7cc2d6c
--- /dev/null
+++ b/utils/cmdline/globals_test.cpp
@@ -0,0 +1,77 @@
+// Copyright 2010 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/cmdline/globals.hpp"
+
+#include <atf-c++.hpp>
+
+namespace cmdline = utils::cmdline;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__absolute);
+ATF_TEST_CASE_BODY(progname__absolute)
+{
+ cmdline::init("/path/to/foobar");
+ ATF_REQUIRE_EQ("foobar", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__relative);
+ATF_TEST_CASE_BODY(progname__relative)
+{
+ cmdline::init("to/barbaz");
+ ATF_REQUIRE_EQ("barbaz", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__plain);
+ATF_TEST_CASE_BODY(progname__plain)
+{
+ cmdline::init("program");
+ ATF_REQUIRE_EQ("program", cmdline::progname());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__override_for_testing);
+ATF_TEST_CASE_BODY(progname__override_for_testing)
+{
+ cmdline::init("program");
+ ATF_REQUIRE_EQ("program", cmdline::progname());
+
+ cmdline::init("foo", true);
+ ATF_REQUIRE_EQ("foo", cmdline::progname());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, progname__absolute);
+ ATF_ADD_TEST_CASE(tcs, progname__relative);
+ ATF_ADD_TEST_CASE(tcs, progname__plain);
+ ATF_ADD_TEST_CASE(tcs, progname__override_for_testing);
+}
diff --git a/utils/cmdline/options.cpp b/utils/cmdline/options.cpp
new file mode 100644
index 000000000000..61736e31c11e
--- /dev/null
+++ b/utils/cmdline/options.cpp
@@ -0,0 +1,605 @@
+// Copyright 2010 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/cmdline/options.hpp"
+
+#include <stdexcept>
+#include <vector>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/operations.ipp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+
+/// Constructs a generic option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ If not NULL, specifies that the option must receive an
+/// argument and specifies the name of such argument for documentation
+/// purposes.
+/// \param default_value_ If not NULL, specifies that the option has a default
+/// value for the mandatory argument.
+cmdline::base_option::base_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ _short_name(short_name_),
+ _long_name(long_name_),
+ _description(description_),
+ _arg_name(arg_name_ == NULL ? "" : arg_name_),
+ _has_default_value(default_value_ != NULL),
+ _default_value(default_value_ == NULL ? "" : default_value_)
+{
+ INV(short_name_ != '\0');
+}
+
+
+/// Constructs a generic option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ If not NULL, specifies that the option must receive an
+/// argument and specifies the name of such argument for documentation
+/// purposes.
+/// \param default_value_ If not NULL, specifies that the option has a default
+/// value for the mandatory argument.
+cmdline::base_option::base_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ _short_name('\0'),
+ _long_name(long_name_),
+ _description(description_),
+ _arg_name(arg_name_ == NULL ? "" : arg_name_),
+ _has_default_value(default_value_ != NULL),
+ _default_value(default_value_ == NULL ? "" : default_value_)
+{
+}
+
+
+/// Destructor for the option.
+cmdline::base_option::~base_option(void)
+{
+}
+
+
+/// Checks whether the option has a short name or not.
+///
+/// \return True if the option has a short name, false otherwise.
+bool
+cmdline::base_option::has_short_name(void) const
+{
+ return _short_name != '\0';
+}
+
+
+/// Returns the short name of the option.
+///
+/// \pre has_short_name() must be true.
+///
+/// \return The short name.
+char
+cmdline::base_option::short_name(void) const
+{
+ PRE(has_short_name());
+ return _short_name;
+}
+
+
+/// Returns the long name of the option.
+///
+/// \return The long name.
+const std::string&
+cmdline::base_option::long_name(void) const
+{
+ return _long_name;
+}
+
+
+/// Returns the description of the option.
+///
+/// \return The description.
+const std::string&
+cmdline::base_option::description(void) const
+{
+ return _description;
+}
+
+
+/// Checks whether the option needs an argument or not.
+///
+/// \return True if the option needs an argument, false otherwise.
+bool
+cmdline::base_option::needs_arg(void) const
+{
+ return !_arg_name.empty();
+}
+
+
+/// Returns the argument name of the option for documentation purposes.
+///
+/// \pre needs_arg() must be true.
+///
+/// \return The argument name.
+const std::string&
+cmdline::base_option::arg_name(void) const
+{
+ INV(needs_arg());
+ return _arg_name;
+}
+
+
+/// Checks whether the option has a default value for its argument.
+///
+/// \pre needs_arg() must be true.
+///
+/// \return True if the option has a default value, false otherwise.
+bool
+cmdline::base_option::has_default_value(void) const
+{
+ PRE(needs_arg());
+ return _has_default_value;
+}
+
+
+/// Returns the default value for the argument to the option.
+///
+/// \pre has_default_value() must be true.
+///
+/// \return The default value.
+const std::string&
+cmdline::base_option::default_value(void) const
+{
+ INV(has_default_value());
+ return _default_value;;
+}
+
+
+/// Formats the short name of the option for documentation purposes.
+///
+/// \return A string describing the option's short name.
+std::string
+cmdline::base_option::format_short_name(void) const
+{
+ PRE(has_short_name());
+
+ if (needs_arg()) {
+ return F("-%s %s") % short_name() % arg_name();
+ } else {
+ return F("-%s") % short_name();
+ }
+}
+
+
+/// Formats the long name of the option for documentation purposes.
+///
+/// \return A string describing the option's long name.
+std::string
+cmdline::base_option::format_long_name(void) const
+{
+ if (needs_arg()) {
+ return F("--%s=%s") % long_name() % arg_name();
+ } else {
+ return F("--%s") % long_name();
+ }
+}
+
+
+
+/// Ensures that an argument passed to the option is valid.
+///
+/// This must be reimplemented by subclasses that describe options with
+/// arguments.
+///
+/// \throw cmdline::option_argument_value_error Subclasses must raise this
+/// exception to indicate the cases in which str is invalid.
+void
+cmdline::base_option::validate(const std::string& /* str */) const
+{
+ UNREACHABLE_MSG("Option does not support an argument");
+}
+
+
+/// Constructs a boolean option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+cmdline::bool_option::bool_option(const char short_name_,
+ const char* long_name_,
+ const char* description_) :
+ base_option(short_name_, long_name_, description_)
+{
+}
+
+
+/// Constructs a boolean option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+cmdline::bool_option::bool_option(const char* long_name_,
+ const char* description_) :
+ base_option(long_name_, description_)
+{
+}
+
+
+/// Constructs an integer option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::int_option::int_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs an integer option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::int_option::int_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that an integer argument passed to the int_option is valid.
+///
+/// \param raw_value The argument representing an integer as provided by the
+/// user.
+///
+/// \throw cmdline::option_argument_value_error If the integer provided in
+/// raw_value is invalid.
+void
+cmdline::int_option::validate(const std::string& raw_value) const
+{
+ try {
+ (void)text::to_type< int >(raw_value);
+ } catch (const std::runtime_error& e) {
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Not a valid integer");
+ }
+}
+
+
+/// Converts an integer argument to a native integer.
+///
+/// \param raw_value The argument representing an integer as provided by the
+/// user.
+///
+/// \return The integer.
+///
+/// \pre validate(raw_value) must be true.
+int
+cmdline::int_option::convert(const std::string& raw_value)
+{
+ try {
+ return text::to_type< int >(raw_value);
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for int option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a list option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::list_option::list_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a list option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::list_option::list_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that a lisstring argument passed to the list_option is valid.
+void
+cmdline::list_option::validate(
+ const std::string& /* raw_value */) const
+{
+ // Any list is potentially valid; the caller must check for semantics.
+}
+
+
+/// Converts a string argument to a vector.
+///
+/// \param raw_value The argument representing a list as provided by the user.
+///
+/// \return The list.
+///
+/// \pre validate(raw_value) must be true.
+cmdline::list_option::option_type
+cmdline::list_option::convert(const std::string& raw_value)
+{
+ try {
+ return text::split(raw_value, ',');
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for list option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a path option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::path_option::path_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a path option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::path_option::path_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Ensures that a path argument passed to the path_option is valid.
+///
+/// \param raw_value The argument representing a path as provided by the user.
+///
+/// \throw cmdline::option_argument_value_error If the path provided in
+/// raw_value is invalid.
+void
+cmdline::path_option::validate(const std::string& raw_value) const
+{
+ try {
+ (void)utils::fs::path(raw_value);
+ } catch (const utils::fs::error& e) {
+ throw cmdline::option_argument_value_error(F("--%s") % long_name(),
+ raw_value, e.what());
+ }
+}
+
+
+/// Converts a path argument to a utils::fs::path.
+///
+/// \param raw_value The argument representing a path as provided by the user.
+///
+/// \return The path.
+///
+/// \pre validate(raw_value) must be true.
+utils::fs::path
+cmdline::path_option::convert(const std::string& raw_value)
+{
+ try {
+ return utils::fs::path(raw_value);
+ } catch (const std::runtime_error& e) {
+ PRE_MSG(false, F("Raw value '%s' for path option not properly "
+ "validated: %s") % raw_value % e.what());
+ }
+}
+
+
+/// Constructs a property option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes. Must include the '=' delimiter.
+cmdline::property_option::property_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_) :
+ base_option(short_name_, long_name_, description_, arg_name_)
+{
+ PRE(arg_name().find('=') != std::string::npos);
+}
+
+
+/// Constructs a property option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes. Must include the '=' delimiter.
+cmdline::property_option::property_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_) :
+ base_option(long_name_, description_, arg_name_)
+{
+ PRE(arg_name().find('=') != std::string::npos);
+}
+
+
+/// Validates the argument to a property option.
+///
+/// \param raw_value The argument provided by the user.
+void
+cmdline::property_option::validate(const std::string& raw_value) const
+{
+ const std::string::size_type pos = raw_value.find('=');
+ if (pos == std::string::npos)
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value,
+ F("Argument does not have the form '%s'") % arg_name());
+
+ const std::string key = raw_value.substr(0, pos);
+ if (key.empty())
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Empty property name");
+
+ const std::string value = raw_value.substr(pos + 1);
+ if (value.empty())
+ throw cmdline::option_argument_value_error(
+ F("--%s") % long_name(), raw_value, "Empty value");
+}
+
+
+/// Returns the property option in a key/value pair form.
+///
+/// \param raw_value The argument provided by the user.
+///
+/// \return raw_value The key/value pair representation of the property.
+///
+/// \pre validate(raw_value) must be true.
+cmdline::property_option::option_type
+cmdline::property_option::convert(const std::string& raw_value)
+{
+ const std::string::size_type pos = raw_value.find('=');
+ return std::make_pair(raw_value.substr(0, pos), raw_value.substr(pos + 1));
+}
+
+
+/// Constructs a string option with both a short and a long name.
+///
+/// \param short_name_ The short name for the option.
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::string_option::string_option(const char short_name_,
+ const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_)
+{
+}
+
+
+/// Constructs a string option with a long name only.
+///
+/// \param long_name_ The long name for the option.
+/// \param description_ A user-friendly description for the option.
+/// \param arg_name_ The name of the mandatory argument, for documentation
+/// purposes.
+/// \param default_value_ If not NULL, the default value for the mandatory
+/// argument.
+cmdline::string_option::string_option(const char* long_name_,
+ const char* description_,
+ const char* arg_name_,
+ const char* default_value_) :
+ base_option(long_name_, description_, arg_name_, default_value_)
+{
+}
+
+
+/// Does nothing; all string values are valid arguments to a string_option.
+void
+cmdline::string_option::validate(
+ const std::string& /* raw_value */) const
+{
+ // Nothing to do.
+}
+
+
+/// Returns the string unmodified.
+///
+/// \param raw_value The argument provided by the user.
+///
+/// \return raw_value
+///
+/// \pre validate(raw_value) must be true.
+std::string
+cmdline::string_option::convert(const std::string& raw_value)
+{
+ return raw_value;
+}
diff --git a/utils/cmdline/options.hpp b/utils/cmdline/options.hpp
new file mode 100644
index 000000000000..f3a83889e491
--- /dev/null
+++ b/utils/cmdline/options.hpp
@@ -0,0 +1,237 @@
+// Copyright 2010 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/cmdline/options.hpp
+/// Definitions of command-line options.
+
+#if !defined(UTILS_CMDLINE_OPTIONS_HPP)
+#define UTILS_CMDLINE_OPTIONS_HPP
+
+#include "utils/cmdline/options_fwd.hpp"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "utils/fs/path_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Type-less base option class.
+///
+/// This abstract class provides the most generic representation of options. It
+/// allows defining options with both short and long names, with and without
+/// arguments and with and without optional values. These are all the possible
+/// combinations supported by the getopt_long(3) function, on which this is
+/// built.
+///
+/// The internal values (e.g. the default value) of a generic option are all
+/// represented as strings. However, from the caller's perspective, this is
+/// suboptimal. Hence why this class must be specialized: the subclasses
+/// provide type-specific accessors and provide automatic validation of the
+/// types (e.g. a string '3foo' is not passed to an integer option).
+///
+/// Given that subclasses are used through templatized code, they must provide:
+///
+/// <ul>
+/// <li>A public option_type typedef that defines the type of the
+/// option.</li>
+///
+/// <li>A convert() method that takes a string and converts it to
+/// option_type. The string can be assumed to be convertible to the
+/// destination type. Should not raise exceptions.</li>
+///
+/// <li>A validate() method that matches the implementation of convert().
+/// This method can throw option_argument_value_error if the string cannot
+/// be converted appropriately. If validate() does not throw, then
+/// convert() must execute successfully.</li>
+/// </ul>
+///
+/// TODO(jmmv): Many methods in this class are split into two parts: has_foo()
+/// and foo(), the former to query if the foo is available and the latter to get
+/// the foo. It'd be very nice if we'd use something similar Boost.Optional to
+/// simplify this interface altogether.
+class base_option {
+ /// Short name of the option; 0 to indicate that none is available.
+ char _short_name;
+
+ /// Long name of the option.
+ std::string _long_name;
+
+ /// Textual description of the purpose of the option.
+ std::string _description;
+
+ /// Descriptive name of the required argument; empty if not allowed.
+ std::string _arg_name;
+
+ /// Whether the option has a default value or not.
+ ///
+ /// \todo We should probably be using the optional class here.
+ bool _has_default_value;
+
+ /// If _has_default_value is true, the default value.
+ std::string _default_value;
+
+public:
+ base_option(const char, const char*, const char*, const char* = NULL,
+ const char* = NULL);
+ base_option(const char*, const char*, const char* = NULL,
+ const char* = NULL);
+ virtual ~base_option(void);
+
+ bool has_short_name(void) const;
+ char short_name(void) const;
+ const std::string& long_name(void) const;
+ const std::string& description(void) const;
+
+ bool needs_arg(void) const;
+ const std::string& arg_name(void) const;
+
+ bool has_default_value(void) const;
+ const std::string& default_value(void) const;
+
+ std::string format_short_name(void) const;
+ std::string format_long_name(void) const;
+
+ virtual void validate(const std::string&) const;
+};
+
+
+/// Definition of a boolean option.
+///
+/// A boolean option can be specified once in the command line, at which point
+/// is set to true. Such an option cannot carry optional arguments.
+class bool_option : public base_option {
+public:
+ bool_option(const char, const char*, const char*);
+ bool_option(const char*, const char*);
+ virtual ~bool_option(void) {}
+
+ /// The data type of this option.
+ typedef bool option_type;
+};
+
+
+/// Definition of an integer option.
+class int_option : public base_option {
+public:
+ int_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ int_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~int_option(void) {}
+
+ /// The data type of this option.
+ typedef int option_type;
+
+ virtual void validate(const std::string& str) const;
+ static int convert(const std::string& str);
+};
+
+
+/// Definition of a comma-separated list of strings.
+class list_option : public base_option {
+public:
+ list_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ list_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~list_option(void) {}
+
+ /// The data type of this option.
+ typedef std::vector< std::string > option_type;
+
+ virtual void validate(const std::string&) const;
+ static option_type convert(const std::string&);
+};
+
+
+/// Definition of an option representing a path.
+///
+/// The path pointed to by the option may not exist, but it must be
+/// syntactically valid.
+class path_option : public base_option {
+public:
+ path_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ path_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~path_option(void) {}
+
+ /// The data type of this option.
+ typedef utils::fs::path option_type;
+
+ virtual void validate(const std::string&) const;
+ static utils::fs::path convert(const std::string&);
+};
+
+
+/// Definition of a property option.
+///
+/// A property option is an option whose required arguments are of the form
+/// 'name=value'. Both components of the property are treated as free-form
+/// non-empty strings; any other validation must happen on the caller side.
+///
+/// \todo Would be nice if the delimiter was parametrizable. With the current
+/// parser interface (convert() being a static method), the only way to do
+/// this would be to templatize this class.
+class property_option : public base_option {
+public:
+ property_option(const char, const char*, const char*, const char*);
+ property_option(const char*, const char*, const char*);
+ virtual ~property_option(void) {}
+
+ /// The data type of this option.
+ typedef std::pair< std::string, std::string > option_type;
+
+ virtual void validate(const std::string& str) const;
+ static option_type convert(const std::string& str);
+};
+
+
+/// Definition of a free-form string option.
+///
+/// This class provides no restrictions on the argument passed to the option.
+class string_option : public base_option {
+public:
+ string_option(const char, const char*, const char*, const char*,
+ const char* = NULL);
+ string_option(const char*, const char*, const char*, const char* = NULL);
+ virtual ~string_option(void) {}
+
+ /// The data type of this option.
+ typedef std::string option_type;
+
+ virtual void validate(const std::string& str) const;
+ static std::string convert(const std::string& str);
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_OPTIONS_HPP)
diff --git a/utils/cmdline/options_fwd.hpp b/utils/cmdline/options_fwd.hpp
new file mode 100644
index 000000000000..8b45797e3920
--- /dev/null
+++ b/utils/cmdline/options_fwd.hpp
@@ -0,0 +1,51 @@
+// 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/cmdline/options_fwd.hpp
+/// Forward declarations for utils/cmdline/options.hpp
+
+#if !defined(UTILS_CMDLINE_OPTIONS_FWD_HPP)
+#define UTILS_CMDLINE_OPTIONS_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class base_option;
+class bool_option;
+class int_option;
+class list_option;
+class path_option;
+class property_option;
+class string_option;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_OPTIONS_FWD_HPP)
diff --git a/utils/cmdline/options_test.cpp b/utils/cmdline/options_test.cpp
new file mode 100644
index 000000000000..82fd706a191a
--- /dev/null
+++ b/utils/cmdline/options_test.cpp
@@ -0,0 +1,526 @@
+// Copyright 2010 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/cmdline/options.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/fs/path.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// Simple string-based option type for testing purposes.
+class mock_option : public cmdline::base_option {
+public:
+ /// Constructs a mock option with a short name and a long name.
+ ///
+ ///
+ /// \param short_name_ The short name for the option.
+ /// \param long_name_ The long name for the option.
+ /// \param description_ A user-friendly description for the option.
+ /// \param arg_name_ If not NULL, specifies that the option must receive an
+ /// argument and specifies the name of such argument for documentation
+ /// purposes.
+ /// \param default_value_ If not NULL, specifies that the option has a
+ /// default value for the mandatory argument.
+ mock_option(const char short_name_, const char* long_name_,
+ const char* description_, const char* arg_name_ = NULL,
+ const char* default_value_ = NULL) :
+ base_option(short_name_, long_name_, description_, arg_name_,
+ default_value_) {}
+
+ /// Constructs a mock option with a long name only.
+ ///
+ /// \param long_name_ The long name for the option.
+ /// \param description_ A user-friendly description for the option.
+ /// \param arg_name_ If not NULL, specifies that the option must receive an
+ /// argument and specifies the name of such argument for documentation
+ /// purposes.
+ /// \param default_value_ If not NULL, specifies that the option has a
+ /// default value for the mandatory argument.
+ mock_option(const char* long_name_,
+ const char* description_, const char* arg_name_ = NULL,
+ const char* default_value_ = NULL) :
+ base_option(long_name_, description_, arg_name_, default_value_) {}
+
+ /// The data type of this option.
+ typedef std::string option_type;
+
+ /// Ensures that the argument passed to the option is valid.
+ ///
+ /// In this particular mock option, this does not perform any validation.
+ void
+ validate(const std::string& /* str */) const
+ {
+ // Do nothing.
+ }
+
+ /// Returns the input parameter without any conversion.
+ ///
+ /// \param str The user-provided argument to the option.
+ ///
+ /// \return The same value as provided by the user without conversion.
+ static std::string
+ convert(const std::string& str)
+ {
+ return str;
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__no_arg);
+ATF_TEST_CASE_BODY(base_option__short_name__no_arg)
+{
+ const mock_option o('f', "force", "Force execution");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('f', o.short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+ ATF_REQUIRE_EQ("-f", o.format_short_name());
+ ATF_REQUIRE_EQ("--force", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__with_arg__no_default);
+ATF_TEST_CASE_BODY(base_option__short_name__with_arg__no_default)
+{
+ const mock_option o('c', "conf_file", "Configuration file", "path");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('c', o.short_name());
+ ATF_REQUIRE_EQ("conf_file", o.long_name());
+ ATF_REQUIRE_EQ("Configuration file", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+ ATF_REQUIRE_EQ("-c path", o.format_short_name());
+ ATF_REQUIRE_EQ("--conf_file=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__short_name__with_arg__with_default);
+ATF_TEST_CASE_BODY(base_option__short_name__with_arg__with_default)
+{
+ const mock_option o('c', "conf_file", "Configuration file", "path",
+ "defpath");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('c', o.short_name());
+ ATF_REQUIRE_EQ("conf_file", o.long_name());
+ ATF_REQUIRE_EQ("Configuration file", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("defpath", o.default_value());
+ ATF_REQUIRE_EQ("-c path", o.format_short_name());
+ ATF_REQUIRE_EQ("--conf_file=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__no_arg);
+ATF_TEST_CASE_BODY(base_option__long_name__no_arg)
+{
+ const mock_option o("dryrun", "Dry run mode");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("dryrun", o.long_name());
+ ATF_REQUIRE_EQ("Dry run mode", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+ ATF_REQUIRE_EQ("--dryrun", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__with_arg__no_default);
+ATF_TEST_CASE_BODY(base_option__long_name__with_arg__no_default)
+{
+ const mock_option o("helper", "Path to helper", "path");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("helper", o.long_name());
+ ATF_REQUIRE_EQ("Path to helper", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("path", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+ ATF_REQUIRE_EQ("--helper=path", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_option__long_name__with_arg__with_default);
+ATF_TEST_CASE_BODY(base_option__long_name__with_arg__with_default)
+{
+ const mock_option o("executable", "Executable name", "file", "foo");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("executable", o.long_name());
+ ATF_REQUIRE_EQ("Executable name", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("file", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("foo", o.default_value());
+ ATF_REQUIRE_EQ("--executable=file", o.format_long_name());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_option__short_name);
+ATF_TEST_CASE_BODY(bool_option__short_name)
+{
+ const cmdline::bool_option o('f', "force", "Force execution");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('f', o.short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_option__long_name);
+ATF_TEST_CASE_BODY(bool_option__long_name)
+{
+ const cmdline::bool_option o("force", "Force execution");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("force", o.long_name());
+ ATF_REQUIRE_EQ("Force execution", o.description());
+ ATF_REQUIRE(!o.needs_arg());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__short_name);
+ATF_TEST_CASE_BODY(int_option__short_name)
+{
+ const cmdline::int_option o('p', "int", "The int", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("int", o.long_name());
+ ATF_REQUIRE_EQ("The int", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__long_name);
+ATF_TEST_CASE_BODY(int_option__long_name)
+{
+ const cmdline::int_option o("int", "The int", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("int", o.long_name());
+ ATF_REQUIRE_EQ("The int", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_option__type);
+ATF_TEST_CASE_BODY(int_option__type)
+{
+ const cmdline::int_option o("int", "The int", "arg");
+
+ o.validate("123");
+ ATF_REQUIRE_EQ(123, cmdline::int_option::convert("123"));
+
+ o.validate("-567");
+ ATF_REQUIRE_EQ(-567, cmdline::int_option::convert("-567"));
+
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate(""));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5a"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("a5"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5 a"));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("5.0"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__short_name);
+ATF_TEST_CASE_BODY(list_option__short_name)
+{
+ const cmdline::list_option o('p', "list", "The list", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("list", o.long_name());
+ ATF_REQUIRE_EQ("The list", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__long_name);
+ATF_TEST_CASE_BODY(list_option__long_name)
+{
+ const cmdline::list_option o("list", "The list", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("list", o.long_name());
+ ATF_REQUIRE_EQ("The list", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(list_option__type);
+ATF_TEST_CASE_BODY(list_option__type)
+{
+ const cmdline::list_option o("list", "The list", "arg");
+
+ o.validate("");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("");
+ ATF_REQUIRE(words.empty());
+ }
+
+ o.validate("foo");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo");
+ ATF_REQUIRE_EQ(1, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ }
+
+ o.validate("foo,bar,baz");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,bar,baz");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("bar", words[1]);
+ ATF_REQUIRE_EQ("baz", words[2]);
+ }
+
+ o.validate("foo,bar,");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,bar,");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("bar", words[1]);
+ ATF_REQUIRE_EQ("", words[2]);
+ }
+
+ o.validate(",foo,bar");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert(",foo,bar");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("", words[0]);
+ ATF_REQUIRE_EQ("foo", words[1]);
+ ATF_REQUIRE_EQ("bar", words[2]);
+ }
+
+ o.validate("foo,,bar");
+ {
+ const cmdline::list_option::option_type words =
+ cmdline::list_option::convert("foo,,bar");
+ ATF_REQUIRE_EQ(3, words.size());
+ ATF_REQUIRE_EQ("foo", words[0]);
+ ATF_REQUIRE_EQ("", words[1]);
+ ATF_REQUIRE_EQ("bar", words[2]);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__short_name);
+ATF_TEST_CASE_BODY(path_option__short_name)
+{
+ const cmdline::path_option o('p', "path", "The path", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("path", o.long_name());
+ ATF_REQUIRE_EQ("The path", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__long_name);
+ATF_TEST_CASE_BODY(path_option__long_name)
+{
+ const cmdline::path_option o("path", "The path", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("path", o.long_name());
+ ATF_REQUIRE_EQ("The path", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(path_option__type);
+ATF_TEST_CASE_BODY(path_option__type)
+{
+ const cmdline::path_option o("path", "The path", "arg");
+
+ o.validate("/some/path");
+
+ try {
+ o.validate("");
+ fail("option_argument_value_error not raised");
+ } catch (const cmdline::option_argument_value_error& e) {
+ // Expected; ignore.
+ }
+
+ const cmdline::path_option::option_type path =
+ cmdline::path_option::convert("/foo/bar");
+ ATF_REQUIRE_EQ("bar", path.leaf_name()); // Ensure valid type.
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__short_name);
+ATF_TEST_CASE_BODY(property_option__short_name)
+{
+ const cmdline::property_option o('p', "property", "The property", "a=b");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("property", o.long_name());
+ ATF_REQUIRE_EQ("The property", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("a=b", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__long_name);
+ATF_TEST_CASE_BODY(property_option__long_name)
+{
+ const cmdline::property_option o("property", "The property", "a=b");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("property", o.long_name());
+ ATF_REQUIRE_EQ("The property", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("a=b", o.arg_name());
+ ATF_REQUIRE(!o.has_default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(property_option__type);
+ATF_TEST_CASE_BODY(property_option__type)
+{
+ typedef std::pair< std::string, std::string > string_pair;
+ const cmdline::property_option o("property", "The property", "a=b");
+
+ o.validate("foo=bar");
+ ATF_REQUIRE(string_pair("foo", "bar") ==
+ cmdline::property_option::convert("foo=bar"));
+
+ o.validate(" foo = bar baz");
+ ATF_REQUIRE(string_pair(" foo ", " bar baz") ==
+ cmdline::property_option::convert(" foo = bar baz"));
+
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate(""));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("="));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("a="));
+ ATF_REQUIRE_THROW(cmdline::option_argument_value_error, o.validate("=b"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__short_name);
+ATF_TEST_CASE_BODY(string_option__short_name)
+{
+ const cmdline::string_option o('p', "string", "The string", "arg", "value");
+ ATF_REQUIRE(o.has_short_name());
+ ATF_REQUIRE_EQ('p', o.short_name());
+ ATF_REQUIRE_EQ("string", o.long_name());
+ ATF_REQUIRE_EQ("The string", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__long_name);
+ATF_TEST_CASE_BODY(string_option__long_name)
+{
+ const cmdline::string_option o("string", "The string", "arg", "value");
+ ATF_REQUIRE(!o.has_short_name());
+ ATF_REQUIRE_EQ("string", o.long_name());
+ ATF_REQUIRE_EQ("The string", o.description());
+ ATF_REQUIRE(o.needs_arg());
+ ATF_REQUIRE_EQ("arg", o.arg_name());
+ ATF_REQUIRE(o.has_default_value());
+ ATF_REQUIRE_EQ("value", o.default_value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_option__type);
+ATF_TEST_CASE_BODY(string_option__type)
+{
+ const cmdline::string_option o("string", "The string", "foo");
+
+ o.validate("");
+ o.validate("some string");
+
+ const cmdline::string_option::option_type string =
+ cmdline::string_option::convert("foo");
+ ATF_REQUIRE_EQ(3, string.length()); // Ensure valid type.
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__no_arg);
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__with_arg__no_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__short_name__with_arg__with_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__no_arg);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__with_arg__no_default);
+ ATF_ADD_TEST_CASE(tcs, base_option__long_name__with_arg__with_default);
+
+ ATF_ADD_TEST_CASE(tcs, bool_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, bool_option__long_name);
+
+ ATF_ADD_TEST_CASE(tcs, int_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, int_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, int_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, list_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, list_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, list_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, path_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, path_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, path_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, property_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, property_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, property_option__type);
+
+ ATF_ADD_TEST_CASE(tcs, string_option__short_name);
+ ATF_ADD_TEST_CASE(tcs, string_option__long_name);
+ ATF_ADD_TEST_CASE(tcs, string_option__type);
+}
diff --git a/utils/cmdline/parser.cpp b/utils/cmdline/parser.cpp
new file mode 100644
index 000000000000..5c83f6d69cc4
--- /dev/null
+++ b/utils/cmdline/parser.cpp
@@ -0,0 +1,385 @@
+// Copyright 2010 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/cmdline/parser.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <getopt.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+
+#include "utils/auto_array.ipp"
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+namespace {
+
+
+/// Auxiliary data to call getopt_long(3).
+struct getopt_data : utils::noncopyable {
+ /// Plain-text representation of the short options.
+ ///
+ /// This string follows the syntax expected by getopt_long(3) in the
+ /// argument to describe the short options.
+ std::string short_options;
+
+ /// Representation of the long options as expected by getopt_long(3).
+ utils::auto_array< ::option > long_options;
+
+ /// Auto-generated identifiers to be able to parse long options.
+ std::map< int, const cmdline::base_option* > ids;
+};
+
+
+/// Converts a cmdline::options_vector to a getopt_data.
+///
+/// \param options The high-level definition of the options.
+/// \param [out] data An object containing the necessary data to call
+/// getopt_long(3) and interpret its results.
+static void
+options_to_getopt_data(const cmdline::options_vector& options,
+ getopt_data& data)
+{
+ data.short_options.clear();
+ data.long_options.reset(new ::option[options.size() + 1]);
+
+ int cur_id = 512;
+
+ for (cmdline::options_vector::size_type i = 0; i < options.size(); i++) {
+ const cmdline::base_option* option = options[i];
+ ::option& long_option = data.long_options[i];
+
+ long_option.name = option->long_name().c_str();
+ if (option->needs_arg())
+ long_option.has_arg = required_argument;
+ else
+ long_option.has_arg = no_argument;
+
+ int id = -1;
+ if (option->has_short_name()) {
+ data.short_options += option->short_name();
+ if (option->needs_arg())
+ data.short_options += ':';
+ id = option->short_name();
+ } else {
+ id = cur_id++;
+ }
+ long_option.flag = NULL;
+ long_option.val = id;
+ data.ids[id] = option;
+ }
+
+ ::option& last_long_option = data.long_options[options.size()];
+ last_long_option.name = NULL;
+ last_long_option.has_arg = 0;
+ last_long_option.flag = NULL;
+ last_long_option.val = 0;
+}
+
+
+/// Converts an argc/argv pair to an args_vector.
+///
+/// \param argc The value of argc as passed to main().
+/// \param argv The value of argv as passed to main().
+///
+/// \return An args_vector with the same contents of argc/argv.
+static cmdline::args_vector
+argv_to_vector(int argc, const char* const argv[])
+{
+ PRE(argv[argc] == NULL);
+ cmdline::args_vector args;
+ for (int i = 0; i < argc; i++)
+ args.push_back(argv[i]);
+ return args;
+}
+
+
+/// Creates a mutable version of argv.
+///
+/// \param argc The value of argc as passed to main().
+/// \param argv The value of argv as passed to main().
+///
+/// \return A new argv, with mutable buffers. The returned array must be
+/// released using the free_mutable_argv() function.
+static char**
+make_mutable_argv(const int argc, const char* const* argv)
+{
+ char** mutable_argv = new char*[argc + 1];
+ for (int i = 0; i < argc; i++)
+ mutable_argv[i] = ::strdup(argv[i]);
+ mutable_argv[argc] = NULL;
+ return mutable_argv;
+}
+
+
+/// Releases the object returned by make_mutable_argv().
+///
+/// \param argv A dynamically-allocated argv as returned by make_mutable_argv().
+static void
+free_mutable_argv(char** argv)
+{
+ char** ptr = argv;
+ while (*ptr != NULL) {
+ ::free(*ptr);
+ ptr++;
+ }
+ delete [] argv;
+}
+
+
+/// Finds the name of the offending option after a getopt_long error.
+///
+/// \param data Our internal getopt data used for the call to getopt_long.
+/// \param getopt_optopt The value of getopt(3)'s optopt after the error.
+/// \param argv The argv passed to getopt_long.
+/// \param getopt_optind The value of getopt(3)'s optind after the error.
+///
+/// \return A fully-specified option name (i.e. an option name prefixed by
+/// either '-' or '--').
+static std::string
+find_option_name(const getopt_data& data, const int getopt_optopt,
+ char** argv, const int getopt_optind)
+{
+ PRE(getopt_optopt >= 0);
+
+ if (getopt_optopt == 0) {
+ return argv[getopt_optind - 1];
+ } else if (getopt_optopt < std::numeric_limits< char >::max()) {
+ INV(getopt_optopt > 0);
+ const char ch = static_cast< char >(getopt_optopt);
+ return F("-%s") % ch;
+ } else {
+ for (const ::option* opt = &data.long_options[0]; opt->name != NULL;
+ opt++) {
+ if (opt->val == getopt_optopt)
+ return F("--%s") % opt->name;
+ }
+ UNREACHABLE;
+ }
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new parsed_cmdline.
+///
+/// Use the cmdline::parse() free functions to construct.
+///
+/// \param option_values_ A mapping of long option names to values. This
+/// contains a representation of the options provided by the user. Note
+/// that each value is actually a collection values: a user may specify a
+/// flag multiple times, and depending on the case we want to honor one or
+/// the other. For those options that support no argument, the argument
+/// value is the empty string.
+/// \param arguments_ The list of non-option arguments in the command line.
+cmdline::parsed_cmdline::parsed_cmdline(
+ const std::map< std::string, std::vector< std::string > >& option_values_,
+ const cmdline::args_vector& arguments_) :
+ _option_values(option_values_),
+ _arguments(arguments_)
+{
+}
+
+
+/// Checks if the given option has been given in the command line.
+///
+/// \param name The long option name to check for presence.
+///
+/// \return True if the option has been given; false otherwise.
+bool
+cmdline::parsed_cmdline::has_option(const std::string& name) const
+{
+ return _option_values.find(name) != _option_values.end();
+}
+
+
+/// Gets the raw value of an option.
+///
+/// The raw value of an option is a collection of strings that represent all the
+/// values passed to the option on the command line. It is up to the consumer
+/// if he wants to honor only the last value or all of them.
+///
+/// The caller has to use get_option() instead; this function is internal.
+///
+/// \pre has_option(name) must be true.
+///
+/// \param name The option to query.
+///
+/// \return The value of the option as a plain string.
+const std::vector< std::string >&
+cmdline::parsed_cmdline::get_option_raw(const std::string& name) const
+{
+ std::map< std::string, std::vector< std::string > >::const_iterator iter =
+ _option_values.find(name);
+ INV_MSG(iter != _option_values.end(), F("Undefined option --%s") % name);
+ return (*iter).second;
+}
+
+
+/// Returns the non-option arguments found in the command line.
+///
+/// \return The arguments, if any.
+const cmdline::args_vector&
+cmdline::parsed_cmdline::arguments(void) const
+{
+ return _arguments;
+}
+
+
+/// Parses a command line.
+///
+/// \param args The command line to parse, broken down by words.
+/// \param options The description of the supported options.
+///
+/// \return The parsed command line.
+///
+/// \pre args[0] must be the program or command name.
+///
+/// \throw cmdline::error See the description of parse(argc, argv, options) for
+/// more details on the raised errors.
+cmdline::parsed_cmdline
+cmdline::parse(const cmdline::args_vector& args,
+ const cmdline::options_vector& options)
+{
+ PRE_MSG(args.size() >= 1, "No progname or command name found");
+
+ utils::auto_array< const char* > argv(new const char*[args.size() + 1]);
+ for (args_vector::size_type i = 0; i < args.size(); i++)
+ argv[i] = args[i].c_str();
+ argv[args.size()] = NULL;
+ return parse(static_cast< int >(args.size()), argv.get(), options);
+}
+
+
+/// Parses a command line.
+///
+/// \param argc The number of arguments in argv, without counting the
+/// terminating NULL.
+/// \param argv The arguments to parse. The array is NULL-terminated.
+/// \param options The description of the supported options.
+///
+/// \return The parsed command line.
+///
+/// \pre args[0] must be the program or command name.
+///
+/// \throw cmdline::missing_option_argument_error If the user specified an
+/// option that requires an argument, but no argument was provided.
+/// \throw cmdline::unknown_option_error If the user specified an unknown
+/// option (i.e. an option not defined in options).
+/// \throw cmdline::option_argument_value_error If the user passed an invalid
+/// argument to a supported option.
+cmdline::parsed_cmdline
+cmdline::parse(const int argc, const char* const* argv,
+ const cmdline::options_vector& options)
+{
+ PRE_MSG(argc >= 1, "No progname or command name found");
+
+ getopt_data data;
+ options_to_getopt_data(options, data);
+
+ std::map< std::string, std::vector< std::string > > option_values;
+
+ for (cmdline::options_vector::const_iterator iter = options.begin();
+ iter != options.end(); iter++) {
+ const cmdline::base_option* option = *iter;
+ if (option->needs_arg() && option->has_default_value())
+ option_values[option->long_name()].push_back(
+ option->default_value());
+ }
+
+ args_vector args;
+
+ int mutable_argc = argc;
+ char** mutable_argv = make_mutable_argv(argc, argv);
+ const int old_opterr = ::opterr;
+ try {
+ int ch;
+
+ ::opterr = 0;
+
+ while ((ch = ::getopt_long(mutable_argc, mutable_argv,
+ ("+:" + data.short_options).c_str(),
+ data.long_options.get(), NULL)) != -1) {
+ if (ch == ':' ) {
+ const std::string name = find_option_name(
+ data, ::optopt, mutable_argv, ::optind);
+ throw cmdline::missing_option_argument_error(name);
+ } else if (ch == '?') {
+ const std::string name = find_option_name(
+ data, ::optopt, mutable_argv, ::optind);
+ throw cmdline::unknown_option_error(name);
+ }
+
+ const std::map< int, const cmdline::base_option* >::const_iterator
+ id = data.ids.find(ch);
+ INV(id != data.ids.end());
+ const cmdline::base_option* option = (*id).second;
+
+ if (option->needs_arg()) {
+ if (::optarg != NULL) {
+ option->validate(::optarg);
+ option_values[option->long_name()].push_back(::optarg);
+ } else
+ INV(option->has_default_value());
+ } else {
+ option_values[option->long_name()].push_back("");
+ }
+ }
+ args = argv_to_vector(mutable_argc - optind, mutable_argv + optind);
+
+ ::opterr = old_opterr;
+ ::optind = GETOPT_OPTIND_RESET_VALUE;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+ } catch (...) {
+ free_mutable_argv(mutable_argv);
+ ::opterr = old_opterr;
+ ::optind = GETOPT_OPTIND_RESET_VALUE;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+ throw;
+ }
+ free_mutable_argv(mutable_argv);
+
+ return parsed_cmdline(option_values, args);
+}
diff --git a/utils/cmdline/parser.hpp b/utils/cmdline/parser.hpp
new file mode 100644
index 000000000000..657fd1f01dd3
--- /dev/null
+++ b/utils/cmdline/parser.hpp
@@ -0,0 +1,85 @@
+// Copyright 2010 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/cmdline/parser.hpp
+/// Routines and data types to parse command line options and arguments.
+
+#if !defined(UTILS_CMDLINE_PARSER_HPP)
+#define UTILS_CMDLINE_PARSER_HPP
+
+#include "utils/cmdline/parser_fwd.hpp"
+
+#include <map>
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace cmdline {
+
+
+/// Representation of a parsed command line.
+///
+/// This class is returned by the command line parsing algorithm and provides
+/// methods to query the values of the options and the value of the arguments.
+/// All the values fed into this class can considered to be sane (i.e. the
+/// arguments to the options and the arguments to the command are valid), as all
+/// validation happens during parsing (before this class is instantiated).
+class parsed_cmdline {
+ /// Mapping of option names to all the values provided.
+ std::map< std::string, std::vector< std::string > > _option_values;
+
+ /// Collection of arguments with all options removed.
+ args_vector _arguments;
+
+ const std::vector< std::string >& get_option_raw(const std::string&) const;
+
+public:
+ parsed_cmdline(const std::map< std::string, std::vector< std::string > >&,
+ const args_vector&);
+
+ bool has_option(const std::string&) const;
+
+ template< typename Option >
+ typename Option::option_type get_option(const std::string&) const;
+
+ template< typename Option >
+ std::vector< typename Option::option_type > get_multi_option(
+ const std::string&) const;
+
+ const args_vector& arguments(void) const;
+};
+
+
+parsed_cmdline parse(const args_vector&, const options_vector&);
+parsed_cmdline parse(const int, const char* const*, const options_vector&);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_PARSER_HPP)
diff --git a/utils/cmdline/parser.ipp b/utils/cmdline/parser.ipp
new file mode 100644
index 000000000000..820826a15bfe
--- /dev/null
+++ b/utils/cmdline/parser.ipp
@@ -0,0 +1,83 @@
+// Copyright 2010 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_CMDLINE_PARSER_IPP)
+#define UTILS_CMDLINE_PARSER_IPP
+
+#include "utils/cmdline/parser.hpp"
+
+
+/// Gets the value of an option.
+///
+/// If the option has been specified multiple times on the command line, this
+/// only returns the last value. This is the traditional behavior.
+///
+/// The option must support arguments. Otherwise, a call to this function will
+/// not compile because the option type will lack the definition of some fields
+/// and/or methods.
+///
+/// \param name The option to query.
+///
+/// \return The value of the option converted to the appropriate type.
+///
+/// \pre has_option(name) must be true.
+template< typename Option > typename Option::option_type
+utils::cmdline::parsed_cmdline::get_option(const std::string& name) const
+{
+ const std::vector< std::string >& raw_values = get_option_raw(name);
+ return Option::convert(raw_values[raw_values.size() - 1]);
+}
+
+
+/// Gets the values of an option that supports repetition.
+///
+/// The option must support arguments. Otherwise, a call to this function will
+/// not compile because the option type will lack the definition of some fields
+/// and/or methods.
+///
+/// \param name The option to query.
+///
+/// \return The values of the option converted to the appropriate type.
+///
+/// \pre has_option(name) must be true.
+template< typename Option > std::vector< typename Option::option_type >
+utils::cmdline::parsed_cmdline::get_multi_option(const std::string& name) const
+{
+ std::vector< typename Option::option_type > values;
+
+ const std::vector< std::string >& raw_values = get_option_raw(name);
+ for (std::vector< std::string >::const_iterator iter = raw_values.begin();
+ iter != raw_values.end(); iter++) {
+ values.push_back(Option::convert(*iter));
+ }
+
+ return values;
+}
+
+
+#endif // !defined(UTILS_CMDLINE_PARSER_IPP)
diff --git a/utils/cmdline/parser_fwd.hpp b/utils/cmdline/parser_fwd.hpp
new file mode 100644
index 000000000000..a136e99a47ac
--- /dev/null
+++ b/utils/cmdline/parser_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/cmdline/parser_fwd.hpp
+/// Forward declarations for utils/cmdline/parser.hpp
+
+#if !defined(UTILS_CMDLINE_PARSER_FWD_HPP)
+#define UTILS_CMDLINE_PARSER_FWD_HPP
+
+#include <string>
+#include <vector>
+
+#include "utils/cmdline/options_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Replacement for argc and argv to represent a command line.
+typedef std::vector< std::string > args_vector;
+
+
+/// Collection of options to be used during parsing.
+typedef std::vector< const base_option* > options_vector;
+
+
+class parsed_cmdline;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_PARSER_FWD_HPP)
diff --git a/utils/cmdline/parser_test.cpp b/utils/cmdline/parser_test.cpp
new file mode 100644
index 000000000000..96370d279d2e
--- /dev/null
+++ b/utils/cmdline/parser_test.cpp
@@ -0,0 +1,688 @@
+// Copyright 2010 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/cmdline/parser.ipp"
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+extern "C" {
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+}
+
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <utility>
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/exceptions.hpp"
+#include "utils/cmdline/options.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace cmdline = utils::cmdline;
+
+using cmdline::base_option;
+using cmdline::bool_option;
+using cmdline::int_option;
+using cmdline::parse;
+using cmdline::parsed_cmdline;
+using cmdline::string_option;
+
+
+namespace {
+
+
+/// Mock option type to check the validate and convert methods sequence.
+///
+/// Instances of this option accept a string argument that must be either "zero"
+/// or "one". These are validated and converted to integers.
+class mock_option : public base_option {
+public:
+ /// Constructs the new option.
+ ///
+ /// \param long_name_ The long name for the option. All other option
+ /// properties are irrelevant for the tests using this, so they are set
+ /// to arbitrary values.
+ mock_option(const char* long_name_) :
+ base_option(long_name_, "Irrelevant description", "arg")
+ {
+ }
+
+ /// The type of the argument of this option.
+ typedef int option_type;
+
+ /// Checks that the user-provided option is valid.
+ ///
+ /// \param str The user argument; must be "zero" or "one".
+ ///
+ /// \throw cmdline::option_argument_value_error If str is not valid.
+ void
+ validate(const std::string& str) const
+ {
+ if (str != "zero" && str != "one")
+ throw cmdline::option_argument_value_error(F("--%s") % long_name(),
+ str, "Unknown value");
+ }
+
+ /// Converts the user-provided argument to our native integer type.
+ ///
+ /// \param str The user argument; must be "zero" or "one".
+ ///
+ /// \return 0 if the input is "zero", or 1 if the input is "one".
+ ///
+ /// \throw std::runtime_error If str is not valid. In real life, this
+ /// should be a precondition because validate() has already ensured that
+ /// the values passed to convert() are correct. However, we raise an
+ /// exception here because we are actually validating that this code
+ /// sequence holds true.
+ static int
+ convert(const std::string& str)
+ {
+ if (str == "zero")
+ return 0;
+ else if (str == "one")
+ return 1;
+ else {
+ // This would generally be an assertion but, given that this is
+ // test code, we want to catch any errors regardless of how the
+ // binary is built.
+ throw std::runtime_error("Value not validated properly.");
+ }
+ }
+};
+
+
+/// Redirects stdout and stderr to a file.
+///
+/// This fails the test case in case of any error.
+///
+/// \param file The name of the file to redirect stdout and stderr to.
+///
+/// \return A copy of the old stdout and stderr file descriptors.
+static std::pair< int, int >
+mock_stdfds(const char* file)
+{
+ std::cout.flush();
+ std::cerr.flush();
+
+ const int oldout = ::dup(STDOUT_FILENO);
+ ATF_REQUIRE(oldout != -1);
+ const int olderr = ::dup(STDERR_FILENO);
+ ATF_REQUIRE(olderr != -1);
+
+ const int fd = ::open(file, O_WRONLY | O_CREAT | O_TRUNC, 0644);
+ ATF_REQUIRE(fd != -1);
+ ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
+ ATF_REQUIRE(::dup2(fd, STDERR_FILENO) != -1);
+ ::close(fd);
+
+ return std::make_pair(oldout, olderr);
+}
+
+
+/// Restores stdout and stderr after a call to mock_stdfds.
+///
+/// \param oldfds The copy of the previous stdout and stderr as returned by the
+/// call to mock_fds().
+static void
+restore_stdfds(const std::pair< int, int >& oldfds)
+{
+ ATF_REQUIRE(::dup2(oldfds.first, STDOUT_FILENO) != -1);
+ ::close(oldfds.first);
+ ATF_REQUIRE(::dup2(oldfds.second, STDERR_FILENO) != -1);
+ ::close(oldfds.second);
+}
+
+
+/// Checks whether a '+:' prefix to the short options of getopt_long works.
+///
+/// It turns out that the getopt_long(3) implementation of Ubuntu 10.04.1 (and
+/// very likely other distributions) does not properly report a missing argument
+/// to a second long option as such. Instead of returning ':' when the second
+/// long option provided on the command line does not carry a required argument,
+/// it will mistakenly return '?' which translates to "unknown option".
+///
+/// As a result of this bug, we cannot properly detect that 'flag2' requires an
+/// argument in a command line like: 'progname --flag1=foo --flag2'.
+///
+/// I am not sure if we could fully workaround the issue in the implementation
+/// of our library. For the time being I am just using this bug detection in
+/// the test cases to prevent failures that are not really our fault.
+///
+/// \return bool True if getopt_long is broken and does not interpret '+:'
+/// correctly; False otherwise.
+static bool
+is_getopt_long_pluscolon_broken(void)
+{
+ struct ::option long_options[] = {
+ { "flag1", 1, NULL, '1' },
+ { "flag2", 1, NULL, '2' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ const int argc = 3;
+ char* argv[4];
+ argv[0] = ::strdup("progname");
+ argv[1] = ::strdup("--flag1=a");
+ argv[2] = ::strdup("--flag2");
+ argv[3] = NULL;
+
+ const int old_opterr = ::opterr;
+ ::opterr = 0;
+
+ bool got_colon = false;
+
+ int opt;
+ while ((opt = ::getopt_long(argc, argv, "+:", long_options, NULL)) != -1) {
+ switch (opt) {
+ case '1': break;
+ case '2': break;
+ case ':': got_colon = true; break;
+ case '?': break;
+ default: UNREACHABLE; break;
+ }
+ }
+
+ ::opterr = old_opterr;
+ ::optind = 1;
+#if defined(HAVE_GETOPT_WITH_OPTRESET)
+ ::optreset = 1;
+#endif
+
+ for (char** arg = &argv[0]; *arg != NULL; arg++)
+ std::free(*arg);
+
+ return !got_colon;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__no_options);
+ATF_TEST_CASE_BODY(progname__no_options)
+{
+ const int argc = 1;
+ const char* const argv[] = {"progname", NULL};
+ std::vector< const base_option* > options;
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.arguments().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(progname__some_options);
+ATF_TEST_CASE_BODY(progname__some_options)
+{
+ const int argc = 1;
+ const char* const argv[] = {"progname", NULL};
+ const string_option a('a', "a_option", "Foo", NULL);
+ const string_option b('b', "b_option", "Bar", "arg", "foo");
+ const string_option c("c_option", "Baz", NULL);
+ const string_option d("d_option", "Wohoo", "arg", "bar");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ options.push_back(&c);
+ options.push_back(&d);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE_EQ("foo", cmdline.get_option< string_option >("b_option"));
+ ATF_REQUIRE_EQ("bar", cmdline.get_option< string_option >("d_option"));
+ ATF_REQUIRE(cmdline.arguments().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_args__no_options);
+ATF_TEST_CASE_BODY(some_args__no_options)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "foo", "-c", "--opt", "bar", NULL};
+ std::vector< const base_option* > options;
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(!cmdline.has_option("c"));
+ ATF_REQUIRE(!cmdline.has_option("opt"));
+ ATF_REQUIRE_EQ(4, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("foo", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("-c", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("--opt", cmdline.arguments()[2]);
+ ATF_REQUIRE_EQ("bar", cmdline.arguments()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_args__some_options);
+ATF_TEST_CASE_BODY(some_args__some_options)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "foo", "-c", "--opt", "bar", NULL};
+ const string_option c('c', "opt", "Description", NULL);
+ std::vector< const base_option* > options;
+ options.push_back(&c);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(!cmdline.has_option("c"));
+ ATF_REQUIRE(!cmdline.has_option("opt"));
+ ATF_REQUIRE_EQ(4, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("foo", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("-c", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("--opt", cmdline.arguments()[2]);
+ ATF_REQUIRE_EQ("bar", cmdline.arguments()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_options__all_known);
+ATF_TEST_CASE_BODY(some_options__all_known)
+{
+ const int argc = 14;
+ const char* const argv[] = {
+ "progname",
+ "-a",
+ "-bvalue_b",
+ "-c", "value_c",
+ //"-d", // Options with default optional values are unsupported.
+ "-evalue_e", // Has default; overriden.
+ "--f_long",
+ "--g_long=value_g",
+ "--h_long", "value_h",
+ //"--i_long", // Options with default optional values are unsupported.
+ "--j_long", "value_j", // Has default; overriden as separate argument.
+ "arg1", "arg2", NULL,
+ };
+ const bool_option a('a', "a_long", "");
+ const string_option b('b', "b_long", "Description", "arg");
+ const string_option c('c', "c_long", "ABCD", "foo");
+ const string_option d('d', "d_long", "Description", "bar", "default_d");
+ const string_option e('e', "e_long", "Description", "baz", "default_e");
+ const bool_option f("f_long", "Description");
+ const string_option g("g_long", "Description", "arg");
+ const string_option h("h_long", "Description", "foo");
+ const string_option i("i_long", "EFGH", "bar", "default_i");
+ const string_option j("j_long", "Description", "baz", "default_j");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ options.push_back(&c);
+ options.push_back(&d);
+ options.push_back(&e);
+ options.push_back(&f);
+ options.push_back(&g);
+ options.push_back(&h);
+ options.push_back(&i);
+ options.push_back(&j);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.has_option("a_long"));
+ ATF_REQUIRE_EQ("value_b", cmdline.get_option< string_option >("b_long"));
+ ATF_REQUIRE_EQ("value_c", cmdline.get_option< string_option >("c_long"));
+ ATF_REQUIRE_EQ("default_d", cmdline.get_option< string_option >("d_long"));
+ ATF_REQUIRE_EQ("value_e", cmdline.get_option< string_option >("e_long"));
+ ATF_REQUIRE(cmdline.has_option("f_long"));
+ ATF_REQUIRE_EQ("value_g", cmdline.get_option< string_option >("g_long"));
+ ATF_REQUIRE_EQ("value_h", cmdline.get_option< string_option >("h_long"));
+ ATF_REQUIRE_EQ("default_i", cmdline.get_option< string_option >("i_long"));
+ ATF_REQUIRE_EQ("value_j", cmdline.get_option< string_option >("j_long"));
+ ATF_REQUIRE_EQ(2, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("arg1", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("arg2", cmdline.arguments()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_options__multi);
+ATF_TEST_CASE_BODY(some_options__multi)
+{
+ const int argc = 9;
+ const char* const argv[] = {
+ "progname",
+ "-a1",
+ "-bvalue1",
+ "-a2",
+ "--a_long=3",
+ "-bvalue2",
+ "--b_long=value3",
+ "arg1", "arg2", NULL,
+ };
+ const int_option a('a', "a_long", "Description", "arg");
+ const string_option b('b', "b_long", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&a);
+ options.push_back(&b);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ {
+ ATF_REQUIRE_EQ(3, cmdline.get_option< int_option >("a_long"));
+ const std::vector< int > multi =
+ cmdline.get_multi_option< int_option >("a_long");
+ ATF_REQUIRE_EQ(3, multi.size());
+ ATF_REQUIRE_EQ(1, multi[0]);
+ ATF_REQUIRE_EQ(2, multi[1]);
+ ATF_REQUIRE_EQ(3, multi[2]);
+ }
+
+ {
+ ATF_REQUIRE_EQ("value3", cmdline.get_option< string_option >("b_long"));
+ const std::vector< std::string > multi =
+ cmdline.get_multi_option< string_option >("b_long");
+ ATF_REQUIRE_EQ(3, multi.size());
+ ATF_REQUIRE_EQ("value1", multi[0]);
+ ATF_REQUIRE_EQ("value2", multi[1]);
+ ATF_REQUIRE_EQ("value3", multi[2]);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subcommands);
+ATF_TEST_CASE_BODY(subcommands)
+{
+ const int argc = 5;
+ const char* const argv[] = {"progname", "--flag1", "subcommand",
+ "--flag2", "arg", NULL};
+ const bool_option flag1("flag1", "");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE( cmdline.has_option("flag1"));
+ ATF_REQUIRE(!cmdline.has_option("flag2"));
+ ATF_REQUIRE_EQ(3, cmdline.arguments().size());
+ ATF_REQUIRE_EQ("subcommand", cmdline.arguments()[0]);
+ ATF_REQUIRE_EQ("--flag2", cmdline.arguments()[1]);
+ ATF_REQUIRE_EQ("arg", cmdline.arguments()[2]);
+
+ const bool_option flag2("flag2", "");
+ std::vector< const base_option* > options2;
+ options2.push_back(&flag2);
+ const parsed_cmdline cmdline2 = parse(cmdline.arguments(), options2);
+
+ ATF_REQUIRE(!cmdline2.has_option("flag1"));
+ ATF_REQUIRE( cmdline2.has_option("flag2"));
+ ATF_REQUIRE_EQ(1, cmdline2.arguments().size());
+ ATF_REQUIRE_EQ("arg", cmdline2.arguments()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__short);
+ATF_TEST_CASE_BODY(missing_option_argument_error__short)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a3", "-b", NULL};
+ const string_option flag1('a', "flag1", "Description", "arg");
+ const string_option flag2('b', "flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("-b", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__shortblock);
+ATF_TEST_CASE_BODY(missing_option_argument_error__shortblock)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-ab3", "-ac", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ const string_option flag2('b', "flag2", "Description", "arg");
+ const string_option flag3('c', "flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+ options.push_back(&flag3);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("-c", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(missing_option_argument_error__long);
+ATF_TEST_CASE_BODY(missing_option_argument_error__long)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2", NULL};
+ const string_option flag1("flag1", "Description", "arg");
+ const string_option flag2("flag2", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("missing_option_argument_error not raised");
+ } catch (const cmdline::missing_option_argument_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ } catch (const cmdline::unknown_option_error& e) {
+ if (is_getopt_long_pluscolon_broken())
+ expect_fail("Your getopt_long is broken");
+ fail("Got unknown_option_error instead of "
+ "missing_option_argument_error");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__short);
+ATF_TEST_CASE_BODY(unknown_option_error__short)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a", "-b", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-b", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__shortblock);
+ATF_TEST_CASE_BODY(unknown_option_error__shortblock)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "-a", "-bdc", NULL};
+ const bool_option flag1('a', "flag1", "Description");
+ const bool_option flag2('b', "flag2", "Description");
+ const bool_option flag3('c', "flag3", "Description");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+ options.push_back(&flag3);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-d", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_option_error__long);
+ATF_TEST_CASE_BODY(unknown_option_error__long)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2", NULL};
+ const string_option flag1("flag1", "Description", "arg");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_plus_option_error);
+ATF_TEST_CASE_BODY(unknown_plus_option_error)
+{
+ const int argc = 2;
+ const char* const argv[] = {"progname", "-+", NULL};
+ const cmdline::options_vector options;
+
+ try {
+ parse(argc, argv, options);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-+", e.option());
+ } catch (const cmdline::missing_option_argument_error& e) {
+ fail("Looks like getopt_long thinks a + option is defined and it "
+ "even requires an argument");
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_types);
+ATF_TEST_CASE_BODY(option_types)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=a", "--flag2=one", NULL};
+ const string_option flag1("flag1", "The flag1", "arg");
+ const mock_option flag2("flag2");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ const parsed_cmdline cmdline = parse(argc, argv, options);
+
+ ATF_REQUIRE(cmdline.has_option("flag1"));
+ ATF_REQUIRE(cmdline.has_option("flag2"));
+ ATF_REQUIRE_EQ("a", cmdline.get_option< string_option >("flag1"));
+ ATF_REQUIRE_EQ(1, cmdline.get_option< mock_option >("flag2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(option_validation_error);
+ATF_TEST_CASE_BODY(option_validation_error)
+{
+ const int argc = 3;
+ const char* const argv[] = {"progname", "--flag1=zero", "--flag2=foo",
+ NULL};
+ const mock_option flag1("flag1");
+ const mock_option flag2("flag2");
+ std::vector< const base_option* > options;
+ options.push_back(&flag1);
+ options.push_back(&flag2);
+
+ try {
+ parse(argc, argv, options);
+ fail("option_argument_value_error not raised");
+ } catch (const cmdline::option_argument_value_error& e) {
+ ATF_REQUIRE_EQ("--flag2", e.option());
+ ATF_REQUIRE_EQ("foo", e.argument());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(silent_errors);
+ATF_TEST_CASE_BODY(silent_errors)
+{
+ const int argc = 2;
+ const char* const argv[] = {"progname", "-h", NULL};
+ cmdline::options_vector options;
+
+ try {
+ std::pair< int, int > oldfds = mock_stdfds("output.txt");
+ try {
+ parse(argc, argv, options);
+ } catch (...) {
+ restore_stdfds(oldfds);
+ throw;
+ }
+ restore_stdfds(oldfds);
+ fail("unknown_option_error not raised");
+ } catch (const cmdline::unknown_option_error& e) {
+ ATF_REQUIRE_EQ("-h", e.option());
+ }
+
+ std::ifstream input("output.txt");
+ ATF_REQUIRE(input);
+
+ bool has_output = false;
+ std::string line;
+ while (std::getline(input, line).good()) {
+ std::cout << line << '\n';
+ has_output = true;
+ }
+
+ if (has_output)
+ fail("getopt_long printed messages on stdout/stderr by itself");
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, progname__no_options);
+ ATF_ADD_TEST_CASE(tcs, progname__some_options);
+ ATF_ADD_TEST_CASE(tcs, some_args__no_options);
+ ATF_ADD_TEST_CASE(tcs, some_args__some_options);
+ ATF_ADD_TEST_CASE(tcs, some_options__all_known);
+ ATF_ADD_TEST_CASE(tcs, some_options__multi);
+ ATF_ADD_TEST_CASE(tcs, subcommands);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__short);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__shortblock);
+ ATF_ADD_TEST_CASE(tcs, missing_option_argument_error__long);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__short);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__shortblock);
+ ATF_ADD_TEST_CASE(tcs, unknown_option_error__long);
+ ATF_ADD_TEST_CASE(tcs, unknown_plus_option_error);
+ ATF_ADD_TEST_CASE(tcs, option_types);
+ ATF_ADD_TEST_CASE(tcs, option_validation_error);
+ ATF_ADD_TEST_CASE(tcs, silent_errors);
+}
diff --git a/utils/cmdline/ui.cpp b/utils/cmdline/ui.cpp
new file mode 100644
index 000000000000..a682360a4259
--- /dev/null
+++ b/utils/cmdline/ui.cpp
@@ -0,0 +1,276 @@
+// Copyright 2011 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/cmdline/ui.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#if defined(HAVE_TERMIOS_H)
+# include <termios.h>
+#endif
+#include <unistd.h>
+}
+
+#include <iostream>
+
+#include "utils/cmdline/globals.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/operations.ipp"
+#include "utils/text/table.hpp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+using utils::none;
+using utils::optional;
+
+
+/// Destructor for the class.
+cmdline::ui::~ui(void)
+{
+}
+
+
+/// Writes a single line to stderr.
+///
+/// The written line is printed as is, without being wrapped to fit within the
+/// screen width. If the caller wants to print more than one line, it shall
+/// invoke this function once per line.
+///
+/// \param message The line to print. Should not include a trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+cmdline::ui::err(const std::string& message, const bool newline)
+{
+ LI(F("stderr: %s") % message);
+ if (newline)
+ std::cerr << message << "\n";
+ else {
+ std::cerr << message;
+ std::cerr.flush();
+ }
+}
+
+
+/// Writes a single line to stdout.
+///
+/// The written line is printed as is, without being wrapped to fit within the
+/// screen width. If the caller wants to print more than one line, it shall
+/// invoke this function once per line.
+///
+/// \param message The line to print. Should not include a trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+cmdline::ui::out(const std::string& message, const bool newline)
+{
+ LI(F("stdout: %s") % message);
+ if (newline)
+ std::cout << message << "\n";
+ else {
+ std::cout << message;
+ std::cout.flush();
+ }
+}
+
+
+/// Queries the width of the screen.
+///
+/// This information comes first from the COLUMNS environment variable. If not
+/// present or invalid, and if the stdout of the current process is connected to
+/// a terminal the width is deduced from the terminal itself. Ultimately, if
+/// all fails, none is returned. This function shall not raise any errors.
+///
+/// Be aware that the results of this query are cached during execution.
+/// Subsequent calls to this function will always return the same value even if
+/// the terminal size has actually changed.
+///
+/// \todo Install a signal handler for SIGWINCH so that we can readjust our
+/// knowledge of the terminal width when the user resizes the window.
+///
+/// \return The width of the screen if it was possible to determine it, or none
+/// otherwise.
+optional< std::size_t >
+cmdline::ui::screen_width(void) const
+{
+ static bool done = false;
+ static optional< std::size_t > width = none;
+
+ if (!done) {
+ const optional< std::string > columns = utils::getenv("COLUMNS");
+ if (columns) {
+ if (columns.get().length() > 0) {
+ try {
+ width = utils::make_optional(
+ utils::text::to_type< std::size_t >(columns.get()));
+ } catch (const utils::text::value_error& e) {
+ LD(F("Ignoring invalid value in COLUMNS variable: %s") %
+ e.what());
+ }
+ }
+ }
+ if (!width) {
+ struct ::winsize ws;
+ if (::ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
+ width = optional< std::size_t >(ws.ws_col);
+ }
+
+ if (width && width.get() >= 80)
+ width.get() -= 5;
+
+ done = true;
+ }
+
+ return width;
+}
+
+
+/// Writes a line to stdout.
+///
+/// The line is wrapped to fit on screen.
+///
+/// \param message The line to print, without the trailing newline character.
+void
+cmdline::ui::out_wrap(const std::string& message)
+{
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width) {
+ const std::vector< std::string > lines = text::refill(
+ message, max_width.get());
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); iter++)
+ out(*iter);
+ } else
+ out(message);
+}
+
+
+/// Writes a line to stdout with a leading tag.
+///
+/// If the line does not fit on the current screen width, the line is broken
+/// into pieces and the tag is repeated on every line.
+///
+/// \param tag The leading line tag.
+/// \param message The message to be printed, without the trailing newline
+/// character.
+/// \param repeat If true, print the tag on every line; otherwise, indent the
+/// text of all lines to match the width of the tag on the first line.
+void
+cmdline::ui::out_tag_wrap(const std::string& tag, const std::string& message,
+ const bool repeat)
+{
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width && max_width.get() > tag.length()) {
+ const std::vector< std::string > lines = text::refill(
+ message, max_width.get() - tag.length());
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); iter++) {
+ if (repeat || iter == lines.begin())
+ out(F("%s%s") % tag % *iter);
+ else
+ out(F("%s%s") % std::string(tag.length(), ' ') % *iter);
+ }
+ } else {
+ out(F("%s%s") % tag % message);
+ }
+}
+
+
+/// Writes a table to stdout.
+///
+/// \param table The table to write.
+/// \param formatter The table formatter to use to convert the table to a
+/// console representation.
+/// \param prefix Text to prepend to all the lines of the output table.
+void
+cmdline::ui::out_table(const text::table& table,
+ text::table_formatter formatter,
+ const std::string& prefix)
+{
+ if (table.empty())
+ return;
+
+ const optional< std::size_t > max_width = screen_width();
+ if (max_width)
+ formatter.set_table_width(max_width.get() - prefix.length());
+
+ const std::vector< std::string > lines = formatter.format(table);
+ for (std::vector< std::string >::const_iterator iter = lines.begin();
+ iter != lines.end(); ++iter)
+ out(prefix + *iter);
+}
+
+
+/// Formats and prints an error message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_error(ui* ui_, const std::string& message)
+{
+ LE(message);
+ ui_->err(F("%s: E: %s") % cmdline::progname() % message);
+}
+
+
+/// Formats and prints an informational message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_info(ui* ui_, const std::string& message)
+{
+ LI(message);
+ ui_->err(F("%s: I: %s") % cmdline::progname() % message);
+}
+
+
+/// Formats and prints a warning message.
+///
+/// \param ui_ The user interface object used to print the message.
+/// \param message The message to print. Should not end with a newline
+/// character.
+void
+cmdline::print_warning(ui* ui_, const std::string& message)
+{
+ LW(message);
+ ui_->err(F("%s: W: %s") % cmdline::progname() % message);
+}
diff --git a/utils/cmdline/ui.hpp b/utils/cmdline/ui.hpp
new file mode 100644
index 000000000000..433bbe903b03
--- /dev/null
+++ b/utils/cmdline/ui.hpp
@@ -0,0 +1,79 @@
+// Copyright 2010 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/cmdline/ui.hpp
+/// Abstractions and utilities to write formatted messages to the console.
+
+#if !defined(UTILS_CMDLINE_UI_HPP)
+#define UTILS_CMDLINE_UI_HPP
+
+#include "utils/cmdline/ui_fwd.hpp"
+
+#include <cstddef>
+#include <string>
+
+#include "utils/optional_fwd.hpp"
+#include "utils/text/table_fwd.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Interface to interact with the CLI.
+///
+/// The main purpose of this class is to substitute direct usages of stdout and
+/// stderr. An instance of this class is passed to every command of a CLI,
+/// which allows unit testing and validation of the interaction with the user.
+///
+/// This class writes directly to stdout and stderr. For testing purposes, see
+/// the utils::cmdline::ui_mock class.
+class ui {
+public:
+ virtual ~ui(void);
+
+ virtual void err(const std::string&, const bool = true);
+ virtual void out(const std::string&, const bool = true);
+ virtual optional< std::size_t > screen_width(void) const;
+
+ void out_wrap(const std::string&);
+ void out_tag_wrap(const std::string&, const std::string&,
+ const bool = true);
+ void out_table(const utils::text::table&, utils::text::table_formatter,
+ const std::string&);
+};
+
+
+void print_error(ui*, const std::string&);
+void print_info(ui*, const std::string&);
+void print_warning(ui*, const std::string&);
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_UI_HPP)
diff --git a/utils/cmdline/ui_fwd.hpp b/utils/cmdline/ui_fwd.hpp
new file mode 100644
index 000000000000..4417beb1a8e8
--- /dev/null
+++ b/utils/cmdline/ui_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/cmdline/ui_fwd.hpp
+/// Forward declarations for utils/cmdline/ui.hpp
+
+#if !defined(UTILS_CMDLINE_UI_FWD_HPP)
+#define UTILS_CMDLINE_UI_FWD_HPP
+
+namespace utils {
+namespace cmdline {
+
+
+class ui;
+
+
+} // namespace cmdline
+} // namespace utils
+
+#endif // !defined(UTILS_CMDLINE_UI_FWD_HPP)
diff --git a/utils/cmdline/ui_mock.cpp b/utils/cmdline/ui_mock.cpp
new file mode 100644
index 000000000000..b77943cf147b
--- /dev/null
+++ b/utils/cmdline/ui_mock.cpp
@@ -0,0 +1,114 @@
+// Copyright 2010 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/cmdline/ui_mock.hpp"
+
+#include <iostream>
+
+#include "utils/optional.ipp"
+
+using utils::cmdline::ui_mock;
+using utils::none;
+using utils::optional;
+
+
+/// Constructs a new mock UI.
+///
+/// \param screen_width_ The width of the screen to use for testing purposes.
+/// Defaults to 0 to prevent uncontrolled wrapping on our tests.
+ui_mock::ui_mock(const std::size_t screen_width_) :
+ _screen_width(screen_width_)
+{
+}
+
+
+/// Writes a line to stderr and records it for further inspection.
+///
+/// \param message The line to print and record, without the trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+ui_mock::err(const std::string& message, const bool newline)
+{
+ if (newline)
+ std::cerr << message << "\n";
+ else {
+ std::cerr << message << "\n";
+ std::cerr.flush();
+ }
+ _err_log.push_back(message);
+}
+
+
+/// Writes a line to stdout and records it for further inspection.
+///
+/// \param message The line to print and record, without the trailing newline
+/// character.
+/// \param newline Whether to append a newline to the message or not.
+void
+ui_mock::out(const std::string& message, const bool newline)
+{
+ if (newline)
+ std::cout << message << "\n";
+ else {
+ std::cout << message << "\n";
+ std::cout.flush();
+ }
+ _out_log.push_back(message);
+}
+
+
+/// Queries the width of the screen.
+///
+/// \return Always none, as we do not want to depend on line wrapping in our
+/// tests.
+optional< std::size_t >
+ui_mock::screen_width(void) const
+{
+ return _screen_width > 0 ? optional< std::size_t >(_screen_width) : none;
+}
+
+
+/// Gets all the lines written to stderr.
+///
+/// \return The printed lines.
+const std::vector< std::string >&
+ui_mock::err_log(void) const
+{
+ return _err_log;
+}
+
+
+/// Gets all the lines written to stdout.
+///
+/// \return The printed lines.
+const std::vector< std::string >&
+ui_mock::out_log(void) const
+{
+ return _out_log;
+}
diff --git a/utils/cmdline/ui_mock.hpp b/utils/cmdline/ui_mock.hpp
new file mode 100644
index 000000000000..2c37683af7f3
--- /dev/null
+++ b/utils/cmdline/ui_mock.hpp
@@ -0,0 +1,78 @@
+// Copyright 2010 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/cmdline/ui_mock.hpp
+/// Provides the utils::cmdline::ui_mock class.
+///
+/// This file is only supposed to be included from test program, never from
+/// production code.
+
+#if !defined(UTILS_CMDLINE_UI_MOCK_HPP)
+#define UTILS_CMDLINE_UI_MOCK_HPP
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "utils/cmdline/ui.hpp"
+
+namespace utils {
+namespace cmdline {
+
+
+/// Testable interface to interact with the CLI.
+///
+/// This class records all writes to stdout and stderr to allow further
+/// inspection for testing purposes.
+class ui_mock : public ui {
+ /// Fake width of the screen; if 0, represents none.
+ std::size_t _screen_width;
+
+ /// Messages sent to stderr.
+ std::vector< std::string > _err_log;
+
+ /// Messages sent to stdout.
+ std::vector< std::string > _out_log;
+
+public:
+ ui_mock(const std::size_t = 0);
+
+ void err(const std::string&, const bool = true);
+ void out(const std::string&, const bool = true);
+ optional< std::size_t > screen_width(void) const;
+
+ const std::vector< std::string >& err_log(void) const;
+ const std::vector< std::string >& out_log(void) const;
+};
+
+
+} // namespace cmdline
+} // namespace utils
+
+
+#endif // !defined(UTILS_CMDLINE_UI_MOCK_HPP)
diff --git a/utils/cmdline/ui_test.cpp b/utils/cmdline/ui_test.cpp
new file mode 100644
index 000000000000..92c64baf95a3
--- /dev/null
+++ b/utils/cmdline/ui_test.cpp
@@ -0,0 +1,424 @@
+// Copyright 2011 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/cmdline/ui.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+extern "C" {
+#include <sys/param.h>
+#include <sys/ioctl.h>
+
+#include <fcntl.h>
+#if defined(HAVE_TERMIOS_H)
+# include <termios.h>
+#endif
+#include <unistd.h>
+}
+
+#include <cerrno>
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/cmdline/globals.hpp"
+#include "utils/cmdline/ui_mock.hpp"
+#include "utils/env.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/table.hpp"
+
+namespace cmdline = utils::cmdline;
+namespace text = utils::text;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Reopens stdout as a tty and returns its width.
+///
+/// \return The width of the tty in columns. If the width is wider than 80, the
+/// result is 5 columns narrower to match the screen_width() algorithm.
+static std::size_t
+reopen_stdout(void)
+{
+ const int fd = ::open("/dev/tty", O_WRONLY);
+ if (fd == -1)
+ ATF_SKIP(F("Cannot open tty for test: %s") % ::strerror(errno));
+ struct ::winsize ws;
+ if (::ioctl(fd, TIOCGWINSZ, &ws) == -1)
+ ATF_SKIP(F("Cannot determine size of tty: %s") % ::strerror(errno));
+
+ if (fd != STDOUT_FILENO) {
+ if (::dup2(fd, STDOUT_FILENO) == -1)
+ ATF_SKIP(F("Failed to redirect stdout: %s") % ::strerror(errno));
+ ::close(fd);
+ }
+
+ return ws.ws_col >= 80 ? ws.ws_col - 5 : ws.ws_col;
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_set__no_tty)
+{
+ utils::setenv("COLUMNS", "4321");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_set__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_set__tty)
+{
+ utils::setenv("COLUMNS", "4321");
+ (void)reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(4321 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__no_tty)
+{
+ utils::setenv("COLUMNS", "");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_empty__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_empty__tty)
+{
+ utils::setenv("COLUMNS", "");
+ const std::size_t columns = reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(columns, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__no_tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__no_tty)
+{
+ utils::setenv("COLUMNS", "foo bar");
+ ::close(STDOUT_FILENO);
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__columns_invalid__tty);
+ATF_TEST_CASE_BODY(ui__screen_width__columns_invalid__tty)
+{
+ utils::setenv("COLUMNS", "foo bar");
+ const std::size_t columns = reopen_stdout();
+
+ cmdline::ui ui;
+ ATF_REQUIRE_EQ(columns, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__tty_is_file);
+ATF_TEST_CASE_BODY(ui__screen_width__tty_is_file)
+{
+ utils::unsetenv("COLUMNS");
+ const int fd = ::open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ ATF_REQUIRE(fd != -1);
+ if (fd != STDOUT_FILENO) {
+ ATF_REQUIRE(::dup2(fd, STDOUT_FILENO) != -1);
+ ::close(fd);
+ }
+
+ cmdline::ui ui;
+ ATF_REQUIRE(!ui.screen_width());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__screen_width__cached);
+ATF_TEST_CASE_BODY(ui__screen_width__cached)
+{
+ cmdline::ui ui;
+
+ utils::setenv("COLUMNS", "100");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+
+ utils::setenv("COLUMNS", "80");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+
+ utils::unsetenv("COLUMNS");
+ ATF_REQUIRE_EQ(100 - 5, ui.screen_width().get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__err);
+ATF_TEST_CASE_BODY(ui__err)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.err("This is a short message");
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.err_log()[0]);
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__err__tolerates_newline);
+ATF_TEST_CASE_BODY(ui__err__tolerates_newline)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.err("This is a short message\n");
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("This is a short message\n", ui.err_log()[0]);
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out);
+ATF_TEST_CASE_BODY(ui__out)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.out("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out__tolerates_newline);
+ATF_TEST_CASE_BODY(ui__out__tolerates_newline)
+{
+ cmdline::ui_mock ui(10); // Keep shorter than message.
+ ui.out("This is a short message\n");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message\n", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__no_refill);
+ATF_TEST_CASE_BODY(ui__out_wrap__no_refill)
+{
+ cmdline::ui_mock ui(100);
+ ui.out_wrap("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_wrap__refill);
+ATF_TEST_CASE_BODY(ui__out_wrap__refill)
+{
+ cmdline::ui_mock ui(16);
+ ui.out_wrap("This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ("message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__no_refill);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__no_refill)
+{
+ cmdline::ui_mock ui(100);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__repeat);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__repeat)
+{
+ cmdline::ui_mock ui(32);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ("Some long tag: message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__refill__no_repeat);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__refill__no_repeat)
+{
+ cmdline::ui_mock ui(32);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message", false);
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(2, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short", ui.out_log()[0]);
+ ATF_REQUIRE_EQ(" message", ui.out_log()[1]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_tag_wrap__tag_too_long);
+ATF_TEST_CASE_BODY(ui__out_tag_wrap__tag_too_long)
+{
+ cmdline::ui_mock ui(5);
+ ui.out_tag_wrap("Some long tag: ", "This is a short message");
+ ATF_REQUIRE(ui.err_log().empty());
+ ATF_REQUIRE_EQ(1, ui.out_log().size());
+ ATF_REQUIRE_EQ("Some long tag: This is a short message", ui.out_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__empty);
+ATF_TEST_CASE_BODY(ui__out_table__empty)
+{
+ const text::table table(3);
+
+ text::table_formatter formatter;
+ formatter.set_separator(" | ");
+ formatter.set_column_width(0, 23);
+ formatter.set_column_width(1, text::table_formatter::width_refill);
+
+ cmdline::ui_mock ui(52);
+ ui.out_table(table, formatter, " ");
+ ATF_REQUIRE(ui.out_log().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(ui__out_table__not_empty);
+ATF_TEST_CASE_BODY(ui__out_table__not_empty)
+{
+ 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);
+ }
+
+ text::table_formatter formatter;
+ formatter.set_separator(" | ");
+ formatter.set_column_width(0, 23);
+ formatter.set_column_width(1, text::table_formatter::width_refill);
+
+ cmdline::ui_mock ui(52);
+ ui.out_table(table, formatter, " ");
+ ATF_REQUIRE_EQ(4, ui.out_log().size());
+ ATF_REQUIRE_EQ(" First | Second | Third",
+ ui.out_log()[0]);
+ ATF_REQUIRE_EQ(" Fourth with some text | Fifth with | Sixth foo",
+ ui.out_log()[1]);
+ ATF_REQUIRE_EQ(" | some more | ",
+ ui.out_log()[2]);
+ ATF_REQUIRE_EQ(" | text | ",
+ ui.out_log()[3]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_error);
+ATF_TEST_CASE_BODY(print_error)
+{
+ cmdline::init("error-program");
+ cmdline::ui_mock ui;
+ cmdline::print_error(&ui, "The error.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("error-program: E: The error.", ui.err_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_info);
+ATF_TEST_CASE_BODY(print_info)
+{
+ cmdline::init("info-program");
+ cmdline::ui_mock ui;
+ cmdline::print_info(&ui, "The info.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("info-program: I: The info.", ui.err_log()[0]);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(print_warning);
+ATF_TEST_CASE_BODY(print_warning)
+{
+ cmdline::init("warning-program");
+ cmdline::ui_mock ui;
+ cmdline::print_warning(&ui, "The warning.");
+ ATF_REQUIRE(ui.out_log().empty());
+ ATF_REQUIRE_EQ(1, ui.err_log().size());
+ ATF_REQUIRE_EQ("warning-program: W: The warning.", ui.err_log()[0]);
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_set__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_empty__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__no_tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__columns_invalid__tty);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__tty_is_file);
+ ATF_ADD_TEST_CASE(tcs, ui__screen_width__cached);
+
+ ATF_ADD_TEST_CASE(tcs, ui__err);
+ ATF_ADD_TEST_CASE(tcs, ui__err__tolerates_newline);
+ ATF_ADD_TEST_CASE(tcs, ui__out);
+ ATF_ADD_TEST_CASE(tcs, ui__out__tolerates_newline);
+
+ ATF_ADD_TEST_CASE(tcs, ui__out_wrap__no_refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_wrap__refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__no_refill);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__repeat);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__refill__no_repeat);
+ ATF_ADD_TEST_CASE(tcs, ui__out_tag_wrap__tag_too_long);
+ ATF_ADD_TEST_CASE(tcs, ui__out_table__empty);
+ ATF_ADD_TEST_CASE(tcs, ui__out_table__not_empty);
+
+ ATF_ADD_TEST_CASE(tcs, print_error);
+ ATF_ADD_TEST_CASE(tcs, print_info);
+ ATF_ADD_TEST_CASE(tcs, print_warning);
+}
diff --git a/utils/config/Kyuafile b/utils/config/Kyuafile
new file mode 100644
index 000000000000..c607a1757275
--- /dev/null
+++ b/utils/config/Kyuafile
@@ -0,0 +1,10 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="keys_test"}
+atf_test_program{name="lua_module_test"}
+atf_test_program{name="nodes_test"}
+atf_test_program{name="parser_test"}
+atf_test_program{name="tree_test"}
diff --git a/utils/config/Makefile.am.inc b/utils/config/Makefile.am.inc
new file mode 100644
index 000000000000..7c276ec4e798
--- /dev/null
+++ b/utils/config/Makefile.am.inc
@@ -0,0 +1,87 @@
+# 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.
+
+UTILS_CFLAGS += $(LUTOK_CFLAGS)
+UTILS_LIBS += $(LUTOK_LIBS)
+
+libutils_a_CPPFLAGS += $(LUTOK_CFLAGS)
+libutils_a_SOURCES += utils/config/exceptions.cpp
+libutils_a_SOURCES += utils/config/exceptions.hpp
+libutils_a_SOURCES += utils/config/keys.cpp
+libutils_a_SOURCES += utils/config/keys.hpp
+libutils_a_SOURCES += utils/config/keys_fwd.hpp
+libutils_a_SOURCES += utils/config/lua_module.cpp
+libutils_a_SOURCES += utils/config/lua_module.hpp
+libutils_a_SOURCES += utils/config/nodes.cpp
+libutils_a_SOURCES += utils/config/nodes.hpp
+libutils_a_SOURCES += utils/config/nodes.ipp
+libutils_a_SOURCES += utils/config/nodes_fwd.hpp
+libutils_a_SOURCES += utils/config/parser.cpp
+libutils_a_SOURCES += utils/config/parser.hpp
+libutils_a_SOURCES += utils/config/parser_fwd.hpp
+libutils_a_SOURCES += utils/config/tree.cpp
+libutils_a_SOURCES += utils/config/tree.hpp
+libutils_a_SOURCES += utils/config/tree.ipp
+libutils_a_SOURCES += utils/config/tree_fwd.hpp
+
+if WITH_ATF
+tests_utils_configdir = $(pkgtestsdir)/utils/config
+
+tests_utils_config_DATA = utils/config/Kyuafile
+EXTRA_DIST += $(tests_utils_config_DATA)
+
+tests_utils_config_PROGRAMS = utils/config/exceptions_test
+utils_config_exceptions_test_SOURCES = utils/config/exceptions_test.cpp
+utils_config_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/keys_test
+utils_config_keys_test_SOURCES = utils/config/keys_test.cpp
+utils_config_keys_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_keys_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/lua_module_test
+utils_config_lua_module_test_SOURCES = utils/config/lua_module_test.cpp
+utils_config_lua_module_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_lua_module_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/nodes_test
+utils_config_nodes_test_SOURCES = utils/config/nodes_test.cpp
+utils_config_nodes_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_nodes_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/parser_test
+utils_config_parser_test_SOURCES = utils/config/parser_test.cpp
+utils_config_parser_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_parser_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_config_PROGRAMS += utils/config/tree_test
+utils_config_tree_test_SOURCES = utils/config/tree_test.cpp
+utils_config_tree_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_config_tree_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/config/exceptions.cpp b/utils/config/exceptions.cpp
new file mode 100644
index 000000000000..e9afdf7ea6f7
--- /dev/null
+++ b/utils/config/exceptions.cpp
@@ -0,0 +1,149 @@
+// 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/config/exceptions.hpp"
+
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The key that caused the combination conflict.
+/// \param format The plain-text error message.
+config::bad_combination_error::bad_combination_error(
+ const detail::tree_key& key, const std::string& format) :
+ error(F(format.empty() ? "Combination conflict in key '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::bad_combination_error::~bad_combination_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::invalid_key_error::invalid_key_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_error::~invalid_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param message The plain-text error message.
+config::invalid_key_value::invalid_key_value(const detail::tree_key& key,
+ const std::string& message) :
+ error(F("Invalid value for property '%s': %s")
+ % detail::flatten_key(key) % message)
+{
+}
+
+
+/// Destructor for the error.
+config::invalid_key_value::~invalid_key_value(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::syntax_error::syntax_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::syntax_error::~syntax_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param key The unknown key.
+/// \param format The message for the error. Must include a single "%s"
+/// placedholder, which will be replaced by the key itself.
+config::unknown_key_error::unknown_key_error(const detail::tree_key& key,
+ const std::string& format) :
+ error(F(format.empty() ? "Unknown configuration property '%s'" : format) %
+ detail::flatten_key(key))
+{
+}
+
+
+/// Destructor for the error.
+config::unknown_key_error::~unknown_key_error(void) throw()
+{
+}
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+config::value_error::value_error(const std::string& message) :
+ error(message)
+{
+}
+
+
+/// Destructor for the error.
+config::value_error::~value_error(void) throw()
+{
+}
diff --git a/utils/config/exceptions.hpp b/utils/config/exceptions.hpp
new file mode 100644
index 000000000000..2096e67f43c8
--- /dev/null
+++ b/utils/config/exceptions.hpp
@@ -0,0 +1,106 @@
+// 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/config/exceptions.hpp
+/// Exception types raised by the config module.
+
+#if !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
+#define UTILS_CONFIG_EXCEPTIONS_HPP
+
+#include <stdexcept>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Base exceptions for config errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ ~error(void) throw();
+};
+
+
+/// Exception denoting that two trees cannot be combined.
+class bad_combination_error : public error {
+public:
+ explicit bad_combination_error(const detail::tree_key&,
+ const std::string&);
+ ~bad_combination_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class invalid_key_error : public error {
+public:
+ explicit invalid_key_error(const std::string&);
+ ~invalid_key_error(void) throw();
+};
+
+
+/// Exception denoting that a key was given an invalid value.
+class invalid_key_value : public error {
+public:
+ explicit invalid_key_value(const detail::tree_key&, const std::string&);
+ ~invalid_key_value(void) throw();
+};
+
+
+/// Exception denoting that a configuration file is invalid.
+class syntax_error : public error {
+public:
+ explicit syntax_error(const std::string&);
+ ~syntax_error(void) throw();
+};
+
+
+/// Exception denoting that a key was not found within a tree.
+class unknown_key_error : public error {
+public:
+ explicit unknown_key_error(const detail::tree_key&,
+ const std::string& = "");
+ ~unknown_key_error(void) throw();
+};
+
+
+/// Exception denoting that a value was invalid.
+class value_error : public error {
+public:
+ explicit value_error(const std::string&);
+ ~value_error(void) throw();
+};
+
+
+} // namespace config
+} // namespace utils
+
+
+#endif // !defined(UTILS_CONFIG_EXCEPTIONS_HPP)
diff --git a/utils/config/exceptions_test.cpp b/utils/config/exceptions_test.cpp
new file mode 100644
index 000000000000..a82fb9ea8f0c
--- /dev/null
+++ b/utils/config/exceptions_test.cpp
@@ -0,0 +1,133 @@
+// 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/config/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const config::error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bad_combination_error);
+ATF_TEST_CASE_BODY(bad_combination_error)
+{
+ detail::tree_key key;
+ key.push_back("first");
+ key.push_back("second");
+
+ const config::bad_combination_error e(key, "Failed to combine '%s'");
+ ATF_REQUIRE(std::strcmp("Failed to combine 'first.second'", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_error);
+ATF_TEST_CASE_BODY(invalid_key_error)
+{
+ const config::invalid_key_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key_value);
+ATF_TEST_CASE_BODY(invalid_key_value)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::invalid_key_value e(key, "foo bar");
+ ATF_REQUIRE(std::strcmp("Invalid value for property '1.two': foo bar",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_error);
+ATF_TEST_CASE_BODY(syntax_error)
+{
+ const config::syntax_error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__default_message);
+ATF_TEST_CASE_BODY(unknown_key_error__default_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key);
+ ATF_REQUIRE(std::strcmp("Unknown configuration property '1.two'",
+ e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key_error__custom_message);
+ATF_TEST_CASE_BODY(unknown_key_error__custom_message)
+{
+ detail::tree_key key;
+ key.push_back("1");
+ key.push_back("two");
+
+ const config::unknown_key_error e(key, "The test '%s' string");
+ ATF_REQUIRE(std::strcmp("The test '1.two' string", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ const config::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, bad_combination_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_error);
+ ATF_ADD_TEST_CASE(tcs, invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, syntax_error);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__default_message);
+ ATF_ADD_TEST_CASE(tcs, unknown_key_error__custom_message);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/keys.cpp b/utils/config/keys.cpp
new file mode 100644
index 000000000000..574eee14dcd2
--- /dev/null
+++ b/utils/config/keys.cpp
@@ -0,0 +1,70 @@
+// 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/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.hpp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+/// Converts a key to its textual representation.
+///
+/// \param key The key to convert.
+///
+/// \return a flattened representation of \p key, "."-joined.
+std::string
+utils::config::detail::flatten_key(const tree_key& key)
+{
+ PRE(!key.empty());
+ return text::join(key, ".");
+}
+
+
+/// Parses and validates a textual key.
+///
+/// \param str The key to process in dotted notation.
+///
+/// \return The tokenized key if valid.
+///
+/// \throw invalid_key_error If the input key is empty or invalid for any other
+/// reason. Invalid does NOT mean unknown though.
+utils::config::detail::tree_key
+utils::config::detail::parse_key(const std::string& str)
+{
+ const tree_key key = text::split(str, '.');
+ if (key.empty())
+ throw invalid_key_error("Empty key");
+ for (tree_key::const_iterator iter = key.begin(); iter != key.end(); iter++)
+ if ((*iter).empty())
+ throw invalid_key_error(F("Empty component in key '%s'") % str);
+ return key;
+}
diff --git a/utils/config/keys.hpp b/utils/config/keys.hpp
new file mode 100644
index 000000000000..ad258d69fc08
--- /dev/null
+++ b/utils/config/keys.hpp
@@ -0,0 +1,52 @@
+// 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/config/keys.hpp
+/// Representation and manipulation of tree keys.
+
+#if !defined(UTILS_CONFIG_KEYS_HPP)
+#define UTILS_CONFIG_KEYS_HPP
+
+#include "utils/config/keys_fwd.hpp"
+
+#include <string>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+std::string flatten_key(const tree_key&);
+tree_key parse_key(const std::string&);
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_HPP)
diff --git a/utils/config/keys_fwd.hpp b/utils/config/keys_fwd.hpp
new file mode 100644
index 000000000000..101272698b65
--- /dev/null
+++ b/utils/config/keys_fwd.hpp
@@ -0,0 +1,51 @@
+// 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/config/keys_fwd.hpp
+/// Forward declarations for utils/config/keys.hpp
+
+#if !defined(UTILS_CONFIG_KEYS_FWD_HPP)
+#define UTILS_CONFIG_KEYS_FWD_HPP
+
+#include <string>
+#include <vector>
+
+namespace utils {
+namespace config {
+namespace detail {
+
+
+/// Representation of a valid, tokenized key.
+typedef std::vector< std::string > tree_key;
+
+
+} // namespace detail
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_KEYS_FWD_HPP)
diff --git a/utils/config/keys_test.cpp b/utils/config/keys_test.cpp
new file mode 100644
index 000000000000..dc30f0fc8806
--- /dev/null
+++ b/utils/config/keys_test.cpp
@@ -0,0 +1,114 @@
+// 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/config/keys.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+
+namespace config = utils::config;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__one);
+ATF_TEST_CASE_BODY(flatten_key__one)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ ATF_REQUIRE_EQ("foo", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(flatten_key__many);
+ATF_TEST_CASE_BODY(flatten_key__many)
+{
+ config::detail::tree_key key;
+ key.push_back("foo");
+ key.push_back("1");
+ key.push_back("bar");
+ ATF_REQUIRE_EQ("foo.1.bar", config::detail::flatten_key(key));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__one);
+ATF_TEST_CASE_BODY(parse_key__one)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__many);
+ATF_TEST_CASE_BODY(parse_key__many)
+{
+ config::detail::tree_key exp_key;
+ exp_key.push_back("one");
+ exp_key.push_back("2");
+ exp_key.push_back("foo");
+ ATF_REQUIRE(exp_key == config::detail::parse_key("one.2.foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_key);
+ATF_TEST_CASE_BODY(parse_key__empty_key)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty key",
+ config::detail::parse_key(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(parse_key__empty_component);
+ATF_TEST_CASE_BODY(parse_key__empty_component)
+{
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.'",
+ config::detail::parse_key("."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a.'",
+ config::detail::parse_key("a."));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key '.b'",
+ config::detail::parse_key(".b"));
+ ATF_REQUIRE_THROW_RE(config::invalid_key_error,
+ "Empty component in key 'a..b'",
+ config::detail::parse_key("a..b"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, flatten_key__one);
+ ATF_ADD_TEST_CASE(tcs, flatten_key__many);
+
+ ATF_ADD_TEST_CASE(tcs, parse_key__one);
+ ATF_ADD_TEST_CASE(tcs, parse_key__many);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_key);
+ ATF_ADD_TEST_CASE(tcs, parse_key__empty_component);
+}
diff --git a/utils/config/lua_module.cpp b/utils/config/lua_module.cpp
new file mode 100644
index 000000000000..891f07302e0a
--- /dev/null
+++ b/utils/config/lua_module.cpp
@@ -0,0 +1,282 @@
+// 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/config/lua_module.hpp"
+
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/tree.ipp"
+
+namespace config = utils::config;
+namespace detail = utils::config::detail;
+
+
+namespace {
+
+
+/// Gets the tree singleton stored in the Lua state.
+///
+/// \param state The Lua state. The registry must contain a key named
+/// "tree" with a pointer to the singleton.
+///
+/// \return A reference to the tree associated with the Lua state.
+///
+/// \throw syntax_error If the tree cannot be located.
+config::tree&
+get_global_tree(lutok::state& state)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ state.get_table(-2);
+ if (state.is_nil(-1))
+ throw config::syntax_error("Cannot find tree singleton; global state "
+ "corrupted?");
+ config::tree& tree = **state.to_userdata< config::tree* >(-1);
+ state.pop(1);
+ return tree;
+}
+
+
+/// Gets a fully-qualified tree key from the state.
+///
+/// \param state The Lua state.
+/// \param table_index An index to the Lua stack pointing to the table being
+/// accessed. If this table contains a tree_key metadata property, this is
+/// considered to be the prefix of the tree key.
+/// \param field_index An index to the Lua stack pointing to the entry
+/// containing the name of the field being indexed.
+///
+/// \return A dotted key.
+///
+/// \throw invalid_key_error If the name of the key is invalid.
+static std::string
+get_tree_key(lutok::state& state, const int table_index, const int field_index)
+{
+ PRE(state.is_string(field_index));
+ const std::string field = state.to_string(field_index);
+ if (!field.empty() && field[0] == '_')
+ throw config::invalid_key_error(
+ F("Configuration key cannot have an underscore as a prefix; "
+ "found %s") % field);
+
+ std::string tree_key;
+ if (state.get_metafield(table_index, "tree_key")) {
+ tree_key = state.to_string(-1) + "." + state.to_string(field_index - 1);
+ state.pop(1);
+ } else
+ tree_key = state.to_string(field_index);
+ return tree_key;
+}
+
+
+static int redirect_newindex(lutok::state&);
+static int redirect_index(lutok::state&);
+
+
+/// Creates a table for a new configuration inner node.
+///
+/// \post state(-1) Contains the new table.
+///
+/// \param state The Lua state in which to push the table.
+/// \param tree_key The key to which the new table corresponds.
+static void
+new_table_for_key(lutok::state& state, const std::string& tree_key)
+{
+ state.new_table();
+ {
+ state.new_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+
+ state.push_string("tree_key");
+ state.push_string(tree_key);
+ state.set_table(-3);
+ }
+ state.set_metatable(-2);
+ }
+}
+
+
+/// Sets the value of an configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level.
+/// \pre state(-2) The field to index into the table. Must be a string.
+/// \pre state(-1) The value to set the indexed table field to.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 0.
+///
+/// \throw invalid_key_error If the provided key is invalid.
+/// \throw unknown_key_error If the key cannot be located.
+/// \throw value_error If the value has an unsupported type or cannot be
+/// set on the key, or if the input table or index are invalid.
+static int
+redirect_newindex(lutok::state& state)
+{
+ if (!state.is_table(-3))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-2))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ const std::string dotted_key = get_tree_key(state, -3, -2);
+ try {
+ config::tree& tree = get_global_tree(state);
+ tree.set_lua(dotted_key, state, -1);
+ } catch (const config::value_error& e) {
+ throw config::invalid_key_value(detail::parse_key(dotted_key),
+ e.what());
+ }
+
+ // Now really set the key in the Lua table, but prevent direct accesses from
+ // the user by prefixing it. We do this to ensure that re-setting the same
+ // key of the tree results in a call to __newindex instead of __index.
+ state.push_string("_" + state.to_string(-2));
+ state.push_value(-2);
+ state.raw_set(-5);
+
+ return 0;
+}
+
+
+/// Indexes a configuration node.
+///
+/// \pre state(-3) The table to index. If this is not _G, then the table
+/// metadata must contain a tree_key property describing the path to
+/// current level. If the field does not exist, a new table is created.
+/// \pre state(-1) The field to index into the table. Must be a string.
+///
+/// \param state The Lua state in which to operate.
+///
+/// \return The number of result values on the Lua stack; always 1.
+///
+/// \throw value_error If the input table or index are invalid.
+static int
+redirect_index(lutok::state& state)
+{
+ if (!state.is_table(-2))
+ throw config::value_error("Indexed object is not a table");
+ if (!state.is_string(-1))
+ throw config::value_error("Invalid field in configuration object "
+ "reference; must be a string");
+
+ // Query if the key has already been set by a call to redirect_newindex.
+ state.push_string("_" + state.to_string(-1));
+ state.raw_get(-3);
+ if (!state.is_nil(-1))
+ return 1;
+ state.pop(1);
+
+ state.push_value(-1); // Duplicate the field name.
+ state.raw_get(-3); // Get table[field] to see if it's defined.
+ if (state.is_nil(-1)) {
+ state.pop(1);
+
+ // The stack is now the same as when we entered the function, but we
+ // know that the field is undefined and thus have to create a new
+ // configuration table.
+ INV(state.is_table(-2));
+ INV(state.is_string(-1));
+
+ const config::tree& tree = get_global_tree(state);
+ const std::string tree_key = get_tree_key(state, -2, -1);
+ if (tree.is_set(tree_key)) {
+ // Publish the pre-recorded value in the tree to the Lua state,
+ // instead of considering this table key a new inner node.
+ tree.push_lua(tree_key, state);
+ } else {
+ state.push_string("_" + state.to_string(-1));
+ state.insert(-2);
+ state.pop(1);
+
+ new_table_for_key(state, tree_key);
+
+ // Duplicate the newly created table and place it deep in the stack
+ // so that the raw_set below leaves us with the return value of this
+ // function at the top of the stack.
+ state.push_value(-1);
+ state.insert(-4);
+
+ state.raw_set(-3);
+ state.pop(1);
+ }
+ }
+ return 1;
+}
+
+
+} // anonymous namespace
+
+
+/// Install wrappers for globals to set values in the configuration tree.
+///
+/// This function installs wrappers to capture all accesses to global variables.
+/// Such wrappers redirect the reads and writes to the out_tree, which is the
+/// entity that defines what configuration variables exist.
+///
+/// \param state The Lua state into which to install the wrappers.
+/// \param out_tree The tree with the layout definition and where the
+/// configuration settings will be collected.
+void
+config::redirect(lutok::state& state, tree& out_tree)
+{
+ lutok::stack_cleaner cleaner(state);
+
+ state.get_global_table();
+ {
+ state.push_string("__index");
+ state.push_cxx_function(redirect_index);
+ state.set_table(-3);
+
+ state.push_string("__newindex");
+ state.push_cxx_function(redirect_newindex);
+ state.set_table(-3);
+ }
+ state.set_metatable(-1);
+
+ state.push_value(lutok::registry_index);
+ state.push_string("tree");
+ config::tree** tree = state.new_userdata< config::tree* >();
+ *tree = &out_tree;
+ state.set_table(-3);
+ state.pop(1);
+}
diff --git a/utils/config/lua_module.hpp b/utils/config/lua_module.hpp
new file mode 100644
index 000000000000..7f0d5d0b4c5f
--- /dev/null
+++ b/utils/config/lua_module.hpp
@@ -0,0 +1,50 @@
+// 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/config/lua_module.hpp
+/// Bindings to expose a configuration tree to Lua.
+
+#if !defined(UTILS_CONFIG_LUA_MODULE_HPP)
+#define UTILS_CONFIG_LUA_MODULE_HPP
+
+#include <string>
+
+#include "lutok/state.hpp"
+#include "utils/config/tree_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+void redirect(lutok::state&, tree&);
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_LUA_MODULE_HPP)
diff --git a/utils/config/lua_module_test.cpp b/utils/config/lua_module_test.cpp
new file mode 100644
index 000000000000..484d129c4021
--- /dev/null
+++ b/utils/config/lua_module_test.cpp
@@ -0,0 +1,474 @@
+// 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/config/lua_module.hpp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/tree.ipp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Non-native type to use as a leaf node.
+struct custom_type {
+ /// The value recorded in the object.
+ int value;
+
+ /// Constructs a new object.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit custom_type(const int value_) :
+ value(value_)
+ {
+ }
+};
+
+
+/// Custom implementation of a node type for testing purposes.
+class custom_node : public config::typed_leaf_node< custom_type > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< custom_node > new_node(new custom_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(value().value * 5);
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ set(custom_type(state.to_integer(value_index) * 2));
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ void
+ set_string(const std::string& /* raw_value */)
+ {
+ ATF_FAIL("Should not be used");
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \post The test case is marked as failed, as this function is not
+ /// supposed to be invoked by the lua_module code.
+ ///
+ /// \return Nothing.
+ std::string
+ to_string(void) const
+ {
+ ATF_FAIL("Should not be used");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__valid_types);
+ATF_TEST_CASE_BODY(top__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+ tree.define< config::string_node >("top_string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 12345\n"
+ "top_string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("top_integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("top_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__invalid_types);
+ATF_TEST_CASE_BODY(top__invalid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("top_boolean");
+ tree.define< config::int_node >("top_integer");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(
+ lutok::error,
+ "Invalid value for property 'top_boolean': Not a boolean",
+ lutok::do_string(state,
+ "top_boolean = true\n"
+ "top_integer = 8\n"
+ "top_boolean = 'foo'\n",
+ 0, 0, 0));
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("top_boolean"));
+ ATF_REQUIRE_EQ(8, tree.lookup< config::int_node >("top_integer"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reuse);
+ATF_TEST_CASE_BODY(top__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.define< config::int_node >("second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; second = first * 2", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__reset);
+ATF_TEST_CASE_BODY(top__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = 100; first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(top__already_set_on_entry);
+ATF_TEST_CASE_BODY(top__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("first");
+ tree.set< config::int_node >("first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "first = first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__valid_types);
+ATF_TEST_CASE_BODY(subtree__valid_types)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("root.boolean");
+ tree.define< config::int_node >("root.a.integer");
+ tree.define< config::string_node >("root.string");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.boolean = true\n"
+ "root.a.integer = 12345\n"
+ "root.string = 'a foo'\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(true, tree.lookup< config::bool_node >("root.boolean"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("root.a.integer"));
+ ATF_REQUIRE_EQ("a foo", tree.lookup< config::string_node >("root.string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reuse);
+ATF_TEST_CASE_BODY(subtree__reuse)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.define< config::int_node >("a.second");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.second = a.first * 2",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(100, tree.lookup< config::int_node >("a.first"));
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.second"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__reset);
+ATF_TEST_CASE_BODY(subtree__reset)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = 100; a.first = 200", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(200, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__already_set_on_entry);
+ATF_TEST_CASE_BODY(subtree__already_set_on_entry)
+{
+ config::tree tree;
+ tree.define< config::int_node >("a.first");
+ tree.set< config::int_node >("a.first", 100);
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "a.first = a.first * 15", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1500, tree.lookup< config::int_node >("a.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(subtree__override_inner);
+ATF_TEST_CASE_BODY(subtree__override_inner)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "root.test = 'a'", 0, 0, 0);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Invalid value for property 'root'",
+ lutok::do_string(state, "root = 'b'", 0, 0, 0));
+ // Ensure that the previous assignment to 'root' did not cause any
+ // inconsistencies in the environment that would prevent a new
+ // assignment from working.
+ lutok::do_string(state, "root.test2 = 'c'", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ("a", tree.lookup< config::string_node >("root.test"));
+ ATF_REQUIRE_EQ("c", tree.lookup< config::string_node >("root.test2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__strings);
+ATF_TEST_CASE_BODY(dynamic_subtree__strings)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "root.key1 = 1234\n"
+ "root.a.b.key2 = 'foo bar'\n",
+ 0, 0, 0);
+
+ ATF_REQUIRE_EQ("1234", tree.lookup< config::string_node >("root.key1"));
+ ATF_REQUIRE_EQ("foo bar",
+ tree.lookup< config::string_node >("root.a.b.key2"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(dynamic_subtree__invalid_types);
+ATF_TEST_CASE_BODY(dynamic_subtree__invalid_types)
+{
+ config::tree tree;
+ tree.define_dynamic("root");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.boolean': "
+ "Not a string",
+ lutok::do_string(state, "root.boolean = true",
+ 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'root.table': "
+ "Not a string",
+ lutok::do_string(state, "root.table = {}",
+ 0, 0, 0));
+ ATF_REQUIRE(!tree.is_set("root.boolean"));
+ ATF_REQUIRE(!tree.is_set("root.table"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(locals);
+ATF_TEST_CASE_BODY(locals)
+{
+ config::tree tree;
+ tree.define< config::int_node >("the_key");
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state,
+ "local function generate()\n"
+ " return 15\n"
+ "end\n"
+ "local test_var = 20\n"
+ "the_key = generate() + test_var\n",
+ 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(35, tree.lookup< config::int_node >("the_key"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_node);
+ATF_TEST_CASE_BODY(custom_node)
+{
+ config::tree tree;
+ tree.define< custom_node >("key1");
+ tree.define< custom_node >("key2");
+ tree.set< custom_node >("key2", custom_type(10));
+
+ {
+ lutok::state state;
+ config::redirect(state, tree);
+ lutok::do_string(state, "key1 = 512\n", 0, 0, 0);
+ lutok::do_string(state, "key2 = key2 * 2\n", 0, 0, 0);
+ }
+
+ ATF_REQUIRE_EQ(1024, tree.lookup< custom_node >("key1").value);
+ ATF_REQUIRE_EQ(200, tree.lookup< custom_node >("key2").value);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_key);
+ATF_TEST_CASE_BODY(invalid_key)
+{
+ config::tree tree;
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error, "Empty component in key 'root.'",
+ lutok::do_string(state, "root['']['a'] = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unknown_key);
+ATF_TEST_CASE_BODY(unknown_key)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("static.bool");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Unknown configuration property 'static.int'",
+ lutok::do_string(state,
+ "static.int = 12345\n",
+ 0, 0, 0));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(value_error);
+ATF_TEST_CASE_BODY(value_error)
+{
+ config::tree tree;
+ tree.define< config::bool_node >("a.b");
+
+ lutok::state state;
+ config::redirect(state, tree);
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a.b': Not a boolean",
+ lutok::do_string(state, "a.b = 12345\n", 0, 0, 0));
+ ATF_REQUIRE_THROW_RE(lutok::error,
+ "Invalid value for property 'a': ",
+ lutok::do_string(state, "a = 1\n", 0, 0, 0));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, top__valid_types);
+ ATF_ADD_TEST_CASE(tcs, top__invalid_types);
+ ATF_ADD_TEST_CASE(tcs, top__reuse);
+ ATF_ADD_TEST_CASE(tcs, top__reset);
+ ATF_ADD_TEST_CASE(tcs, top__already_set_on_entry);
+
+ ATF_ADD_TEST_CASE(tcs, subtree__valid_types);
+ ATF_ADD_TEST_CASE(tcs, subtree__reuse);
+ ATF_ADD_TEST_CASE(tcs, subtree__reset);
+ ATF_ADD_TEST_CASE(tcs, subtree__already_set_on_entry);
+ ATF_ADD_TEST_CASE(tcs, subtree__override_inner);
+
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__strings);
+ ATF_ADD_TEST_CASE(tcs, dynamic_subtree__invalid_types);
+
+ ATF_ADD_TEST_CASE(tcs, locals);
+ ATF_ADD_TEST_CASE(tcs, custom_node);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_key);
+ ATF_ADD_TEST_CASE(tcs, unknown_key);
+ ATF_ADD_TEST_CASE(tcs, value_error);
+}
diff --git a/utils/config/nodes.cpp b/utils/config/nodes.cpp
new file mode 100644
index 000000000000..1c6e848daf07
--- /dev/null
+++ b/utils/config/nodes.cpp
@@ -0,0 +1,589 @@
+// 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/config/nodes.ipp"
+
+#include <memory>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Destructor.
+config::detail::base_node::~base_node(void)
+{
+}
+
+
+/// Constructor.
+///
+/// \param dynamic_ Whether the node is dynamic or not.
+config::detail::inner_node::inner_node(const bool dynamic_) :
+ _dynamic(dynamic_)
+{
+}
+
+
+/// Destructor.
+config::detail::inner_node::~inner_node(void)
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter)
+ delete (*iter).second;
+}
+
+
+/// Fills the given node with a copy of this node's data.
+///
+/// \param node The node to fill. Should be the fresh return value of a
+/// deep_copy() operation.
+void
+config::detail::inner_node::copy_into(inner_node* node) const
+{
+ node->_dynamic = _dynamic;
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ base_node* new_node = (*iter).second->deep_copy();
+ try {
+ node->_children[(*iter).first] = new_node;
+ } catch (...) {
+ delete new_node;
+ throw;
+ }
+ }
+}
+
+
+/// Combines two children sets, preferring the keys in the first set only.
+///
+/// This operation is not symmetrical on c1 and c2. The caller is responsible
+/// for invoking this twice so that the two key sets are combined if they happen
+/// to differ.
+///
+/// \param key Key to this node.
+/// \param c1 First children set.
+/// \param c2 First children set.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_children_into(
+ const tree_key& key,
+ const children_map& c1, const children_map& c2,
+ inner_node* node) const
+{
+ for (children_map::const_iterator iter1 = c1.begin();
+ iter1 != c1.end(); ++iter1) {
+ const std::string& name = (*iter1).first;
+
+ if (node->_children.find(name) != node->_children.end()) {
+ continue;
+ }
+
+ std::auto_ptr< base_node > new_node;
+
+ children_map::const_iterator iter2 = c2.find(name);
+ if (iter2 == c2.end()) {
+ new_node.reset((*iter1).second->deep_copy());
+ } else {
+ tree_key child_key = key;
+ child_key.push_back(name);
+ new_node.reset((*iter1).second->combine(child_key,
+ (*iter2).second));
+ }
+
+ node->_children[name] = new_node.release();
+ }
+}
+
+
+/// Combines this inner node with another inner node onto a new node.
+///
+/// The "dynamic" property is inherited by the new node if either of the two
+/// nodes are dynamic.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+/// \param [in,out] node The node to combine into.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+void
+config::detail::inner_node::combine_into(const tree_key& key,
+ const base_node* other_base,
+ inner_node* node) const
+{
+ try {
+ const inner_node& other = dynamic_cast< const inner_node& >(
+ *other_base);
+
+ node->_dynamic = _dynamic || other._dynamic;
+
+ combine_children_into(key, _children, other._children, node);
+ combine_children_into(key, other._children, _children, node);
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is an inner node in the base tree but a leaf node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Finds a node without creating it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key.
+/// The returned node is constant, so this can only be used for querying
+/// purposes. For this reason, this algorithm does not create intermediate
+/// nodes if they don't exist (as would be necessary to set a new node).
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw unknown_key_error If the provided key is unknown.
+const config::detail::base_node*
+config::detail::inner_node::lookup_ro(const tree_key& key,
+ const tree_key::size_type key_pos) const
+{
+ PRE(key_pos < key.size());
+
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+ if (child_iter == _children.end())
+ throw unknown_key_error(key);
+
+ if (key_pos == key.size() - 1) {
+ return (*child_iter).second;
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ const inner_node& child = dynamic_cast< const inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_ro(key, key_pos + 1);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Finds a node and creates it if not found.
+///
+/// This recursive algorithm traverses the tree searching for a particular key,
+/// creating any intermediate nodes if they do not already exist (for the case
+/// of dynamic inner nodes). The returned node is non-constant, so this can be
+/// used by the algorithms that set key values.
+///
+/// \param key The key to be queried.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type. This is only called if the leaf cannot be found, but it has
+/// already been defined.
+///
+/// \return A reference to the located node, if successful.
+///
+/// \throw invalid_key_value If the resulting node of the search would be an
+/// inner node.
+/// \throw unknown_key_error If the provided key is unknown.
+config::leaf_node*
+config::detail::inner_node::lookup_rw(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ children_map::const_iterator child_iter = _children.find(key[key_pos]);
+ if (child_iter == _children.end()) {
+ if (_dynamic) {
+ base_node* const child = (key_pos == key.size() - 1) ?
+ static_cast< base_node* >(new_node()) :
+ static_cast< base_node* >(new dynamic_inner_node());
+ _children.insert(children_map::value_type(key[key_pos], child));
+ child_iter = _children.find(key[key_pos]);
+ } else {
+ throw unknown_key_error(key);
+ }
+ }
+
+ if (key_pos == key.size() - 1) {
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(
+ *(*child_iter).second);
+ return &child;
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+ } else {
+ PRE(key_pos < key.size() - 1);
+ try {
+ inner_node& child = dynamic_cast< inner_node& >(
+ *(*child_iter).second);
+ return child.lookup_rw(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ throw unknown_key_error(
+ key, "Cannot address incomplete configuration property '%s'");
+ }
+ }
+}
+
+
+/// Converts the subtree to a collection of key/value string pairs.
+///
+/// \param [out] properties The accumulator for the generated properties. The
+/// contents of the map are only extended.
+/// \param key The path to the current node.
+void
+config::detail::inner_node::all_properties(properties_map& properties,
+ const tree_key& key) const
+{
+ for (children_map::const_iterator iter = _children.begin();
+ iter != _children.end(); ++iter) {
+ tree_key child_key = key;
+ child_key.push_back((*iter).first);
+ try {
+ leaf_node& child = dynamic_cast< leaf_node& >(*(*iter).second);
+ if (child.is_set())
+ properties[flatten_key(child_key)] = child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ inner_node& child = dynamic_cast< inner_node& >(*(*iter).second);
+ child.all_properties(properties, child_key);
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::static_inner_node::static_inner_node(void) :
+ inner_node(false)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::static_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::static_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new static_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param key The key to be registered.
+/// \param key_pos The current level within the key to be examined.
+/// \param new_node A function that returns a new leaf node of the desired
+/// type.
+void
+config::detail::static_inner_node::define(const tree_key& key,
+ const tree_key::size_type key_pos,
+ new_node_hook new_node)
+{
+ PRE(key_pos < key.size());
+
+ if (key_pos == key.size() - 1) {
+ PRE_MSG(_children.find(key[key_pos]) == _children.end(),
+ "Key already defined");
+ _children.insert(children_map::value_type(key[key_pos], new_node()));
+ } else {
+ PRE(key_pos < key.size() - 1);
+ const children_map::const_iterator child_iter = _children.find(
+ key[key_pos]);
+
+ if (child_iter == _children.end()) {
+ static_inner_node* const child_ptr = new static_inner_node();
+ _children.insert(children_map::value_type(key[key_pos], child_ptr));
+ child_ptr->define(key, key_pos + 1, new_node);
+ } else {
+ try {
+ static_inner_node& child = dynamic_cast< static_inner_node& >(
+ *(*child_iter).second);
+ child.define(key, key_pos + 1, new_node);
+ } catch (const std::bad_cast& e) {
+ UNREACHABLE;
+ }
+ }
+ }
+}
+
+
+/// Constructor.
+config::detail::dynamic_inner_node::dynamic_inner_node(void) :
+ inner_node(true)
+{
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::detail::dynamic_inner_node::deep_copy(void) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ copy_into(new_node.get());
+ return new_node.release();
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::detail::dynamic_inner_node::combine(const tree_key& key,
+ const base_node* other) const
+{
+ std::auto_ptr< inner_node > new_node(new dynamic_inner_node());
+ combine_into(key, other, new_node.get());
+ return new_node.release();
+}
+
+
+/// Destructor.
+config::leaf_node::~leaf_node(void)
+{
+}
+
+
+/// Combines this node with another one.
+///
+/// \param key Key to this node.
+/// \param other_base The node to combine with.
+///
+/// \return A new node representing the combination.
+///
+/// \throw bad_combination_error If the two nodes cannot be combined.
+config::detail::base_node*
+config::leaf_node::combine(const detail::tree_key& key,
+ const base_node* other_base) const
+{
+ try {
+ const leaf_node& other = dynamic_cast< const leaf_node& >(*other_base);
+
+ if (other.is_set()) {
+ return other.deep_copy();
+ } else {
+ return deep_copy();
+ }
+ } catch (const std::bad_cast& unused_e) {
+ throw config::bad_combination_error(
+ key, "'%s' is a leaf node in the base tree but an inner node in "
+ "the overrides treee");
+ }
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::bool_node::deep_copy(void) const
+{
+ std::auto_ptr< bool_node > new_node(new bool_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::bool_node::push_lua(lutok::state& state) const
+{
+ state.push_boolean(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::bool_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_boolean(value_index))
+ set(state.to_boolean(value_index));
+ else
+ throw value_error("Not a boolean");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::int_node::deep_copy(void) const
+{
+ std::auto_ptr< int_node > new_node(new int_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::int_node::push_lua(lutok::state& state) const
+{
+ state.push_integer(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::int_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_number(value_index))
+ set(state.to_integer(value_index));
+ else
+ throw value_error("Not an integer");
+}
+
+
+/// Checks a given value for validity.
+///
+/// \param new_value The value to validate.
+///
+/// \throw value_error If the value is not valid.
+void
+config::positive_int_node::validate(const value_type& new_value) const
+{
+ if (new_value <= 0)
+ throw value_error("Must be a positive integer");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::string_node::deep_copy(void) const
+{
+ std::auto_ptr< string_node > new_node(new string_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+///
+/// \param state The Lua state onto which to push the value.
+void
+config::string_node::push_lua(lutok::state& state) const
+{
+ state.push_string(value());
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \param state The Lua state from which to get the value.
+/// \param value_index The stack index in which the value resides.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+void
+config::string_node::set_lua(lutok::state& state, const int value_index)
+{
+ if (state.is_string(value_index))
+ set(state.to_string(value_index));
+ else
+ throw value_error("Not a string");
+}
+
+
+/// Copies the node.
+///
+/// \return A dynamically-allocated node.
+config::detail::base_node*
+config::strings_set_node::deep_copy(void) const
+{
+ std::auto_ptr< strings_set_node > new_node(new strings_set_node());
+ new_node->_value = _value;
+ return new_node.release();
+}
+
+
+/// Converts a single word to the native type.
+///
+/// \param raw_value The value to parse.
+///
+/// \return The parsed value.
+std::string
+config::strings_set_node::parse_one(const std::string& raw_value) const
+{
+ return raw_value;
+}
diff --git a/utils/config/nodes.hpp b/utils/config/nodes.hpp
new file mode 100644
index 000000000000..6b766ff5d8f7
--- /dev/null
+++ b/utils/config/nodes.hpp
@@ -0,0 +1,272 @@
+// 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/config/nodes.hpp
+/// Representation of tree nodes.
+
+#if !defined(UTILS_CONFIG_NODES_HPP)
+#define UTILS_CONFIG_NODES_HPP
+
+#include "utils/config/nodes_fwd.hpp"
+
+#include <set>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+#include "utils/noncopyable.hpp"
+#include "utils/optional.hpp"
+
+namespace utils {
+namespace config {
+
+
+namespace detail {
+
+
+/// Base representation of a node.
+///
+/// This abstract class provides the base type for every node in the tree. Due
+/// to the dynamic nature of our trees (each leaf being able to hold arbitrary
+/// data types), this base type is a necessity.
+class base_node : noncopyable {
+public:
+ virtual ~base_node(void) = 0;
+
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node* deep_copy(void) const = 0;
+
+ /// Combines this node with another one.
+ ///
+ /// \param key Key to this node.
+ /// \param other The node to combine with.
+ ///
+ /// \return A new node representing the combination.
+ ///
+ /// \throw bad_combination_error If the two nodes cannot be combined.
+ virtual base_node* combine(const tree_key& key, const base_node* other)
+ const = 0;
+};
+
+
+} // namespace detail
+
+
+/// Abstract leaf node without any specified type.
+///
+/// This base abstract type is necessary to have a common pointer type to which
+/// to cast any leaf. We later provide templated derivates of this class, and
+/// those cannot act in this manner.
+///
+/// It is important to understand that a leaf can exist without actually holding
+/// a value. Our trees are "strictly keyed": keys must have been pre-defined
+/// before a value can be set on them. This is to ensure that the end user is
+/// using valid key names and not making mistakes due to typos, for example. To
+/// represent this condition, we define an "empty" key in the tree to denote
+/// that the key is valid, yet it has not been set by the user. Only when an
+/// explicit set is performed on the key, it gets a value.
+class leaf_node : public detail::base_node {
+public:
+ virtual ~leaf_node(void);
+
+ virtual bool is_set(void) const = 0;
+
+ base_node* combine(const detail::tree_key&, const base_node*) const;
+
+ virtual void push_lua(lutok::state&) const = 0;
+ virtual void set_lua(lutok::state&, const int) = 0;
+
+ virtual void set_string(const std::string&) = 0;
+ virtual std::string to_string(void) const = 0;
+};
+
+
+/// Base leaf node for a single arbitrary type.
+///
+/// This templated leaf node holds a single object of any type. The conversion
+/// to/from string representations is undefined, as that depends on the
+/// particular type being processed. You should reimplement this class for any
+/// type that needs additional processing/validation during conversion.
+template< typename ValueType >
+class typed_leaf_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef ValueType value_type;
+
+ /// Constructs a new leaf node that contains no value.
+ typed_leaf_node(void);
+
+ /// Checks whether the node has been set by the user.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ virtual void validate(const value_type&) const;
+};
+
+
+/// Leaf node holding a native type.
+///
+/// This templated leaf node holds a native type. The conversion to/from string
+/// representations of the value happens by means of iostreams.
+template< typename ValueType >
+class native_leaf_node : public typed_leaf_node< ValueType > {
+public:
+ void set_string(const std::string&);
+ std::string to_string(void) const;
+};
+
+
+/// A leaf node that holds a boolean value.
+class bool_node : public native_leaf_node< bool > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds an integer value.
+class int_node : public native_leaf_node< int > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// A leaf node that holds a positive non-zero integer value.
+class positive_int_node : public int_node {
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a string value.
+class string_node : public native_leaf_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+ void push_lua(lutok::state&) const;
+ void set_lua(lutok::state&, const int);
+};
+
+
+/// Base leaf node for a set of native types.
+///
+/// This is a base abstract class because there is no generic way to parse a
+/// single word in the textual representation of the set to the native value.
+template< typename ValueType >
+class base_set_node : public leaf_node {
+public:
+ /// The type of the value held by this node.
+ typedef std::set< ValueType > value_type;
+
+ base_set_node(void);
+
+ /// Checks whether the node has been set by the user.
+ ///
+ /// \return True if a value has been set in the node.
+ bool is_set(void) const;
+
+ /// Gets the value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ const value_type& value(void) const;
+
+ /// Gets the read-write value stored in the node.
+ ///
+ /// \pre The node must have a value.
+ ///
+ /// \return The value in the node.
+ value_type& value(void);
+
+ /// Sets the value of the node.
+ void set(const value_type&);
+
+ /// Sets the value of the node from a raw string representation.
+ void set_string(const std::string&);
+
+ /// Converts the contents of the node to a string.
+ std::string to_string(void) const;
+
+ /// Pushes the node's value onto the Lua stack.
+ void push_lua(lutok::state&) const;
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ void set_lua(lutok::state&, const int);
+
+protected:
+ /// The value held by this node.
+ optional< value_type > _value;
+
+private:
+ /// Converts a single word to the native type.
+ ///
+ /// \return The parsed value.
+ ///
+ /// \throw value_error If the value is invalid.
+ virtual ValueType parse_one(const std::string&) const = 0;
+
+ virtual void validate(const value_type&) const;
+};
+
+
+/// A leaf node that holds a set of strings.
+class strings_set_node : public base_set_node< std::string > {
+public:
+ virtual base_node* deep_copy(void) const;
+
+private:
+ std::string parse_one(const std::string&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_HPP)
diff --git a/utils/config/nodes.ipp b/utils/config/nodes.ipp
new file mode 100644
index 000000000000..9e0a1228cccd
--- /dev/null
+++ b/utils/config/nodes.ipp
@@ -0,0 +1,408 @@
+// 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/config/nodes.hpp"
+
+#if !defined(UTILS_CONFIG_NODES_IPP)
+#define UTILS_CONFIG_NODES_IPP
+
+#include <memory>
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/defs.hpp"
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+namespace config {
+namespace detail {
+
+
+/// Type of the new_node() family of functions.
+typedef base_node* (*new_node_hook)(void);
+
+
+/// Creates a new leaf node of a given type.
+///
+/// \tparam NodeType The type of the leaf node to create.
+///
+/// \return A pointer to the newly-created node.
+template< class NodeType >
+base_node*
+new_node(void)
+{
+ return new NodeType();
+}
+
+
+/// Internal node of the tree.
+///
+/// This abstract base class provides the mechanism to implement both static and
+/// dynamic nodes. Ideally, the implementation would be split in subclasses and
+/// this class would not include the knowledge of whether the node is dynamic or
+/// not. However, because the static/dynamic difference depends on the leaf
+/// types, we need to declare template functions and these cannot be virtual.
+class inner_node : public base_node {
+ /// Whether the node is dynamic or not.
+ bool _dynamic;
+
+protected:
+ /// Type to represent the collection of children of this node.
+ ///
+ /// Note that these are one-level keys. They cannot contain dots, and thus
+ /// is why we use a string rather than a tree_key.
+ typedef std::map< std::string, base_node* > children_map;
+
+ /// Mapping of keys to values that are descendants of this node.
+ children_map _children;
+
+ void copy_into(inner_node*) const;
+ void combine_into(const tree_key&, const base_node*, inner_node*) const;
+
+private:
+ void combine_children_into(const tree_key&,
+ const children_map&, const children_map&,
+ inner_node*) const;
+
+public:
+ inner_node(const bool);
+ virtual ~inner_node(void) = 0;
+
+ const base_node* lookup_ro(const tree_key&,
+ const tree_key::size_type) const;
+ leaf_node* lookup_rw(const tree_key&, const tree_key::size_type,
+ new_node_hook);
+
+ void all_properties(properties_map&, const tree_key&) const;
+};
+
+
+/// Static internal node of the tree.
+///
+/// The direct children of this node must be pre-defined by calls to define().
+/// Attempts to traverse this node and resolve a key that is not a pre-defined
+/// children will result in an "unknown key" error.
+class static_inner_node : public config::detail::inner_node {
+public:
+ static_inner_node(void);
+
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ void define(const tree_key&, const tree_key::size_type, new_node_hook);
+};
+
+
+/// Dynamic internal node of the tree.
+///
+/// The children of this node need not be pre-defined. Attempts to traverse
+/// this node and resolve a key will result in such key being created. Any
+/// intermediate non-existent nodes of the traversal will be created as dynamic
+/// inner nodes as well.
+class dynamic_inner_node : public config::detail::inner_node {
+public:
+ virtual base_node* deep_copy(void) const;
+ virtual base_node* combine(const tree_key&, const base_node*) const;
+
+ dynamic_inner_node(void);
+};
+
+
+} // namespace detail
+} // namespace config
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::typed_leaf_node< ValueType >::typed_leaf_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set by the user.
+///
+/// Nodes of the tree are predefined by the caller to specify the valid
+/// types of the leaves. Such predefinition results in the creation of
+/// nodes within the tree, but these nodes have not yet been set.
+/// Traversing these nodes is invalid and should result in an "unknown key"
+/// error.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::typed_leaf_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::typed_leaf_node< ValueType >::value_type&
+config::typed_leaf_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::typed_leaf_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::native_leaf_node< ValueType >::set_string(const std::string& raw_value)
+{
+ try {
+ typed_leaf_node< ValueType >::set(text::to_type< ValueType >(
+ raw_value));
+ } catch (const text::value_error& e) {
+ throw config::value_error(F("Failed to convert string value '%s' to "
+ "the node's type") % raw_value);
+ }
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::native_leaf_node< ValueType >::to_string(void) const
+{
+ PRE(typed_leaf_node< ValueType >::is_set());
+ return F("%s") % typed_leaf_node< ValueType >::value();
+}
+
+
+/// Constructor for a node with an undefined value.
+///
+/// This should only be called by the tree's define() method as a way to
+/// register a node as known but undefined. The node will then serve as a
+/// placeholder for future values.
+template< typename ValueType >
+config::base_set_node< ValueType >::base_set_node(void) :
+ _value(none)
+{
+}
+
+
+/// Checks whether the node has been set.
+///
+/// Remember that a node can exist before holding a value (i.e. when the node
+/// has been defined as "known" but not yet set by the user). This function
+/// checks whether the node laready holds a value.
+///
+/// \return True if a value has been set in the node.
+template< typename ValueType >
+bool
+config::base_set_node< ValueType >::is_set(void) const
+{
+ return static_cast< bool >(_value);
+}
+
+
+/// Gets the value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+const typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void) const
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Gets the read-write value stored in the node.
+///
+/// \pre The node must have a value.
+///
+/// \return The value in the node.
+template< typename ValueType >
+typename config::base_set_node< ValueType >::value_type&
+config::base_set_node< ValueType >::value(void)
+{
+ PRE(is_set());
+ return _value.get();
+}
+
+
+/// Sets the value of the node.
+///
+/// \param value_ The new value to set the node to.
+///
+/// \throw value_error If the value is invalid, according to validate().
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set(const value_type& value_)
+{
+ validate(value_);
+ _value = optional< value_type >(value_);
+}
+
+
+/// Sets the value of the node from a raw string representation.
+///
+/// \param raw_value The value to set the node to.
+///
+/// \throw value_error If the value is invalid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_string(const std::string& raw_value)
+{
+ std::set< ValueType > new_value;
+
+ const std::vector< std::string > words = text::split(raw_value, ' ');
+ for (std::vector< std::string >::const_iterator iter = words.begin();
+ iter != words.end(); ++iter) {
+ if (!(*iter).empty())
+ new_value.insert(parse_one(*iter));
+ }
+
+ set(new_value);
+}
+
+
+/// Converts the contents of the node to a string.
+///
+/// \pre The node must have a value.
+///
+/// \return A string representation of the value held by the node.
+template< typename ValueType >
+std::string
+config::base_set_node< ValueType >::to_string(void) const
+{
+ PRE(is_set());
+ return text::join(_value.get(), " ");
+}
+
+
+/// Pushes the node's value onto the Lua stack.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::push_lua(lutok::state& /* state */) const
+{
+ UNREACHABLE;
+}
+
+
+/// Sets the value of the node from an entry in the Lua stack.
+///
+/// \throw value_error If the value in state(value_index) cannot be
+/// processed by this node.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::set_lua(
+ lutok::state& /* state */,
+ const int /* value_index */)
+{
+ UNREACHABLE;
+}
+
+
+/// Checks a given value for validity.
+///
+/// This is called internally by the node right before updating the recorded
+/// value. This method can be redefined by subclasses.
+///
+/// \throw value_error If the value is not valid.
+template< typename ValueType >
+void
+config::base_set_node< ValueType >::validate(
+ const value_type& /* new_value */) const
+{
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_IPP)
diff --git a/utils/config/nodes_fwd.hpp b/utils/config/nodes_fwd.hpp
new file mode 100644
index 000000000000..b03328e79e95
--- /dev/null
+++ b/utils/config/nodes_fwd.hpp
@@ -0,0 +1,70 @@
+// 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/config/nodes_fwd.hpp
+/// Forward declarations for utils/config/nodes.hpp
+
+#if !defined(UTILS_CONFIG_NODES_FWD_HPP)
+#define UTILS_CONFIG_NODES_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+namespace detail {
+
+
+class base_node;
+class static_inner_node;
+
+
+} // namespace detail
+
+
+class leaf_node;
+template< typename > class typed_leaf_node;
+template< typename > class native_leaf_node;
+class bool_node;
+class int_node;
+class positive_int_node;
+class string_node;
+template< typename > class base_set_node;
+class strings_set_node;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_NODES_FWD_HPP)
diff --git a/utils/config/nodes_test.cpp b/utils/config/nodes_test.cpp
new file mode 100644
index 000000000000..e762d3aac38c
--- /dev/null
+++ b/utils/config/nodes_test.cpp
@@ -0,0 +1,695 @@
+// 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/config/nodes.ipp"
+
+#include <atf-c++.hpp>
+
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/defs.hpp"
+
+namespace config = utils::config;
+
+
+namespace {
+
+
+/// Typed leaf node that specializes the validate() method.
+class validation_node : public config::int_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ if (new_value == 12345)
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+/// Set node that specializes the validate() method.
+class set_validation_node : public config::strings_set_node {
+ /// Checks a given value for validity against a fake value.
+ ///
+ /// \param new_value The value to validate.
+ ///
+ /// \throw value_error If the value is not valid.
+ void
+ validate(const value_type& new_value) const
+ {
+ for (value_type::const_iterator iter = new_value.begin();
+ iter != new_value.end(); ++iter)
+ if (*iter == "throw")
+ throw config::value_error("Custom validate method");
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__deep_copy);
+ATF_TEST_CASE_BODY(bool_node__deep_copy)
+{
+ config::bool_node node;
+ node.set(true);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::bool_node* copy = static_cast< config::bool_node* >(raw_copy);
+ ATF_REQUIRE(copy->value());
+ copy->set(false);
+ ATF_REQUIRE(node.value());
+ ATF_REQUIRE(!copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__is_set_and_set);
+ATF_TEST_CASE_BODY(bool_node__is_set_and_set)
+{
+ config::bool_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(false);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__value_and_set);
+ATF_TEST_CASE_BODY(bool_node__value_and_set)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE(!node.value());
+ node.set(true);
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__push_lua);
+ATF_TEST_CASE_BODY(bool_node__push_lua)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ node.set(true);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_boolean(-1));
+ ATF_REQUIRE(state.to_boolean(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__ok);
+ATF_TEST_CASE_BODY(bool_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_boolean(false);
+ node.set_lua(state, -1);
+ state.pop(1);
+ ATF_REQUIRE(!node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.push_string("foo bar");
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__ok);
+ATF_TEST_CASE_BODY(bool_node__set_string__ok)
+{
+ config::bool_node node;
+ node.set_string("false");
+ ATF_REQUIRE(!node.value());
+ node.set_string("true");
+ ATF_REQUIRE( node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(bool_node__set_string__invalid_value)
+{
+ config::bool_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("12345"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bool_node__to_string);
+ATF_TEST_CASE_BODY(bool_node__to_string)
+{
+ config::bool_node node;
+ node.set(false);
+ ATF_REQUIRE_EQ("false", node.to_string());
+ node.set(true);
+ ATF_REQUIRE_EQ("true", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__deep_copy);
+ATF_TEST_CASE_BODY(int_node__deep_copy)
+{
+ config::int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::int_node* copy = static_cast< config::int_node* >(raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(int_node__is_set_and_set)
+{
+ config::int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__value_and_set);
+ATF_TEST_CASE_BODY(int_node__value_and_set)
+{
+ config::int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(0);
+ ATF_REQUIRE_EQ(0, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__push_lua);
+ATF_TEST_CASE_BODY(int_node__push_lua)
+{
+ lutok::state state;
+
+ config::int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__ok);
+ATF_TEST_CASE_BODY(int_node__set_string__ok)
+{
+ config::int_node node;
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+ node.set_string("-123");
+ ATF_REQUIRE_EQ(-123, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(int_node__set_string__invalid_value)
+{
+ config::int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(int_node__to_string);
+ATF_TEST_CASE_BODY(int_node__to_string)
+{
+ config::int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+ node.set(-57);
+ ATF_REQUIRE_EQ("-57", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__deep_copy);
+ATF_TEST_CASE_BODY(positive_int_node__deep_copy)
+{
+ config::positive_int_node node;
+ node.set(5);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::positive_int_node* copy = static_cast< config::positive_int_node* >(
+ raw_copy);
+ ATF_REQUIRE_EQ(5, copy->value());
+ copy->set(10);
+ ATF_REQUIRE_EQ(5, node.value());
+ ATF_REQUIRE_EQ(10, copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__is_set_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__is_set_and_set)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(20);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__value_and_set);
+ATF_TEST_CASE_BODY(positive_int_node__value_and_set)
+{
+ config::positive_int_node node;
+ node.set(20);
+ ATF_REQUIRE_EQ(20, node.value());
+ node.set(1);
+ ATF_REQUIRE_EQ(1, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__push_lua);
+ATF_TEST_CASE_BODY(positive_int_node__push_lua)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ node.set(754);
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_number(-1));
+ ATF_REQUIRE_EQ(754, state.to_integer(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_integer(123);
+ state.push_string("456");
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ(123, node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ(456, node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::positive_int_node node;
+ state.push_boolean(true);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+ state.push_integer(0);
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__ok);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__ok)
+{
+ config::positive_int_node node;
+ node.set_string("1");
+ ATF_REQUIRE_EQ(1, node.value());
+ node.set_string("178");
+ ATF_REQUIRE_EQ(178, node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__set_string__invalid_value);
+ATF_TEST_CASE_BODY(positive_int_node__set_string__invalid_value)
+{
+ config::positive_int_node node;
+ ATF_REQUIRE_THROW(config::value_error, node.set_string(" 23"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("0"));
+ ATF_REQUIRE(!node.is_set());
+ ATF_REQUIRE_THROW(config::value_error, node.set_string("-5"));
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(positive_int_node__to_string);
+ATF_TEST_CASE_BODY(positive_int_node__to_string)
+{
+ config::positive_int_node node;
+ node.set(89);
+ ATF_REQUIRE_EQ("89", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__deep_copy);
+ATF_TEST_CASE_BODY(string_node__deep_copy)
+{
+ config::string_node node;
+ node.set("first");
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::string_node* copy = static_cast< config::string_node* >(raw_copy);
+ ATF_REQUIRE_EQ("first", copy->value());
+ copy->set("second");
+ ATF_REQUIRE_EQ("first", node.value());
+ ATF_REQUIRE_EQ("second", copy->value());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__is_set_and_set);
+ATF_TEST_CASE_BODY(string_node__is_set_and_set)
+{
+ config::string_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set("foo");
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__value_and_set);
+ATF_TEST_CASE_BODY(string_node__value_and_set)
+{
+ config::string_node node;
+ node.set("foo");
+ ATF_REQUIRE_EQ("foo", node.value());
+ node.set("");
+ ATF_REQUIRE_EQ("", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__push_lua);
+ATF_TEST_CASE_BODY(string_node__push_lua)
+{
+ lutok::state state;
+
+ config::string_node node;
+ node.set("some message");
+ node.push_lua(state);
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("some message", state.to_string(-1));
+ state.pop(1);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__ok);
+ATF_TEST_CASE_BODY(string_node__set_lua__ok)
+{
+ lutok::state state;
+
+ config::string_node node;
+ state.push_string("text 1");
+ state.push_integer(231);
+ node.set_lua(state, -2);
+ ATF_REQUIRE_EQ("text 1", node.value());
+ node.set_lua(state, -1);
+ ATF_REQUIRE_EQ("231", node.value());
+ state.pop(2);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_lua__invalid_value);
+ATF_TEST_CASE_BODY(string_node__set_lua__invalid_value)
+{
+ lutok::state state;
+
+ config::bool_node node;
+ state.new_table();
+ ATF_REQUIRE_THROW(config::value_error, node.set_lua(state, -1));
+ state.pop(1);
+ ATF_REQUIRE(!node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__set_string);
+ATF_TEST_CASE_BODY(string_node__set_string)
+{
+ config::string_node node;
+ node.set_string("abcd efgh");
+ ATF_REQUIRE_EQ("abcd efgh", node.value());
+ node.set_string(" 1234 ");
+ ATF_REQUIRE_EQ(" 1234 ", node.value());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(string_node__to_string);
+ATF_TEST_CASE_BODY(string_node__to_string)
+{
+ config::string_node node;
+ node.set("");
+ ATF_REQUIRE_EQ("", node.to_string());
+ node.set("aaa");
+ ATF_REQUIRE_EQ("aaa", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__deep_copy);
+ATF_TEST_CASE_BODY(strings_set_node__deep_copy)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("foo");
+ node.set(value);
+ config::detail::base_node* raw_copy = node.deep_copy();
+ config::strings_set_node* copy =
+ static_cast< config::strings_set_node* >(raw_copy);
+ value.insert("bar");
+ ATF_REQUIRE_EQ(1, copy->value().size());
+ copy->set(value);
+ ATF_REQUIRE_EQ(1, node.value().size());
+ ATF_REQUIRE_EQ(2, copy->value().size());
+ delete copy;
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__is_set_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__is_set_and_set)
+{
+ std::set< std::string > value;
+ value.insert("foo");
+
+ config::strings_set_node node;
+ ATF_REQUIRE(!node.is_set());
+ node.set(value);
+ ATF_REQUIRE( node.is_set());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__value_and_set);
+ATF_TEST_CASE_BODY(strings_set_node__value_and_set)
+{
+ std::set< std::string > value;
+ value.insert("first");
+
+ config::strings_set_node node;
+ node.set(value);
+ ATF_REQUIRE(value == node.value());
+ value.clear();
+ node.set(value);
+ value.insert("second");
+ ATF_REQUIRE(node.value().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__set_string);
+ATF_TEST_CASE_BODY(strings_set_node__set_string)
+{
+ config::strings_set_node node;
+ {
+ std::set< std::string > expected;
+ expected.insert("abcd");
+ expected.insert("efgh");
+
+ node.set_string("abcd efgh");
+ ATF_REQUIRE(expected == node.value());
+ }
+ {
+ std::set< std::string > expected;
+ expected.insert("1234");
+
+ node.set_string(" 1234 ");
+ ATF_REQUIRE(expected == node.value());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(strings_set_node__to_string);
+ATF_TEST_CASE_BODY(strings_set_node__to_string)
+{
+ std::set< std::string > value;
+ config::strings_set_node node;
+ value.insert("second");
+ value.insert("first");
+ node.set(value);
+ ATF_REQUIRE_EQ("first second", node.to_string());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set)
+{
+ validation_node node;
+ node.set(1234);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(12345));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(typed_leaf_node__validate_set_string);
+ATF_TEST_CASE_BODY(typed_leaf_node__validate_set_string)
+{
+ validation_node node;
+ node.set_string("1234");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("12345"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set);
+ATF_TEST_CASE_BODY(base_set_node__validate_set)
+{
+ set_validation_node node;
+ set_validation_node::value_type values;
+ values.insert("foo");
+ values.insert("bar");
+ node.set(values);
+ values.insert("throw");
+ values.insert("baz");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set(values));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(base_set_node__validate_set_string);
+ATF_TEST_CASE_BODY(base_set_node__validate_set_string)
+{
+ set_validation_node node;
+ node.set_string("foo bar");
+ ATF_REQUIRE_THROW_RE(config::value_error, "Custom validate method",
+ node.set_string("foo bar throw baz"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, bool_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, bool_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, bool_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, bool_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, bool_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__set_string__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, positive_int_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, string_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, string_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, string_node__push_lua);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_lua__invalid_value);
+ ATF_ADD_TEST_CASE(tcs, string_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, string_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__is_set_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__value_and_set);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__set_string);
+ ATF_ADD_TEST_CASE(tcs, strings_set_node__to_string);
+
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, typed_leaf_node__validate_set_string);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set);
+ ATF_ADD_TEST_CASE(tcs, base_set_node__validate_set_string);
+}
diff --git a/utils/config/parser.cpp b/utils/config/parser.cpp
new file mode 100644
index 000000000000..7bfe5517fdd0
--- /dev/null
+++ b/utils/config/parser.cpp
@@ -0,0 +1,181 @@
+// 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/config/parser.hpp"
+
+#include <lutok/exceptions.hpp>
+#include <lutok/operations.hpp>
+#include <lutok/stack_cleaner.hpp>
+#include <lutok/state.ipp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/lua_module.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace config = utils::config;
+
+
+// History of configuration file versions:
+//
+// 2 - Changed the syntax() call to take only a version number, instead of the
+// word 'config' as the first argument and the version as the second one.
+// Files now start with syntax(2) instead of syntax('config', 1).
+//
+// 1 - Initial version.
+
+
+/// Internal implementation of the parser.
+struct utils::config::parser::impl : utils::noncopyable {
+ /// Pointer to the parent parser. Needed for callbacks.
+ parser* _parent;
+
+ /// The Lua state used by this parser to process the configuration file.
+ lutok::state _state;
+
+ /// The tree to be filed in by the configuration parameters, as provided by
+ /// the caller.
+ config::tree& _tree;
+
+ /// Whether syntax() has been called or not.
+ bool _syntax_called;
+
+ /// Constructs a new implementation.
+ ///
+ /// \param parent_ Pointer to the class being constructed.
+ /// \param config_tree_ The configuration tree provided by the user.
+ impl(parser* const parent_, tree& config_tree_) :
+ _parent(parent_), _tree(config_tree_), _syntax_called(false)
+ {
+ }
+
+ friend void lua_syntax(lutok::state&);
+
+ /// Callback executed by the Lua syntax() function.
+ ///
+ /// \param syntax_version The syntax format version as provided by the
+ /// configuration file in the call to syntax().
+ void
+ syntax_callback(const int syntax_version)
+ {
+ if (_syntax_called)
+ throw syntax_error("syntax() can only be called once");
+ _syntax_called = true;
+
+ // Allow the parser caller to populate the tree with its own schema
+ // depending on the format/version combination.
+ _parent->setup(_tree, syntax_version);
+
+ // Export the config module to the Lua state so that all global variable
+ // accesses are redirected to the configuration tree.
+ config::redirect(_state, _tree);
+ }
+};
+
+
+namespace {
+
+
+static int
+lua_syntax(lutok::state& state)
+{
+ if (!state.is_number(-1))
+ throw config::value_error("Last argument to syntax must be a number");
+ const int syntax_version = state.to_integer(-1);
+
+ if (syntax_version == 1) {
+ if (state.get_top() != 2)
+ throw config::value_error("Version 1 files need two arguments to "
+ "syntax()");
+ if (!state.is_string(-2) || state.to_string(-2) != "config")
+ throw config::value_error("First argument to syntax must be "
+ "'config' for version 1 files");
+ } else {
+ if (state.get_top() != 1)
+ throw config::value_error("syntax() only takes one argument");
+ }
+
+ state.get_global("_config_parser");
+ config::parser::impl* impl =
+ *state.to_userdata< config::parser::impl* >(-1);
+ state.pop(1);
+
+ impl->syntax_callback(syntax_version);
+
+ return 0;
+}
+
+
+} // anonymous namespace
+
+
+/// Constructs a new parser.
+///
+/// \param [in,out] config_tree The configuration tree into which the values set
+/// in the configuration file will be stored.
+config::parser::parser(tree& config_tree) :
+ _pimpl(new impl(this, config_tree))
+{
+ lutok::stack_cleaner cleaner(_pimpl->_state);
+
+ _pimpl->_state.push_cxx_function(lua_syntax);
+ _pimpl->_state.set_global("syntax");
+ *_pimpl->_state.new_userdata< config::parser::impl* >() = _pimpl.get();
+ _pimpl->_state.set_global("_config_parser");
+}
+
+
+/// Destructor.
+config::parser::~parser(void)
+{
+}
+
+
+/// Parses a configuration file.
+///
+/// \post The tree registered during the construction of this class is updated
+/// to contain the values read from the configuration file. If the processing
+/// fails, the state of the output tree is undefined.
+///
+/// \param file The path to the file to process.
+///
+/// \throw syntax_error If there is any problem processing the file.
+void
+config::parser::parse(const fs::path& file)
+{
+ try {
+ lutok::do_file(_pimpl->_state, file.str(), 0, 0, 0);
+ } catch (const lutok::error& e) {
+ throw syntax_error(e.what());
+ }
+
+ if (!_pimpl->_syntax_called)
+ throw syntax_error("No syntax defined (no call to syntax() found)");
+}
diff --git a/utils/config/parser.hpp b/utils/config/parser.hpp
new file mode 100644
index 000000000000..cb69e756cbe8
--- /dev/null
+++ b/utils/config/parser.hpp
@@ -0,0 +1,95 @@
+// 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/config/parser.hpp
+/// Utilities to read a configuration file into memory.
+
+#if !defined(UTILS_CONFIG_PARSER_HPP)
+#define UTILS_CONFIG_PARSER_HPP
+
+#include "utils/config/parser_fwd.hpp"
+
+#include <memory>
+
+#include "utils/config/tree_fwd.hpp"
+#include "utils/fs/path_fwd.hpp"
+#include "utils/noncopyable.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// A configuration parser.
+///
+/// This parser is a class rather than a function because we need to support
+/// callbacks to perform the initialization of the config file schema. The
+/// configuration files always start with a call to syntax(), which define the
+/// particular version of the schema being used. Depending on such version, the
+/// layout of the internal tree representation needs to be different.
+///
+/// A parser implementation must provide a setup() method to set up the
+/// configuration schema based on the particular combination of syntax format
+/// and version specified on the file.
+///
+/// Parser objects are not supposed to be reused, and specific trees are not
+/// supposed to be passed to multiple parsers (even if sequentially). Doing so
+/// will cause all kinds of inconsistencies in the managed tree itself or in the
+/// Lua state.
+class parser : noncopyable {
+public:
+ struct impl;
+
+private:
+ /// Pointer to the internal implementation.
+ std::auto_ptr< impl > _pimpl;
+
+ /// Hook to initialize the tree keys before reading the file.
+ ///
+ /// This hook gets called when the configuration file defines its specific
+ /// format by calling the syntax() function. We have to delay the tree
+ /// initialization until this point because, before we know what version of
+ /// a configuration file we are parsing, we cannot know what keys are valid.
+ ///
+ /// \param [in,out] config_tree The tree in which to define the key
+ /// structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ virtual void setup(tree& config_tree, const int syntax_version) = 0;
+
+public:
+ explicit parser(tree&);
+ virtual ~parser(void);
+
+ void parse(const fs::path&);
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_HPP)
diff --git a/utils/config/parser_fwd.hpp b/utils/config/parser_fwd.hpp
new file mode 100644
index 000000000000..6278b6c95c12
--- /dev/null
+++ b/utils/config/parser_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/config/parser_fwd.hpp
+/// Forward declarations for utils/config/parser.hpp
+
+#if !defined(UTILS_CONFIG_PARSER_FWD_HPP)
+#define UTILS_CONFIG_PARSER_FWD_HPP
+
+namespace utils {
+namespace config {
+
+
+class parser;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_PARSER_FWD_HPP)
diff --git a/utils/config/parser_test.cpp b/utils/config/parser_test.cpp
new file mode 100644
index 000000000000..f5445f55c490
--- /dev/null
+++ b/utils/config/parser_test.cpp
@@ -0,0 +1,252 @@
+// 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/config/parser.hpp"
+
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/parser.hpp"
+#include "utils/config/tree.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/fs/path.hpp"
+
+namespace config = utils::config;
+namespace fs = utils::fs;
+
+
+namespace {
+
+
+/// Implementation of a parser for testing purposes.
+class mock_parser : public config::parser {
+ /// Initializes the tree keys before reading the file.
+ ///
+ /// \param [in,out] tree The tree in which to define the key structure.
+ /// \param syntax_version The version of the file format as specified in the
+ /// configuration file.
+ void
+ setup(config::tree& tree, const int syntax_version)
+ {
+ if (syntax_version == 1) {
+ // Do nothing on config_tree.
+ } else if (syntax_version == 2) {
+ tree.define< config::string_node >("top_string");
+ tree.define< config::int_node >("inner.int");
+ tree.define_dynamic("inner.dynamic");
+ } else {
+ throw std::runtime_error(F("Unknown syntax version %s") %
+ syntax_version);
+ }
+ }
+
+public:
+ /// Initializes a parser.
+ ///
+ /// \param tree The mock config tree to parse.
+ mock_parser(config::tree& tree) :
+ config::parser(tree)
+ {
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__ok);
+ATF_TEST_CASE_BODY(no_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "local foo = 'value'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::string_node >("foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(no_keys__unknown_key);
+ATF_TEST_CASE_BODY(no_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "foo = 'value'\n");
+
+ config::tree tree;
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "foo",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__ok);
+ATF_TEST_CASE_BODY(some_keys__ok)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "inner.int = 12345\n"
+ "inner.dynamic.foo = 78\n"
+ "inner.dynamic.bar = 'some text'\n");
+
+ config::tree tree;
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE_EQ(12345, tree.lookup< config::int_node >("inner.int"));
+ ATF_REQUIRE_EQ("78",
+ tree.lookup< config::string_node >("inner.dynamic.foo"));
+ ATF_REQUIRE_EQ("some text",
+ tree.lookup< config::string_node >("inner.dynamic.bar"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__not_strict);
+ATF_TEST_CASE_BODY(some_keys__not_strict)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string = 'foo'\n"
+ "unknown_string = 'bar'\n"
+ "top_string = 'baz'\n");
+
+ config::tree tree(false);
+ mock_parser(tree).parse(fs::path("output.lua"));
+ ATF_REQUIRE_EQ("baz", tree.lookup< config::string_node >("top_string"));
+ ATF_REQUIRE(!tree.is_set("unknown_string"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(some_keys__unknown_key);
+ATF_TEST_CASE_BODY(some_keys__unknown_key)
+{
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "top_string2 = 'foo'\n");
+ config::tree tree1;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'top_string2'",
+ mock_parser(tree1).parse(fs::path("output.lua")));
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "inner.int2 = 12345\n");
+ config::tree tree2;
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown configuration property 'inner.int2'",
+ mock_parser(tree2).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(invalid_syntax);
+ATF_TEST_CASE_BODY(invalid_syntax)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax(56)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "Unknown syntax version 56",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_deprecated_format);
+ATF_TEST_CASE_BODY(syntax_deprecated_format)
+{
+ config::tree tree;
+
+ atf::utils::create_file("output.lua", "syntax('config', 1)\n");
+ (void)mock_parser(tree).parse(fs::path("output.lua"));
+
+ atf::utils::create_file("output.lua", "syntax('foo', 1)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "must be 'config'",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ atf::utils::create_file("output.lua", "syntax('config', 2)\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "only takes one argument",
+ mock_parser(tree).parse(fs::path("output.lua")));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_not_called);
+ATF_TEST_CASE_BODY(syntax_not_called)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file("output.lua", "var = 3\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error, "No syntax defined",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE(!tree.is_set("var"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(syntax_called_more_than_once);
+ATF_TEST_CASE_BODY(syntax_called_more_than_once)
+{
+ config::tree tree;
+ tree.define< config::int_node >("var");
+
+ atf::utils::create_file(
+ "output.lua",
+ "syntax(2)\n"
+ "var = 3\n"
+ "syntax(2)\n"
+ "var = 5\n");
+ ATF_REQUIRE_THROW_RE(config::syntax_error,
+ "syntax\\(\\) can only be called once",
+ mock_parser(tree).parse(fs::path("output.lua")));
+
+ ATF_REQUIRE_EQ(3, tree.lookup< config::int_node >("var"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, no_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, no_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, some_keys__ok);
+ ATF_ADD_TEST_CASE(tcs, some_keys__not_strict);
+ ATF_ADD_TEST_CASE(tcs, some_keys__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, invalid_syntax);
+ ATF_ADD_TEST_CASE(tcs, syntax_deprecated_format);
+ ATF_ADD_TEST_CASE(tcs, syntax_not_called);
+ ATF_ADD_TEST_CASE(tcs, syntax_called_more_than_once);
+}
diff --git a/utils/config/tree.cpp b/utils/config/tree.cpp
new file mode 100644
index 000000000000..1aa2d85b89cd
--- /dev/null
+++ b/utils/config/tree.cpp
@@ -0,0 +1,338 @@
+// 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/config/tree.ipp"
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+
+namespace config = utils::config;
+
+
+/// Constructor.
+///
+/// \param strict Whether keys must be validated at "set" time.
+config::tree::tree(const bool strict) :
+ _strict(strict), _root(new detail::static_inner_node())
+{
+}
+
+
+/// Constructor with a non-empty root.
+///
+/// \param strict Whether keys must be validated at "set" time.
+/// \param root The root to the tree to be owned by this instance.
+config::tree::tree(const bool strict, detail::static_inner_node* root) :
+ _strict(strict), _root(root)
+{
+}
+
+
+/// Destructor.
+config::tree::~tree(void)
+{
+}
+
+
+/// Generates a deep copy of the input tree.
+///
+/// \return A new tree that is an exact copy of this tree.
+config::tree
+config::tree::deep_copy(void) const
+{
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(_root->deep_copy());
+ return config::tree(_strict, new_root);
+}
+
+
+/// Combines two trees.
+///
+/// By combination we understand a new tree that contains the full key space of
+/// the two input trees and, for the keys that match, respects the value of the
+/// right-hand side (aka "other") tree.
+///
+/// Any nodes marked as dynamic "win" over non-dynamic nodes and the resulting
+/// tree will have the dynamic property set on those.
+///
+/// \param overrides The tree to use as value overrides.
+///
+/// \return The combined tree.
+///
+/// \throw bad_combination_error If the two trees cannot be combined; for
+/// example, if a single key represents an inner node in one tree but a leaf
+/// node in the other one.
+config::tree
+config::tree::combine(const tree& overrides) const
+{
+ const detail::static_inner_node* other_root =
+ dynamic_cast< const detail::static_inner_node * >(
+ overrides._root.get());
+
+ detail::static_inner_node* new_root =
+ dynamic_cast< detail::static_inner_node* >(
+ _root->combine(detail::tree_key(), other_root));
+ return config::tree(_strict, new_root);
+}
+
+
+/// Registers a node as being dynamic.
+///
+/// This operation creates the given key as an inner node. Further set
+/// operations that trespass this node will automatically create any missing
+/// keys.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+void
+config::tree::define_dynamic(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< detail::dynamic_inner_node >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG("define() failing due to key errors is a programming "
+ "mistake: " + std::string(e.what()));
+ }
+}
+
+
+/// Checks if a given node is set.
+///
+/// \param dotted_key The key to be checked.
+///
+/// \return True if the key is set to a specific value (not just defined).
+/// False if the key is not set or if the key does not exist.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+bool
+config::tree::is_set(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(
+ *raw_node);
+ return child.is_set();
+ } catch (const std::bad_cast& unused_error) {
+ return false;
+ }
+ } catch (const unknown_key_error& unused_error) {
+ return false;
+ }
+}
+
+
+/// Pushes a leaf node's value onto the Lua stack.
+///
+/// \param dotted_key The key to be pushed.
+/// \param state The Lua state into which to push the key's value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::push_lua(const std::string& dotted_key, lutok::state& state) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ child.push_lua(state);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets a leaf node's value from a value in the Lua stack.
+///
+/// \param dotted_key The key to be set.
+/// \param state The Lua state from which to retrieve the value.
+/// \param value_index The position in the Lua stack holding the value.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_lua(const std::string& dotted_key, lutok::state& state,
+ const int value_index)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_lua(state, value_index);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Gets the value of a node as a plain string.
+///
+/// \param dotted_key The key to be looked up.
+///
+/// \return The value of the located node as a string.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+std::string
+config::tree::lookup_string(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const leaf_node& child = dynamic_cast< const leaf_node& >(*raw_node);
+ return child.to_string();
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key from a string value.
+///
+/// This respects the native types of all the nodes that have been predefined.
+/// For new nodes under a dynamic subtree, this has no mechanism of determining
+/// what type they need to have, so they are created as plain string nodes.
+///
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param raw_value The string representation of the value to set the node to.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+void
+config::tree::set_string(const std::string& dotted_key,
+ const std::string& raw_value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< string_node >);
+ leaf_node& child = dynamic_cast< leaf_node& >(*raw_node);
+ child.set_string(raw_value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+/// Converts the tree to a collection of key/value string pairs.
+///
+/// \param dotted_key Subtree from which to start the export.
+/// \param strip_key If true, remove the dotted_key prefix from the resulting
+/// properties.
+///
+/// \return A map of keys to values in their textual representation.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+/// \throw value_error If the provided key points to a leaf.
+config::properties_map
+config::tree::all_properties(const std::string& dotted_key,
+ const bool strip_key) const
+{
+ PRE(!strip_key || !dotted_key.empty());
+
+ properties_map properties;
+
+ detail::tree_key key;
+ const detail::base_node* raw_node;
+ if (dotted_key.empty()) {
+ raw_node = _root.get();
+ } else {
+ key = detail::parse_key(dotted_key);
+ raw_node = _root->lookup_ro(key, 0);
+ }
+ try {
+ const detail::inner_node& child =
+ dynamic_cast< const detail::inner_node& >(*raw_node);
+ child.all_properties(properties, key);
+ } catch (const std::bad_cast& unused_error) {
+ INV(!dotted_key.empty());
+ throw value_error(F("Cannot export properties from a leaf node; "
+ "'%s' given") % dotted_key);
+ }
+
+ if (strip_key) {
+ properties_map stripped;
+ for (properties_map::const_iterator iter = properties.begin();
+ iter != properties.end(); ++iter) {
+ stripped[(*iter).first.substr(dotted_key.length() + 1)] =
+ (*iter).second;
+ }
+ properties = stripped;
+ }
+
+ return properties;
+}
+
+
+/// Equality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are equal; false otherwise.
+bool
+config::tree::operator==(const tree& other) const
+{
+ // TODO(jmmv): Would be nicer to perform the comparison directly on the
+ // nodes, instead of exporting the values to strings first.
+ return _root == other._root || all_properties() == other.all_properties();
+}
+
+
+/// Inequality comparator.
+///
+/// \param other The other object to compare this one to.
+///
+/// \return True if this object and other are different; false otherwise.
+bool
+config::tree::operator!=(const tree& other) const
+{
+ return !(*this == other);
+}
diff --git a/utils/config/tree.hpp b/utils/config/tree.hpp
new file mode 100644
index 000000000000..cad0a9b4fc0b
--- /dev/null
+++ b/utils/config/tree.hpp
@@ -0,0 +1,128 @@
+// 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/config/tree.hpp
+/// Data type to represent a tree of arbitrary values with string keys.
+
+#if !defined(UTILS_CONFIG_TREE_HPP)
+#define UTILS_CONFIG_TREE_HPP
+
+#include "utils/config/tree_fwd.hpp"
+
+#include <memory>
+#include <string>
+
+#include <lutok/state.hpp>
+
+#include "utils/config/keys_fwd.hpp"
+#include "utils/config/nodes_fwd.hpp"
+
+namespace utils {
+namespace config {
+
+
+/// Representation of a tree.
+///
+/// The string keys of the tree are in dotted notation and actually represent
+/// path traversals through the nodes.
+///
+/// Our trees are "strictly-keyed": keys must be defined as "existent" before
+/// their values can be set. Defining a key is a separate action from setting
+/// its value. The rationale is that we want to be able to control what keys
+/// get defined: because trees are used to hold configuration, we want to catch
+/// typos as early as possible. Also, users cannot set keys unless the types
+/// are known in advance because our leaf nodes are strictly typed.
+///
+/// However, there is an exception to the strict keys: the inner nodes of the
+/// tree can be static or dynamic. Static inner nodes have a known subset of
+/// children and attempting to set keys not previously defined will result in an
+/// error. Dynamic inner nodes do not have a predefined set of keys and can be
+/// used to accept arbitrary user input.
+///
+/// For simplicity reasons, we force the root of the tree to be a static inner
+/// node. In other words, the root can never contain a value by itself and this
+/// is not a problem because the root is not addressable by the key space.
+/// Additionally, the root is strict so all of its direct children must be
+/// explicitly defined.
+///
+/// This is, effectively, a simple wrapper around the node representing the
+/// root. Having a separate class aids in clearly representing the concept of a
+/// tree and all of its public methods. Also, the tree accepts dotted notations
+/// for the keys while the internal structures do not.
+///
+/// Note that trees are shallow-copied unless a deep copy is requested with
+/// deep_copy().
+class tree {
+ /// Whether keys must be validated at "set" time.
+ bool _strict;
+
+ /// The root of the tree.
+ std::shared_ptr< detail::static_inner_node > _root;
+
+ tree(const bool, detail::static_inner_node*);
+
+public:
+ tree(const bool = true);
+ ~tree(void);
+
+ tree deep_copy(void) const;
+ tree combine(const tree&) const;
+
+ template< class LeafType >
+ void define(const std::string&);
+
+ void define_dynamic(const std::string&);
+
+ bool is_set(const std::string&) const;
+
+ template< class LeafType >
+ const typename LeafType::value_type& lookup(const std::string&) const;
+ template< class LeafType >
+ typename LeafType::value_type& lookup_rw(const std::string&);
+
+ template< class LeafType >
+ void set(const std::string&, const typename LeafType::value_type&);
+
+ void push_lua(const std::string&, lutok::state&) const;
+ void set_lua(const std::string&, lutok::state&, const int);
+
+ std::string lookup_string(const std::string&) const;
+ void set_string(const std::string&, const std::string&);
+
+ properties_map all_properties(const std::string& = "",
+ const bool = false) const;
+
+ bool operator==(const tree&) const;
+ bool operator!=(const tree&) const;
+};
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_HPP)
diff --git a/utils/config/tree.ipp b/utils/config/tree.ipp
new file mode 100644
index 000000000000..a79acc3be184
--- /dev/null
+++ b/utils/config/tree.ipp
@@ -0,0 +1,156 @@
+// 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/config/tree.hpp"
+
+#if !defined(UTILS_CONFIG_TREE_IPP)
+#define UTILS_CONFIG_TREE_IPP
+
+#include <typeinfo>
+
+#include "utils/config/exceptions.hpp"
+#include "utils/config/keys.hpp"
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/sanity.hpp"
+
+namespace utils {
+
+
+/// Registers a key as valid and having a specific type.
+///
+/// This method does not raise errors on invalid/unknown keys or other
+/// tree-related issues. The reasons is that define() is a method that does not
+/// depend on user input: it is intended to pre-populate the tree with a
+/// specific structure, and that happens once at coding time.
+///
+/// \tparam LeafType The node type of the leaf we are defining.
+/// \param dotted_key The key to be registered in dotted representation.
+template< class LeafType >
+void
+config::tree::define(const std::string& dotted_key)
+{
+ try {
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ _root->define(key, 0, detail::new_node< LeafType >);
+ } catch (const error& e) {
+ UNREACHABLE_MSG(F("define() failing due to key errors is a programming "
+ "mistake: %s") % e.what());
+ }
+}
+
+
+/// Gets a read-only reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+const typename LeafType::value_type&
+config::tree::lookup(const std::string& dotted_key) const
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ const detail::base_node* raw_node = _root->lookup_ro(key, 0);
+ try {
+ const LeafType& child = dynamic_cast< const LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Gets a read-write reference to the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are querying.
+/// \param dotted_key The key to be registered in dotted representation.
+///
+/// \return A reference to the value in the located leaf, if successful.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+typename LeafType::value_type&
+config::tree::lookup_rw(const std::string& dotted_key)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ detail::base_node* raw_node = _root->lookup_rw(
+ key, 0, detail::new_node< LeafType >);
+ try {
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ if (child.is_set())
+ return child.value();
+ else
+ throw unknown_key_error(key);
+ } catch (const std::bad_cast& unused_error) {
+ throw unknown_key_error(key);
+ }
+}
+
+
+/// Sets the value of a leaf addressed by its key.
+///
+/// \tparam LeafType The node type of the leaf we are setting.
+/// \param dotted_key The key to be registered in dotted representation.
+/// \param value The value to set into the node.
+///
+/// \throw invalid_key_error If the provided key has an invalid format.
+/// \throw invalid_key_value If the value mismatches the node type.
+/// \throw unknown_key_error If the provided key is unknown.
+template< class LeafType >
+void
+config::tree::set(const std::string& dotted_key,
+ const typename LeafType::value_type& value)
+{
+ const detail::tree_key key = detail::parse_key(dotted_key);
+ try {
+ leaf_node* raw_node = _root->lookup_rw(key, 0,
+ detail::new_node< LeafType >);
+ LeafType& child = dynamic_cast< LeafType& >(*raw_node);
+ child.set(value);
+ } catch (const unknown_key_error& e) {
+ if (_strict)
+ throw e;
+ } catch (const value_error& e) {
+ throw invalid_key_value(key, e.what());
+ } catch (const std::bad_cast& unused_error) {
+ throw invalid_key_value(key, "Type mismatch");
+ }
+}
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_IPP)
diff --git a/utils/config/tree_fwd.hpp b/utils/config/tree_fwd.hpp
new file mode 100644
index 000000000000..e494d8c0f4ee
--- /dev/null
+++ b/utils/config/tree_fwd.hpp
@@ -0,0 +1,52 @@
+// 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/config/tree_fwd.hpp
+/// Forward declarations for utils/config/tree.hpp
+
+#if !defined(UTILS_CONFIG_TREE_FWD_HPP)
+#define UTILS_CONFIG_TREE_FWD_HPP
+
+#include <map>
+#include <string>
+
+namespace utils {
+namespace config {
+
+
+/// Flat representation of all properties as strings.
+typedef std::map< std::string, std::string > properties_map;
+
+
+class tree;
+
+
+} // namespace config
+} // namespace utils
+
+#endif // !defined(UTILS_CONFIG_TREE_FWD_HPP)
diff --git a/utils/config/tree_test.cpp b/utils/config/tree_test.cpp
new file mode 100644
index 000000000000..b6efd64a84a6
--- /dev/null
+++ b/utils/config/tree_test.cpp
@@ -0,0 +1,1086 @@
+// 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/config/tree.ipp"
+
+#include <atf-c++.hpp>
+
+#include "utils/config/nodes.ipp"
+#include "utils/format/macros.hpp"
+#include "utils/text/operations.ipp"
+
+namespace config = utils::config;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Simple wrapper around an integer value without default constructors.
+///
+/// The purpose of this type is to have a simple class without default
+/// constructors to validate that we can use it as a leaf of a tree.
+class int_wrapper {
+ /// The wrapped integer value.
+ int _value;
+
+public:
+ /// Constructs a new wrapped integer.
+ ///
+ /// \param value_ The value to store in the object.
+ explicit int_wrapper(int value_) :
+ _value(value_)
+ {
+ }
+
+ /// \return The integer value stored by the object.
+ int
+ value(void) const
+ {
+ return _value;
+ }
+};
+
+
+/// Custom tree leaf type for an object without defualt constructors.
+class wrapped_int_node : public config::typed_leaf_node< int_wrapper > {
+public:
+ /// Copies the node.
+ ///
+ /// \return A dynamically-allocated node.
+ virtual base_node*
+ deep_copy(void) const
+ {
+ std::auto_ptr< wrapped_int_node > new_node(new wrapped_int_node());
+ new_node->_value = _value;
+ return new_node.release();
+ }
+
+ /// Pushes the node's value onto the Lua stack.
+ ///
+ /// \param state The Lua state onto which to push the value.
+ void
+ push_lua(lutok::state& state) const
+ {
+ state.push_integer(
+ config::typed_leaf_node< int_wrapper >::value().value());
+ }
+
+ /// Sets the value of the node from an entry in the Lua stack.
+ ///
+ /// \param state The Lua state from which to get the value.
+ /// \param value_index The stack index in which the value resides.
+ void
+ set_lua(lutok::state& state, const int value_index)
+ {
+ ATF_REQUIRE(state.is_number(value_index));
+ int_wrapper new_value(state.to_integer(value_index));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Sets the value of the node from a raw string representation.
+ ///
+ /// \param raw_value The value to set the node to.
+ void
+ set_string(const std::string& raw_value)
+ {
+ int_wrapper new_value(text::to_type< int >(raw_value));
+ config::typed_leaf_node< int_wrapper >::set(new_value);
+ }
+
+ /// Converts the contents of the node to a string.
+ ///
+ /// \return A string representation of the value held by the node.
+ std::string
+ to_string(void) const
+ {
+ return F("%s") %
+ config::typed_leaf_node< int_wrapper >::value().value();
+ }
+};
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__one_level);
+ATF_TEST_CASE_BODY(define_set_lookup__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("var2", "hello");
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("var2"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(define_set_lookup__multiple_levels);
+ATF_TEST_CASE_BODY(define_set_lookup__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define< config::bool_node >("foo.3");
+ tree.define_dynamic("sub.tree");
+
+ tree.set< config::int_node >("foo.bar.1", 42);
+ tree.set< config::string_node >("foo.bar.2", "hello");
+ tree.set< config::bool_node >("foo.3", true);
+ tree.set< config::string_node >("sub.tree.1", "bye");
+ tree.set< config::int_node >("sub.tree.2", 4);
+ tree.set< config::int_node >("sub.tree.3.4", 123);
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE(tree.lookup< config::bool_node >("foo.3"));
+ ATF_REQUIRE_EQ(4, tree.lookup< config::int_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ(123, tree.lookup< config::int_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__empty);
+ATF_TEST_CASE_BODY(deep_copy__empty)
+{
+ config::tree tree1;
+ config::tree tree2 = tree1.deep_copy();
+
+ tree1.define< config::bool_node >("var1");
+ // This would crash if the copy shared the internal data.
+ tree2.define< config::int_node >("var1");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(deep_copy__some);
+ATF_TEST_CASE_BODY(deep_copy__some)
+{
+ config::tree tree1;
+ tree1.define< config::bool_node >("this.is.a.var");
+ tree1.set< config::bool_node >("this.is.a.var", true);
+ tree1.define< config::int_node >("this.is.another.var");
+ tree1.set< config::int_node >("this.is.another.var", 34);
+ tree1.define< config::int_node >("and.another");
+ tree1.set< config::int_node >("and.another", 123);
+
+ config::tree tree2 = tree1.deep_copy();
+ tree2.set< config::bool_node >("this.is.a.var", false);
+ tree2.set< config::int_node >("this.is.another.var", 43);
+
+ ATF_REQUIRE( tree1.lookup< config::bool_node >("this.is.a.var"));
+ ATF_REQUIRE(!tree2.lookup< config::bool_node >("this.is.a.var"));
+
+ ATF_REQUIRE_EQ(34, tree1.lookup< config::int_node >("this.is.another.var"));
+ ATF_REQUIRE_EQ(43, tree2.lookup< config::int_node >("this.is.another.var"));
+
+ ATF_REQUIRE_EQ(123, tree1.lookup< config::int_node >("and.another"));
+ ATF_REQUIRE_EQ(123, tree2.lookup< config::int_node >("and.another"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__empty);
+ATF_TEST_CASE_BODY(combine__empty)
+{
+ const config::tree t1, t2;
+ const config::tree combined = t1.combine(t2);
+
+ const config::tree expected;
+ ATF_REQUIRE(expected == combined);
+}
+
+
+static void
+init_tree_for_combine_test(config::tree& tree)
+{
+ tree.define< config::int_node >("int-node");
+ tree.define< config::string_node >("string-node");
+ tree.define< config::int_node >("unused.node");
+ tree.define< config::int_node >("deeper.int.node");
+ tree.define_dynamic("deeper.dynamic");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_overrides);
+ATF_TEST_CASE_BODY(combine__same_layout__no_overrides)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t1.set< config::string_node >("string-node", "foo");
+ t1.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t1 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__no_base);
+ATF_TEST_CASE_BODY(combine__same_layout__no_base)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t2.set< config::int_node >("int-node", 3);
+ t2.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t2.set_string("deeper.dynamic.first", "value1");
+ t2.set_string("deeper.dynamic.second", "value2");
+ const config::tree combined = t1.combine(t2);
+
+ ATF_REQUIRE(t2 == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__same_layout__mix);
+ATF_TEST_CASE_BODY(combine__same_layout__mix)
+{
+ config::tree t1, t2;
+ init_tree_for_combine_test(t1);
+ init_tree_for_combine_test(t2);
+ t1.set< config::int_node >("int-node", 3);
+ t2.set< config::int_node >("int-node", 5);
+ t1.set< config::string_node >("string-node", "foo");
+ t2.set< config::int_node >("deeper.int.node", 15);
+ t1.set_string("deeper.dynamic.first", "value1");
+ t1.set_string("deeper.dynamic.second", "value2.1");
+ t2.set_string("deeper.dynamic.second", "value2.2");
+ t2.set_string("deeper.dynamic.third", "value3");
+ const config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ init_tree_for_combine_test(expected);
+ expected.set< config::int_node >("int-node", 5);
+ expected.set< config::string_node >("string-node", "foo");
+ expected.set< config::int_node >("deeper.int.node", 15);
+ expected.set_string("deeper.dynamic.first", "value1");
+ expected.set_string("deeper.dynamic.second", "value2.2");
+ expected.set_string("deeper.dynamic.third", "value3");
+ ATF_REQUIRE(expected == combined);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__different_layout);
+ATF_TEST_CASE_BODY(combine__different_layout)
+{
+ config::tree t1;
+ t1.define< config::int_node >("common.base1");
+ t1.define< config::int_node >("common.base2");
+ t1.define_dynamic("dynamic.base");
+ t1.define< config::int_node >("unset.base");
+
+ config::tree t2;
+ t2.define< config::int_node >("common.base2");
+ t2.define< config::int_node >("common.base3");
+ t2.define_dynamic("dynamic.other");
+ t2.define< config::int_node >("unset.other");
+
+ t1.set< config::int_node >("common.base1", 1);
+ t1.set< config::int_node >("common.base2", 2);
+ t1.set_string("dynamic.base.first", "foo");
+ t1.set_string("dynamic.base.second", "bar");
+
+ t2.set< config::int_node >("common.base2", 4);
+ t2.set< config::int_node >("common.base3", 3);
+ t2.set_string("dynamic.other.first", "FOO");
+ t2.set_string("dynamic.other.second", "BAR");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define< config::int_node >("common.base1");
+ expected.define< config::int_node >("common.base2");
+ expected.define< config::int_node >("common.base3");
+ expected.define_dynamic("dynamic.base");
+ expected.define_dynamic("dynamic.other");
+ expected.define< config::int_node >("unset.base");
+ expected.define< config::int_node >("unset.other");
+
+ expected.set< config::int_node >("common.base1", 1);
+ expected.set< config::int_node >("common.base2", 4);
+ expected.set< config::int_node >("common.base3", 3);
+ expected.set_string("dynamic.base.first", "foo");
+ expected.set_string("dynamic.base.second", "bar");
+ expected.set_string("dynamic.other.first", "FOO");
+ expected.set_string("dynamic.other.second", "BAR");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined tree should have respected existing but unset nodes. Check
+ // that these calls do not crash.
+ combined.set< config::int_node >("unset.base", 5);
+ combined.set< config::int_node >("unset.other", 5);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__dynamic_wins);
+ATF_TEST_CASE_BODY(combine__dynamic_wins)
+{
+ config::tree t1;
+ t1.define< config::int_node >("inner.leaf1");
+ t1.set< config::int_node >("inner.leaf1", 3);
+
+ config::tree t2;
+ t2.define_dynamic("inner");
+ t2.set_string("inner.leaf2", "4");
+
+ config::tree combined = t1.combine(t2);
+
+ config::tree expected;
+ expected.define_dynamic("inner");
+ expected.set_string("inner.leaf1", "3");
+ expected.set_string("inner.leaf2", "4");
+
+ ATF_REQUIRE(expected == combined);
+
+ // The combined inner node should have become dynamic so this call should
+ // not fail.
+ combined.set_string("inner.leaf3", "5");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(combine__inner_leaf_mismatch);
+ATF_TEST_CASE_BODY(combine__inner_leaf_mismatch)
+{
+ config::tree t1;
+ t1.define< config::int_node >("top.foo.bar");
+
+ config::tree t2;
+ t2.define< config::int_node >("top.foo");
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is an inner node in the base tree but a "
+ "leaf node in the overrides tree",
+ t1.combine(t2));
+
+ ATF_REQUIRE_THROW_RE(config::bad_combination_error,
+ "'top.foo' is a leaf node in the base tree but an "
+ "inner node in the overrides tree",
+ t2.combine(t1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__invalid_key);
+ATF_TEST_CASE_BODY(lookup__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.lookup< config::int_node >("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup__unknown_key);
+ATF_TEST_CASE_BODY(lookup__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::int_node >("a.d.100", 0);
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("abc"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("foo.bar.baz"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.c"));
+ (void)tree.lookup< config::int_node >("a.b.c");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.b.c.d"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d"));
+ (void)tree.lookup< config::int_node >("a.d.100");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.101"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.100.3"));
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.lookup< config::int_node >("a.d.e"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__one_level);
+ATF_TEST_CASE_BODY(is_set__one_level)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("var2");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ ATF_REQUIRE( tree.is_set("var1"));
+ ATF_REQUIRE(!tree.is_set("var2"));
+ ATF_REQUIRE( tree.is_set("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__multiple_levels);
+ATF_TEST_CASE_BODY(is_set__multiple_levels)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.var1");
+ tree.define< config::string_node >("a.b.var2");
+ tree.define< config::bool_node >("e.var3");
+
+ tree.set< config::int_node >("a.b.var1", 42);
+ tree.set< config::bool_node >("e.var3", false);
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE( tree.is_set("a.b.var1"));
+ ATF_REQUIRE(!tree.is_set("a.b.var1.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("a"));
+ ATF_REQUIRE(!tree.is_set("a.b"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2"));
+ ATF_REQUIRE(!tree.is_set("a.b.var2.trailing"));
+
+ ATF_REQUIRE(!tree.is_set("e"));
+ ATF_REQUIRE( tree.is_set("e.var3"));
+ ATF_REQUIRE(!tree.is_set("e.var3.trailing"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(is_set__invalid_key);
+ATF_TEST_CASE_BODY(is_set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.is_set(".abc"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key);
+ATF_TEST_CASE_BODY(set__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error,
+ tree.set< config::int_node >("foo.", 54));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__invalid_key_value);
+ATF_TEST_CASE_BODY(set__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define_dynamic("a.d");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("foo", 3));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set< config::int_node >("a", -10));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key);
+ATF_TEST_CASE_BODY(set__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("abc", 2));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("foo.bar.baz", 0));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.c", 100));
+ tree.set< config::int_node >("a.b.c", -3);
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.b.c.d", 82));
+ tree.set< config::string_node >("a.d.3", "bar");
+ tree.set< config::string_node >("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set< config::int_node >("a.d.4.5", 82));
+ tree.set< config::int_node >("a.d.5.6", 82);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set< config::int_node >("a.b.c", 123);
+ tree.set< config::string_node >("a.d.3", "foo");
+
+ tree.set< config::int_node >("abc", 2);
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set< config::int_node >("foo.bar", 15);
+ tree.set< config::int_node >("foo.bar.baz", 0);
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set< config::int_node >("a.c", 100);
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(push_lua__ok);
+ATF_TEST_CASE_BODY(push_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+ tree.set< config::int_node >("top.integer", 5);
+ tree.set< wrapped_int_node >("top.custom", int_wrapper(10));
+ tree.set_string("dynamic.first", "foo");
+
+ lutok::state state;
+ tree.push_lua("top.integer", state);
+ tree.push_lua("top.custom", state);
+ tree.push_lua("dynamic.first", state);
+ ATF_REQUIRE(state.is_number(-3));
+ ATF_REQUIRE_EQ(5, state.to_integer(-3));
+ ATF_REQUIRE(state.is_number(-2));
+ ATF_REQUIRE_EQ(10, state.to_integer(-2));
+ ATF_REQUIRE(state.is_string(-1));
+ ATF_REQUIRE_EQ("foo", state.to_string(-1));
+ state.pop(3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_lua__ok);
+ATF_TEST_CASE_BODY(set_lua__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("top.integer");
+ tree.define< wrapped_int_node >("top.custom");
+ tree.define_dynamic("dynamic");
+
+ {
+ lutok::state state;
+ state.push_integer(5);
+ state.push_integer(10);
+ state.push_string("foo");
+ tree.set_lua("top.integer", state, -3);
+ tree.set_lua("top.custom", state, -2);
+ tree.set_lua("dynamic.first", state, -1);
+ state.pop(3);
+ }
+
+ ATF_REQUIRE_EQ(5, tree.lookup< config::int_node >("top.integer"));
+ ATF_REQUIRE_EQ(10, tree.lookup< wrapped_int_node >("top.custom").value());
+ ATF_REQUIRE_EQ("foo", tree.lookup< config::string_node >("dynamic.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_rw);
+ATF_TEST_CASE_BODY(lookup_rw)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::bool_node >("var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::bool_node >("var3", false);
+
+ tree.lookup_rw< config::int_node >("var1") += 10;
+ ATF_REQUIRE_EQ(52, tree.lookup< config::int_node >("var1"));
+ ATF_REQUIRE(!tree.lookup< config::bool_node >("var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__ok);
+ATF_TEST_CASE_BODY(lookup_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("var1");
+ tree.define< config::string_node >("b.var2");
+ tree.define< config::bool_node >("c.d.var3");
+
+ tree.set< config::int_node >("var1", 42);
+ tree.set< config::string_node >("b.var2", "hello");
+ tree.set< config::bool_node >("c.d.var3", false);
+
+ ATF_REQUIRE_EQ("42", tree.lookup_string("var1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup_string("b.var2"));
+ ATF_REQUIRE_EQ("false", tree.lookup_string("c.d.var3"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__invalid_key);
+ATF_TEST_CASE_BODY(lookup_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.lookup_string(""));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(lookup_string__unknown_key);
+ATF_TEST_CASE_BODY(lookup_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("a.b.c");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b"));
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.lookup_string("a.b.c.d"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__ok);
+ATF_TEST_CASE_BODY(set_string__ok)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar.1");
+ tree.define< config::string_node >("foo.bar.2");
+ tree.define_dynamic("sub.tree");
+
+ tree.set_string("foo.bar.1", "42");
+ tree.set_string("foo.bar.2", "hello");
+ tree.set_string("sub.tree.2", "15");
+ tree.set_string("sub.tree.3.4", "bye");
+
+ ATF_REQUIRE_EQ(42, tree.lookup< config::int_node >("foo.bar.1"));
+ ATF_REQUIRE_EQ("hello", tree.lookup< config::string_node >("foo.bar.2"));
+ ATF_REQUIRE_EQ("15", tree.lookup< config::string_node >("sub.tree.2"));
+ ATF_REQUIRE_EQ("bye", tree.lookup< config::string_node >("sub.tree.3.4"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key);
+ATF_TEST_CASE_BODY(set_string__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.set_string(".", "foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__invalid_key_value);
+ATF_TEST_CASE_BODY(set_string__invalid_key_value)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo", "abc"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", " -3"));
+ ATF_REQUIRE_THROW(config::invalid_key_value,
+ tree.set_string("foo.bar", "3 "));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key);
+ATF_TEST_CASE_BODY(set_string__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error, tree.set_string("abc", "2"));
+
+ tree.set_string("foo.bar", "15");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("foo.bar.baz", "0"));
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.c", "100"));
+ tree.set_string("a.b.c", "-3");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.b.c.d", "82"));
+ tree.set_string("a.d.3", "bar");
+ tree.set_string("a.d.4", "bar");
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.set_string("a.d.4.5", "82"));
+ tree.set_string("a.d.5.6", "82");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(set_string__unknown_key_not_strict);
+ATF_TEST_CASE_BODY(set_string__unknown_key_not_strict)
+{
+ config::tree tree(false);
+
+ tree.define< config::int_node >("foo.bar");
+ tree.define< config::int_node >("a.b.c");
+ tree.define_dynamic("a.d");
+ tree.set_string("a.b.c", "123");
+ tree.set_string("a.d.3", "foo");
+
+ tree.set_string("abc", "2");
+ ATF_REQUIRE(!tree.is_set("abc"));
+
+ tree.set_string("foo.bar", "15");
+ tree.set_string("foo.bar.baz", "0");
+ ATF_REQUIRE(!tree.is_set("foo.bar.baz"));
+
+ tree.set_string("a.c", "100");
+ ATF_REQUIRE(!tree.is_set("a.c"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__none);
+ATF_TEST_CASE_BODY(all_properties__none)
+{
+ const config::tree tree;
+ ATF_REQUIRE(tree.all_properties().empty());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__all_set);
+ATF_TEST_CASE_BODY(all_properties__all_set)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("plain");
+ tree.set< config::int_node >("plain", 1234);
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+ tree.set< config::string_node >("static.second", "some text");
+
+ tree.define_dynamic("dynamic");
+ tree.set< config::string_node >("dynamic.first", "hello");
+ tree.set< config::string_node >("dynamic.second", "bye");
+
+ config::properties_map exp_properties;
+ exp_properties["plain"] = "1234";
+ exp_properties["static.first"] = "-3";
+ exp_properties["static.second"] = "some text";
+ exp_properties["dynamic.first"] = "hello";
+ exp_properties["dynamic.second"] = "bye";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__some_unset);
+ATF_TEST_CASE_BODY(all_properties__some_unset)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("static.first");
+ tree.set< config::int_node >("static.first", -3);
+ tree.define< config::string_node >("static.second");
+
+ tree.define_dynamic("dynamic");
+
+ config::properties_map exp_properties;
+ exp_properties["static.first"] = "-3";
+
+ const config::properties_map properties = tree.all_properties();
+ ATF_REQUIRE(exp_properties == properties);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__inner);
+ATF_TEST_CASE_BODY(all_properties__subtree__inner)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.b.c.first"] = "1";
+ exp_properties["root.a.b.c.second"] = "2";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b"));
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.b.c"));
+ }
+
+ {
+ config::properties_map exp_properties;
+ exp_properties["root.a.d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a.d"));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__leaf);
+ATF_TEST_CASE_BODY(all_properties__subtree__leaf)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.first"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__strip_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__strip_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.define< config::int_node >("root.a.b.c.second");
+ tree.define< config::int_node >("root.a.d.first");
+
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.set< config::int_node >("root.a.b.c.second", 2);
+ tree.set< config::int_node >("root.a.d.first", 3);
+
+ config::properties_map exp_properties;
+ exp_properties["b.c.first"] = "1";
+ exp_properties["b.c.second"] = "2";
+ exp_properties["d.first"] = "3";
+ ATF_REQUIRE(exp_properties == tree.all_properties("root.a", true));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__invalid_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__invalid_key)
+{
+ config::tree tree;
+
+ ATF_REQUIRE_THROW(config::invalid_key_error, tree.all_properties("."));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(all_properties__subtree__unknown_key);
+ATF_TEST_CASE_BODY(all_properties__subtree__unknown_key)
+{
+ config::tree tree;
+
+ tree.define< config::int_node >("root.a.b.c.first");
+ tree.set< config::int_node >("root.a.b.c.first", 1);
+ tree.define< config::int_node >("root.a.b.c.unset");
+
+ ATF_REQUIRE_THROW(config::unknown_key_error,
+ tree.all_properties("root.a.b.c.first.foo"));
+ ATF_REQUIRE_THROW_RE(config::value_error, "Cannot export.*leaf",
+ tree.all_properties("root.a.b.c.unset"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__empty);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__empty)
+{
+ config::tree t1;
+ config::tree t2;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__shallow_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__shallow_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1;
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__deep_copy);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__deep_copy)
+{
+ config::tree t1;
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ config::tree t2 = t1.deep_copy();
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(operators_eq_and_ne__some_contents);
+ATF_TEST_CASE_BODY(operators_eq_and_ne__some_contents)
+{
+ config::tree t1, t2;
+
+ t1.define< config::int_node >("root.a.b.c.first");
+ t1.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::int_node >("root.a.b.c.first");
+ t2.set< config::int_node >("root.a.b.c.first", 1);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.set< config::int_node >("root.a.b.c.first", 2);
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+
+ t1.define< config::string_node >("another.key");
+ t1.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE(!(t1 == t2));
+ ATF_REQUIRE( t1 != t2);
+
+ t2.define< config::string_node >("another.key");
+ t2.set< config::string_node >("another.key", "some text");
+ ATF_REQUIRE( t1 == t2);
+ ATF_REQUIRE(!(t1 != t2));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(custom_leaf__no_default_ctor);
+ATF_TEST_CASE_BODY(custom_leaf__no_default_ctor)
+{
+ config::tree tree;
+
+ tree.define< wrapped_int_node >("test1");
+ tree.define< wrapped_int_node >("test2");
+ tree.set< wrapped_int_node >("test1", int_wrapper(5));
+ tree.set< wrapped_int_node >("test2", int_wrapper(10));
+ const int_wrapper& test1 = tree.lookup< wrapped_int_node >("test1");
+ ATF_REQUIRE_EQ(5, test1.value());
+ const int_wrapper& test2 = tree.lookup< wrapped_int_node >("test2");
+ ATF_REQUIRE_EQ(10, test2.value());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__one_level);
+ ATF_ADD_TEST_CASE(tcs, define_set_lookup__multiple_levels);
+
+ ATF_ADD_TEST_CASE(tcs, deep_copy__empty);
+ ATF_ADD_TEST_CASE(tcs, deep_copy__some);
+
+ ATF_ADD_TEST_CASE(tcs, combine__empty);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_overrides);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__no_base);
+ ATF_ADD_TEST_CASE(tcs, combine__same_layout__mix);
+ ATF_ADD_TEST_CASE(tcs, combine__different_layout);
+ ATF_ADD_TEST_CASE(tcs, combine__dynamic_wins);
+ ATF_ADD_TEST_CASE(tcs, combine__inner_leaf_mismatch);
+
+ ATF_ADD_TEST_CASE(tcs, lookup__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, is_set__one_level);
+ ATF_ADD_TEST_CASE(tcs, is_set__multiple_levels);
+ ATF_ADD_TEST_CASE(tcs, is_set__invalid_key);
+
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, push_lua__ok);
+ ATF_ADD_TEST_CASE(tcs, set_lua__ok);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_rw);
+
+ ATF_ADD_TEST_CASE(tcs, lookup_string__ok);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, lookup_string__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, set_string__ok);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__invalid_key_value);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key);
+ ATF_ADD_TEST_CASE(tcs, set_string__unknown_key_not_strict);
+
+ ATF_ADD_TEST_CASE(tcs, all_properties__none);
+ ATF_ADD_TEST_CASE(tcs, all_properties__all_set);
+ ATF_ADD_TEST_CASE(tcs, all_properties__some_unset);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__inner);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__leaf);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__strip_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__invalid_key);
+ ATF_ADD_TEST_CASE(tcs, all_properties__subtree__unknown_key);
+
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__empty);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__shallow_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__deep_copy);
+ ATF_ADD_TEST_CASE(tcs, operators_eq_and_ne__some_contents);
+
+ ATF_ADD_TEST_CASE(tcs, custom_leaf__no_default_ctor);
+}
diff --git a/utils/datetime.cpp b/utils/datetime.cpp
new file mode 100644
index 000000000000..ae3fdb62fe55
--- /dev/null
+++ b/utils/datetime.cpp
@@ -0,0 +1,613 @@
+// Copyright 2010 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/datetime.hpp"
+
+extern "C" {
+#include <sys/time.h>
+
+#include <time.h>
+}
+
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/optional.ipp"
+#include "utils/noncopyable.hpp"
+#include "utils/sanity.hpp"
+
+namespace datetime = utils::datetime;
+
+using utils::none;
+using utils::optional;
+
+
+namespace {
+
+
+/// Fake value for the current time.
+static optional< datetime::timestamp > mock_now = none;
+
+
+} // anonymous namespace
+
+
+/// Creates a zero time delta.
+datetime::delta::delta(void) :
+ seconds(0),
+ useconds(0)
+{
+}
+
+
+/// Creates a time delta.
+///
+/// \param seconds_ The seconds in the delta.
+/// \param useconds_ The microseconds in the delta.
+///
+/// \throw std::runtime_error If the input delta is negative.
+datetime::delta::delta(const int64_t seconds_,
+ const unsigned long useconds_) :
+ seconds(seconds_),
+ useconds(useconds_)
+{
+ if (seconds_ < 0) {
+ throw std::runtime_error(F("Negative deltas are not supported by the "
+ "datetime::delta class; got: %s") % (*this));
+ }
+}
+
+
+/// Converts a time expressed in microseconds to a delta.
+///
+/// \param useconds The amount of microseconds representing the delta.
+///
+/// \return A new delta object.
+///
+/// \throw std::runtime_error If the input delta is negative.
+datetime::delta
+datetime::delta::from_microseconds(const int64_t useconds)
+{
+ if (useconds < 0) {
+ throw std::runtime_error(F("Negative deltas are not supported by the "
+ "datetime::delta class; got: %sus") %
+ useconds);
+ }
+
+ return delta(useconds / 1000000, useconds % 1000000);
+}
+
+
+/// Convers the delta to a flat representation expressed in microseconds.
+///
+/// \return The amount of microseconds that corresponds to this delta.
+int64_t
+datetime::delta::to_microseconds(void) const
+{
+ return seconds * 1000000 + useconds;
+}
+
+
+/// Checks if two time deltas are equal.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two time deltas are equals; false otherwise.
+bool
+datetime::delta::operator==(const datetime::delta& other) const
+{
+ return seconds == other.seconds && useconds == other.useconds;
+}
+
+
+/// Checks if two time deltas are different.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two time deltas are different; false otherwise.
+bool
+datetime::delta::operator!=(const datetime::delta& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Checks if this time delta is shorter than another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is shorter than other; false otherwise.
+bool
+datetime::delta::operator<(const datetime::delta& other) const
+{
+ return seconds < other.seconds ||
+ (seconds == other.seconds && useconds < other.useconds);
+}
+
+
+/// Checks if this time delta is shorter than or equal to another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is shorter than or equal to; false
+/// otherwise.
+bool
+datetime::delta::operator<=(const datetime::delta& other) const
+{
+ return (*this) < other || (*this) == other;
+}
+
+
+/// Checks if this time delta is larger than another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is larger than other; false otherwise.
+bool
+datetime::delta::operator>(const datetime::delta& other) const
+{
+ return seconds > other.seconds ||
+ (seconds == other.seconds && useconds > other.useconds);
+}
+
+
+/// Checks if this time delta is larger than or equal to another one.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this time delta is larger than or equal to; false
+/// otherwise.
+bool
+datetime::delta::operator>=(const datetime::delta& other) const
+{
+ return (*this) > other || (*this) == other;
+}
+
+
+/// Adds a time delta to this one.
+///
+/// \param other The time delta to add.
+///
+/// \return The addition of this time delta with the other time delta.
+datetime::delta
+datetime::delta::operator+(const datetime::delta& other) const
+{
+ return delta::from_microseconds(to_microseconds() +
+ other.to_microseconds());
+}
+
+
+/// Adds a time delta to this one and updates this with the result.
+///
+/// \param other The time delta to add.
+///
+/// \return The addition of this time delta with the other time delta.
+datetime::delta&
+datetime::delta::operator+=(const datetime::delta& other)
+{
+ *this = *this + other;
+ return *this;
+}
+
+
+/// Scales this delta by a positive integral factor.
+///
+/// \param factor The scaling factor.
+///
+/// \return The scaled delta.
+datetime::delta
+datetime::delta::operator*(const std::size_t factor) const
+{
+ return delta::from_microseconds(to_microseconds() * factor);
+}
+
+
+/// Scales this delta by and updates this delta with the result.
+///
+/// \param factor The scaling factor.
+///
+/// \return The scaled delta as a reference to the input object.
+datetime::delta&
+datetime::delta::operator*=(const std::size_t factor)
+{
+ *this = *this * factor;
+ return *this;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+std::ostream&
+datetime::operator<<(std::ostream& output, const delta& object)
+{
+ return (output << object.to_microseconds() << "us");
+}
+
+
+namespace utils {
+namespace datetime {
+
+
+/// Internal representation for datetime::timestamp.
+struct timestamp::impl : utils::noncopyable {
+ /// The raw timestamp as provided by libc.
+ ::timeval data;
+
+ /// Constructs an impl object from initialized data.
+ ///
+ /// \param data_ The raw timestamp to use.
+ impl(const ::timeval& data_) : data(data_)
+ {
+ }
+};
+
+
+} // namespace datetime
+} // namespace utils
+
+
+/// Constructs a new timestamp.
+///
+/// \param pimpl_ An existing impl representation.
+datetime::timestamp::timestamp(std::shared_ptr< impl > pimpl_) :
+ _pimpl(pimpl_)
+{
+}
+
+
+/// Constructs a timestamp from the amount of microseconds since the epoch.
+///
+/// \param value Microseconds since the epoch in UTC. Must be positive.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::from_microseconds(const int64_t value)
+{
+ PRE(value >= 0);
+ ::timeval data;
+ data.tv_sec = static_cast< time_t >(value / 1000000);
+ data.tv_usec = static_cast< suseconds_t >(value % 1000000);
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Constructs a timestamp based on user-friendly values.
+///
+/// \param year The year in the [1900,inf) range.
+/// \param month The month in the [1,12] range.
+/// \param day The day in the [1,30] range.
+/// \param hour The hour in the [0,23] range.
+/// \param minute The minute in the [0,59] range.
+/// \param second The second in the [0,60] range. Yes, that is 60, which can be
+/// the case on leap seconds.
+/// \param microsecond The microsecond in the [0,999999] range.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::from_values(const int year, const int month,
+ const int day, const int hour,
+ const int minute, const int second,
+ const int microsecond)
+{
+ PRE(year >= 1900);
+ PRE(month >= 1 && month <= 12);
+ PRE(day >= 1 && day <= 30);
+ PRE(hour >= 0 && hour <= 23);
+ PRE(minute >= 0 && minute <= 59);
+ PRE(second >= 0 && second <= 60);
+ PRE(microsecond >= 0 && microsecond <= 999999);
+
+ // The code below is quite convoluted. The problem is that we can't assume
+ // that some fields (like tm_zone) of ::tm exist, and thus we can't blindly
+ // set them from the code. Instead of detecting their presence in the
+ // configure script, we just query the current time to initialize such
+ // fields and then we override the ones we are interested in. (There might
+ // be some better way to do this, but I don't know it and the documentation
+ // does not shed much light into how to create your own fake date.)
+
+ const time_t current_time = ::time(NULL);
+
+ ::tm timedata;
+ if (::gmtime_r(&current_time, &timedata) == NULL)
+ UNREACHABLE;
+
+ timedata.tm_sec = second;
+ timedata.tm_min = minute;
+ timedata.tm_hour = hour;
+ timedata.tm_mday = day;
+ timedata.tm_mon = month - 1;
+ timedata.tm_year = year - 1900;
+ // Ignored: timedata.tm_wday
+ // Ignored: timedata.tm_yday
+
+ ::timeval data;
+ data.tv_sec = ::mktime(&timedata);
+ data.tv_usec = static_cast< suseconds_t >(microsecond);
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Constructs a new timestamp representing the current time in UTC.
+///
+/// \return A new timestamp.
+datetime::timestamp
+datetime::timestamp::now(void)
+{
+ if (mock_now)
+ return mock_now.get();
+
+ ::timeval data;
+ {
+ const int ret = ::gettimeofday(&data, NULL);
+ INV(ret != -1);
+ }
+
+ return timestamp(std::shared_ptr< impl >(new impl(data)));
+}
+
+
+/// Formats a timestamp.
+///
+/// \param format The format string to use as consumed by strftime(3).
+///
+/// \return The formatted time.
+std::string
+datetime::timestamp::strftime(const std::string& format) const
+{
+ ::tm timedata;
+ // This conversion to time_t is necessary because tv_sec is not guaranteed
+ // to be a time_t. For example, it isn't in NetBSD 5.x
+ ::time_t epoch_seconds;
+ epoch_seconds = _pimpl->data.tv_sec;
+ if (::gmtime_r(&epoch_seconds, &timedata) == NULL)
+ UNREACHABLE_MSG("gmtime_r(3) did not accept the value returned by "
+ "gettimeofday(2)");
+
+ char buf[128];
+ if (::strftime(buf, sizeof(buf), format.c_str(), &timedata) == 0)
+ UNREACHABLE_MSG("Arbitrary-long format strings are unimplemented");
+ return buf;
+}
+
+
+/// Formats a timestamp with the ISO 8601 standard and in UTC.
+///
+/// \return A string with the formatted timestamp.
+std::string
+datetime::timestamp::to_iso8601_in_utc(void) const
+{
+ return F("%s.%06sZ") % strftime("%Y-%m-%dT%H:%M:%S") % _pimpl->data.tv_usec;
+}
+
+
+/// Returns the number of microseconds since the epoch in UTC.
+///
+/// \return A number of microseconds.
+int64_t
+datetime::timestamp::to_microseconds(void) const
+{
+ return static_cast< int64_t >(_pimpl->data.tv_sec) * 1000000 +
+ _pimpl->data.tv_usec;
+}
+
+
+/// Returns the number of seconds since the epoch in UTC.
+///
+/// \return A number of seconds.
+int64_t
+datetime::timestamp::to_seconds(void) const
+{
+ return static_cast< int64_t >(_pimpl->data.tv_sec);
+}
+
+
+/// Sets the current time for testing purposes.
+void
+datetime::set_mock_now(const int year, const int month,
+ const int day, const int hour,
+ const int minute, const int second,
+ const int microsecond)
+{
+ mock_now = timestamp::from_values(year, month, day, hour, minute, second,
+ microsecond);
+}
+
+
+/// Sets the current time for testing purposes.
+///
+/// \param mock_now_ The mock timestamp to set the time to.
+void
+datetime::set_mock_now(const timestamp& mock_now_)
+{
+ mock_now = mock_now_;
+}
+
+
+/// Checks if two timestamps are equal.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two timestamps are equals; false otherwise.
+bool
+datetime::timestamp::operator==(const datetime::timestamp& other) const
+{
+ return _pimpl->data.tv_sec == other._pimpl->data.tv_sec &&
+ _pimpl->data.tv_usec == other._pimpl->data.tv_usec;
+}
+
+
+/// Checks if two timestamps are different.
+///
+/// \param other The object to compare to.
+///
+/// \return True if the two timestamps are different; false otherwise.
+bool
+datetime::timestamp::operator!=(const datetime::timestamp& other) const
+{
+ return !(*this == other);
+}
+
+
+/// Checks if a timestamp is before another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes before other; false otherwise.
+bool
+datetime::timestamp::operator<(const datetime::timestamp& other) const
+{
+ return to_microseconds() < other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is before or equal to another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes before other or is equal to it;
+/// false otherwise.
+bool
+datetime::timestamp::operator<=(const datetime::timestamp& other) const
+{
+ return to_microseconds() <= other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is after another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes after other; false otherwise;
+bool
+datetime::timestamp::operator>(const datetime::timestamp& other) const
+{
+ return to_microseconds() > other.to_microseconds();
+}
+
+
+/// Checks if a timestamp is after or equal to another.
+///
+/// \param other The object to compare to.
+///
+/// \return True if this timestamp comes after other or is equal to it;
+/// false otherwise.
+bool
+datetime::timestamp::operator>=(const datetime::timestamp& other) const
+{
+ return to_microseconds() >= other.to_microseconds();
+}
+
+
+/// Calculates the addition of a delta to a timestamp.
+///
+/// \param other The delta to add.
+///
+/// \return A new timestamp in the future.
+datetime::timestamp
+datetime::timestamp::operator+(const datetime::delta& other) const
+{
+ return datetime::timestamp::from_microseconds(to_microseconds() +
+ other.to_microseconds());
+}
+
+
+/// Calculates the addition of a delta to this timestamp.
+///
+/// \param other The delta to add.
+///
+/// \return A reference to the modified timestamp.
+datetime::timestamp&
+datetime::timestamp::operator+=(const datetime::delta& other)
+{
+ *this = *this + other;
+ return *this;
+}
+
+
+/// Calculates the subtraction of a delta from a timestamp.
+///
+/// \param other The delta to subtract.
+///
+/// \return A new timestamp in the past.
+datetime::timestamp
+datetime::timestamp::operator-(const datetime::delta& other) const
+{
+ return datetime::timestamp::from_microseconds(to_microseconds() -
+ other.to_microseconds());
+}
+
+
+/// Calculates the subtraction of a delta from this timestamp.
+///
+/// \param other The delta to subtract.
+///
+/// \return A reference to the modified timestamp.
+datetime::timestamp&
+datetime::timestamp::operator-=(const datetime::delta& other)
+{
+ *this = *this - other;
+ return *this;
+}
+
+
+/// Calculates the delta between two timestamps.
+///
+/// \param other The subtrahend.
+///
+/// \return The difference between this object and the other object.
+///
+/// \throw std::runtime_error If the subtraction would result in a negative time
+/// delta, which are currently not supported.
+datetime::delta
+datetime::timestamp::operator-(const datetime::timestamp& other) const
+{
+ if ((*this) < other) {
+ throw std::runtime_error(
+ F("Cannot subtract %s from %s as it would result in a negative "
+ "datetime::delta, which are not supported") % other % (*this));
+ }
+ return datetime::delta::from_microseconds(to_microseconds() -
+ other.to_microseconds());
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+std::ostream&
+datetime::operator<<(std::ostream& output, const timestamp& object)
+{
+ return (output << object.to_microseconds() << "us");
+}
diff --git a/utils/datetime.hpp b/utils/datetime.hpp
new file mode 100644
index 000000000000..0c24f332f6d3
--- /dev/null
+++ b/utils/datetime.hpp
@@ -0,0 +1,140 @@
+// Copyright 2010 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/datetime.hpp
+/// Provides date and time-related classes and functions.
+
+#if !defined(UTILS_DATETIME_HPP)
+#define UTILS_DATETIME_HPP
+
+#include "utils/datetime_fwd.hpp"
+
+extern "C" {
+#include <stdint.h>
+}
+
+#include <cstddef>
+#include <memory>
+#include <ostream>
+#include <string>
+
+
+namespace utils {
+namespace datetime {
+
+
+/// Represents a time delta to describe deadlines.
+///
+/// Because we use this class to handle deadlines, we currently do not support
+/// negative deltas.
+class delta {
+public:
+ /// The amount of seconds in the time delta.
+ int64_t seconds;
+
+ /// The amount of microseconds in the time delta.
+ unsigned long useconds;
+
+ delta(void);
+ delta(const int64_t, const unsigned long);
+
+ static delta from_microseconds(const int64_t);
+ int64_t to_microseconds(void) const;
+
+ bool operator==(const delta&) const;
+ bool operator!=(const delta&) const;
+ bool operator<(const delta&) const;
+ bool operator<=(const delta&) const;
+ bool operator>(const delta&) const;
+ bool operator>=(const delta&) const;
+
+ delta operator+(const delta&) const;
+ delta& operator+=(const delta&);
+ // operator- and operator-= do not exist because we do not support negative
+ // deltas. See class docstring.
+ delta operator*(const std::size_t) const;
+ delta& operator*=(const std::size_t);
+};
+
+
+std::ostream& operator<<(std::ostream&, const delta&);
+
+
+/// Represents a fixed date/time.
+///
+/// Timestamps are immutable objects and therefore we can simply use a shared
+/// pointer to hide the implementation type of the date. By not using an auto
+/// pointer, we don't have to worry about providing our own copy constructor and
+/// assignment opertor.
+class timestamp {
+ struct impl;
+
+ /// Pointer to the shared internal implementation.
+ std::shared_ptr< impl > _pimpl;
+
+ timestamp(std::shared_ptr< impl >);
+
+public:
+ static timestamp from_microseconds(const int64_t);
+ static timestamp from_values(const int, const int, const int,
+ const int, const int, const int,
+ const int);
+ static timestamp now(void);
+
+ std::string strftime(const std::string&) const;
+ std::string to_iso8601_in_utc(void) const;
+ int64_t to_microseconds(void) const;
+ int64_t to_seconds(void) const;
+
+ bool operator==(const timestamp&) const;
+ bool operator!=(const timestamp&) const;
+ bool operator<(const timestamp&) const;
+ bool operator<=(const timestamp&) const;
+ bool operator>(const timestamp&) const;
+ bool operator>=(const timestamp&) const;
+
+ timestamp operator+(const delta&) const;
+ timestamp& operator+=(const delta&);
+ timestamp operator-(const delta&) const;
+ timestamp& operator-=(const delta&);
+ delta operator-(const timestamp&) const;
+};
+
+
+std::ostream& operator<<(std::ostream&, const timestamp&);
+
+
+void set_mock_now(const int, const int, const int, const int, const int,
+ const int, const int);
+void set_mock_now(const timestamp&);
+
+
+} // namespace datetime
+} // namespace utils
+
+#endif // !defined(UTILS_DATETIME_HPP)
diff --git a/utils/datetime_fwd.hpp b/utils/datetime_fwd.hpp
new file mode 100644
index 000000000000..1dd886070a34
--- /dev/null
+++ b/utils/datetime_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/datetime_fwd.hpp
+/// Forward declarations for utils/datetime.hpp
+
+#if !defined(UTILS_DATETIME_FWD_HPP)
+#define UTILS_DATETIME_FWD_HPP
+
+namespace utils {
+namespace datetime {
+
+
+class delta;
+class timestamp;
+
+
+} // namespace datetime
+} // namespace utils
+
+#endif // !defined(UTILS_DATETIME_FWD_HPP)
diff --git a/utils/datetime_test.cpp b/utils/datetime_test.cpp
new file mode 100644
index 000000000000..9f8ff50cd0f8
--- /dev/null
+++ b/utils/datetime_test.cpp
@@ -0,0 +1,593 @@
+// Copyright 2010 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/datetime.hpp"
+
+extern "C" {
+#include <time.h>
+#include <unistd.h>
+}
+
+#include <sstream>
+#include <stdexcept>
+
+#include <atf-c++.hpp>
+
+namespace datetime = utils::datetime;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__defaults);
+ATF_TEST_CASE_BODY(delta__defaults)
+{
+ const datetime::delta delta;
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__overrides);
+ATF_TEST_CASE_BODY(delta__overrides)
+{
+ const datetime::delta delta(1, 2);
+ ATF_REQUIRE_EQ(1, delta.seconds);
+ ATF_REQUIRE_EQ(2, delta.useconds);
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error, "Negative.*not supported.*-4999997us",
+ datetime::delta(-5, 3));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__from_microseconds);
+ATF_TEST_CASE_BODY(delta__from_microseconds)
+{
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(0);
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 999999);
+ ATF_REQUIRE_EQ(0, delta.seconds);
+ ATF_REQUIRE_EQ(999999, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 1000000);
+ ATF_REQUIRE_EQ(1, delta.seconds);
+ ATF_REQUIRE_EQ(0, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 10576293);
+ ATF_REQUIRE_EQ(10, delta.seconds);
+ ATF_REQUIRE_EQ(576293, delta.useconds);
+ }
+ {
+ const datetime::delta delta = datetime::delta::from_microseconds(
+ 123456789123456LL);
+ ATF_REQUIRE_EQ(123456789, delta.seconds);
+ ATF_REQUIRE_EQ(123456, delta.useconds);
+ }
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error, "Negative.*not supported.*-12345us",
+ datetime::delta::from_microseconds(-12345));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__to_microseconds);
+ATF_TEST_CASE_BODY(delta__to_microseconds)
+{
+ ATF_REQUIRE_EQ(0, datetime::delta(0, 0).to_microseconds());
+ ATF_REQUIRE_EQ(999999, datetime::delta(0, 999999).to_microseconds());
+ ATF_REQUIRE_EQ(1000000, datetime::delta(1, 0).to_microseconds());
+ ATF_REQUIRE_EQ(10576293, datetime::delta(10, 576293).to_microseconds());
+ ATF_REQUIRE_EQ(11576293, datetime::delta(10, 1576293).to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__equals);
+ATF_TEST_CASE_BODY(delta__equals)
+{
+ ATF_REQUIRE(datetime::delta() == datetime::delta());
+ ATF_REQUIRE(datetime::delta() == datetime::delta(0, 0));
+ ATF_REQUIRE(datetime::delta(1, 2) == datetime::delta(1, 2));
+
+ ATF_REQUIRE(!(datetime::delta() == datetime::delta(0, 1)));
+ ATF_REQUIRE(!(datetime::delta() == datetime::delta(1, 0)));
+ ATF_REQUIRE(!(datetime::delta(1, 2) == datetime::delta(2, 1)));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__differs);
+ATF_TEST_CASE_BODY(delta__differs)
+{
+ ATF_REQUIRE(!(datetime::delta() != datetime::delta()));
+ ATF_REQUIRE(!(datetime::delta() != datetime::delta(0, 0)));
+ ATF_REQUIRE(!(datetime::delta(1, 2) != datetime::delta(1, 2)));
+
+ ATF_REQUIRE(datetime::delta() != datetime::delta(0, 1));
+ ATF_REQUIRE(datetime::delta() != datetime::delta(1, 0));
+ ATF_REQUIRE(datetime::delta(1, 2) != datetime::delta(2, 1));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__sorting);
+ATF_TEST_CASE_BODY(delta__sorting)
+{
+ ATF_REQUIRE(!(datetime::delta() < datetime::delta()));
+ ATF_REQUIRE( datetime::delta() <= datetime::delta());
+ ATF_REQUIRE(!(datetime::delta() > datetime::delta()));
+ ATF_REQUIRE( datetime::delta() >= datetime::delta());
+
+ ATF_REQUIRE(!(datetime::delta(9, 8) < datetime::delta(9, 8)));
+ ATF_REQUIRE( datetime::delta(9, 8) <= datetime::delta(9, 8));
+ ATF_REQUIRE(!(datetime::delta(9, 8) > datetime::delta(9, 8)));
+ ATF_REQUIRE( datetime::delta(9, 8) >= datetime::delta(9, 8));
+
+ ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(4, 8));
+ ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(4, 8));
+ ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(4, 8)));
+ ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(4, 8)));
+
+ ATF_REQUIRE( datetime::delta(2, 5) < datetime::delta(2, 8));
+ ATF_REQUIRE( datetime::delta(2, 5) <= datetime::delta(2, 8));
+ ATF_REQUIRE(!(datetime::delta(2, 5) > datetime::delta(2, 8)));
+ ATF_REQUIRE(!(datetime::delta(2, 5) >= datetime::delta(2, 8)));
+
+ ATF_REQUIRE(!(datetime::delta(4, 8) < datetime::delta(2, 5)));
+ ATF_REQUIRE(!(datetime::delta(4, 8) <= datetime::delta(2, 5)));
+ ATF_REQUIRE( datetime::delta(4, 8) > datetime::delta(2, 5));
+ ATF_REQUIRE( datetime::delta(4, 8) >= datetime::delta(2, 5));
+
+ ATF_REQUIRE(!(datetime::delta(2, 8) < datetime::delta(2, 5)));
+ ATF_REQUIRE(!(datetime::delta(2, 8) <= datetime::delta(2, 5)));
+ ATF_REQUIRE( datetime::delta(2, 8) > datetime::delta(2, 5));
+ ATF_REQUIRE( datetime::delta(2, 8) >= datetime::delta(2, 5));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__addition);
+ATF_TEST_CASE_BODY(delta__addition)
+{
+ using datetime::delta;
+
+ ATF_REQUIRE_EQ(delta(), delta() + delta());
+ ATF_REQUIRE_EQ(delta(0, 10), delta() + delta(0, 10));
+ ATF_REQUIRE_EQ(delta(10, 0), delta(10, 0) + delta());
+
+ ATF_REQUIRE_EQ(delta(1, 234567), delta(0, 1234567) + delta());
+ ATF_REQUIRE_EQ(delta(12, 34), delta(10, 20) + delta(2, 14));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__addition_and_set);
+ATF_TEST_CASE_BODY(delta__addition_and_set)
+{
+ using datetime::delta;
+
+ {
+ delta d;
+ d += delta(3, 5);
+ ATF_REQUIRE_EQ(delta(3, 5), d);
+ }
+ {
+ delta d(1, 2);
+ d += delta(3, 5);
+ ATF_REQUIRE_EQ(delta(4, 7), d);
+ }
+ {
+ delta d(1, 2);
+ ATF_REQUIRE_EQ(delta(4, 7), (d += delta(3, 5)));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__scale);
+ATF_TEST_CASE_BODY(delta__scale)
+{
+ using datetime::delta;
+
+ ATF_REQUIRE_EQ(delta(), delta() * 0);
+ ATF_REQUIRE_EQ(delta(), delta() * 5);
+
+ ATF_REQUIRE_EQ(delta(0, 30), delta(0, 10) * 3);
+ ATF_REQUIRE_EQ(delta(17, 500000), delta(3, 500000) * 5);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__scale_and_set);
+ATF_TEST_CASE_BODY(delta__scale_and_set)
+{
+ using datetime::delta;
+
+ {
+ delta d(3, 5);
+ d *= 2;
+ ATF_REQUIRE_EQ(delta(6, 10), d);
+ }
+ {
+ delta d(8, 0);
+ d *= 8;
+ ATF_REQUIRE_EQ(delta(64, 0), d);
+ }
+ {
+ delta d(3, 5);
+ ATF_REQUIRE_EQ(delta(9, 15), (d *= 3));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(delta__output);
+ATF_TEST_CASE_BODY(delta__output)
+{
+ {
+ std::ostringstream str;
+ str << datetime::delta(15, 8791);
+ ATF_REQUIRE_EQ("15008791us", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << datetime::delta(12345678, 0);
+ ATF_REQUIRE_EQ("12345678000000us", str.str());
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__copy);
+ATF_TEST_CASE_BODY(timestamp__copy)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2011, 2, 16, 19, 15, 30, 0);
+ {
+ const datetime::timestamp ts2 = ts1;
+ const datetime::timestamp ts3 = datetime::timestamp::from_values(
+ 2012, 2, 16, 19, 15, 30, 0);
+ ATF_REQUIRE_EQ("2011", ts1.strftime("%Y"));
+ ATF_REQUIRE_EQ("2011", ts2.strftime("%Y"));
+ ATF_REQUIRE_EQ("2012", ts3.strftime("%Y"));
+ }
+ ATF_REQUIRE_EQ("2011", ts1.strftime("%Y"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__from_microseconds);
+ATF_TEST_CASE_BODY(timestamp__from_microseconds)
+{
+ const datetime::timestamp ts = datetime::timestamp::from_microseconds(
+ 1328829351987654LL);
+ ATF_REQUIRE_EQ("2012-02-09 23:15:51", ts.strftime("%Y-%m-%d %H:%M:%S"));
+ ATF_REQUIRE_EQ(1328829351987654LL, ts.to_microseconds());
+ ATF_REQUIRE_EQ(1328829351, ts.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__mock);
+ATF_TEST_CASE_BODY(timestamp__now__mock)
+{
+ datetime::set_mock_now(2011, 2, 21, 18, 5, 10, 0);
+ ATF_REQUIRE_EQ("2011-02-21 18:05:10",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+
+ datetime::set_mock_now(datetime::timestamp::from_values(
+ 2012, 3, 22, 19, 6, 11, 54321));
+ ATF_REQUIRE_EQ("2012-03-22 19:06:11",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+ ATF_REQUIRE_EQ("2012-03-22 19:06:11",
+ datetime::timestamp::now().strftime("%Y-%m-%d %H:%M:%S"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__real);
+ATF_TEST_CASE_BODY(timestamp__now__real)
+{
+ // This test is might fail if we happen to run at the crossing of one
+ // day to the other and the two measures we pick of the current time
+ // differ. This is so unlikely that I haven't bothered to do this in any
+ // other way.
+
+ const time_t just_before = ::time(NULL);
+ const datetime::timestamp now = datetime::timestamp::now();
+
+ ::tm data;
+ char buf[1024];
+ ATF_REQUIRE(::gmtime_r(&just_before, &data) != 0);
+ ATF_REQUIRE(::strftime(buf, sizeof(buf), "%Y-%m-%d", &data) != 0);
+ ATF_REQUIRE_EQ(buf, now.strftime("%Y-%m-%d"));
+
+ ATF_REQUIRE(now.strftime("%Z") == "GMT" || now.strftime("%Z") == "UTC");
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__now__granularity);
+ATF_TEST_CASE_BODY(timestamp__now__granularity)
+{
+ const datetime::timestamp first = datetime::timestamp::now();
+ ::usleep(1);
+ const datetime::timestamp second = datetime::timestamp::now();
+ ATF_REQUIRE(first.to_microseconds() != second.to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__strftime);
+ATF_TEST_CASE_BODY(timestamp__strftime)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 0);
+ ATF_REQUIRE_EQ("2010-12-10", ts1.strftime("%Y-%m-%d"));
+ ATF_REQUIRE_EQ("08:45:50", ts1.strftime("%H:%M:%S"));
+
+ const datetime::timestamp ts2 = datetime::timestamp::from_values(
+ 2011, 2, 16, 19, 15, 30, 0);
+ ATF_REQUIRE_EQ("2011-02-16T19:15:30", ts2.strftime("%Y-%m-%dT%H:%M:%S"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_iso8601_in_utc);
+ATF_TEST_CASE_BODY(timestamp__to_iso8601_in_utc)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 0);
+ ATF_REQUIRE_EQ("2010-12-10T08:45:50.000000Z", ts1.to_iso8601_in_utc());
+
+ const datetime::timestamp ts2= datetime::timestamp::from_values(
+ 2016, 7, 11, 17, 51, 28, 123456);
+ ATF_REQUIRE_EQ("2016-07-11T17:51:28.123456Z", ts2.to_iso8601_in_utc());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_microseconds);
+ATF_TEST_CASE_BODY(timestamp__to_microseconds)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 123456);
+ ATF_REQUIRE_EQ(1291970750123456LL, ts1.to_microseconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__to_seconds);
+ATF_TEST_CASE_BODY(timestamp__to_seconds)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2010, 12, 10, 8, 45, 50, 123456);
+ ATF_REQUIRE_EQ(1291970750, ts1.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__leap_second);
+ATF_TEST_CASE_BODY(timestamp__leap_second)
+{
+ // This is actually a test for from_values(), which is the function that
+ // includes assertions to validate the input parameters.
+ const datetime::timestamp ts1 = datetime::timestamp::from_values(
+ 2012, 6, 30, 23, 59, 60, 543);
+ ATF_REQUIRE_EQ(1341100800, ts1.to_seconds());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__equals);
+ATF_TEST_CASE_BODY(timestamp__equals)
+{
+ ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) ==
+ datetime::timestamp::from_microseconds(1291970750123456LL));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__differs);
+ATF_TEST_CASE_BODY(timestamp__differs)
+{
+ ATF_REQUIRE(datetime::timestamp::from_microseconds(1291970750123456LL) !=
+ datetime::timestamp::from_microseconds(1291970750123455LL));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__sorting);
+ATF_TEST_CASE_BODY(timestamp__sorting)
+{
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+
+ ATF_REQUIRE(!(ts1 < ts2));
+ ATF_REQUIRE( ts1 <= ts2);
+ ATF_REQUIRE(!(ts1 > ts2));
+ ATF_REQUIRE( ts1 >= ts2);
+ }
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970759123455LL);
+
+ ATF_REQUIRE( ts1 < ts2);
+ ATF_REQUIRE( ts1 <= ts2);
+ ATF_REQUIRE(!(ts1 > ts2));
+ ATF_REQUIRE(!(ts1 >= ts2));
+ }
+ {
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970759123455LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123455LL);
+
+ ATF_REQUIRE(!(ts1 < ts2));
+ ATF_REQUIRE(!(ts1 <= ts2));
+ ATF_REQUIRE( ts1 > ts2);
+ ATF_REQUIRE( ts1 >= ts2);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta);
+ATF_TEST_CASE_BODY(timestamp__add_delta)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234),
+ timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) +
+ delta(30, 1234));
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100),
+ timestamp::from_values(2014, 12, 11, 21, 43, 0, 0) +
+ delta(3602, 5000100));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__add_delta_and_set);
+ATF_TEST_CASE_BODY(timestamp__add_delta_and_set)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0);
+ ts += delta(30, 1234);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 30, 1234),
+ ts);
+ }
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 0, 0);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 22, 43, 7, 100),
+ ts += delta(3602, 5000100));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta);
+ATF_TEST_CASE_BODY(timestamp__subtract_delta)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321),
+ timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555) -
+ delta(30, 1234));
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300),
+ timestamp::from_values(2014, 12, 11, 21, 43, 8, 400) -
+ delta(3602, 5000100));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtract_delta_and_set);
+ATF_TEST_CASE_BODY(timestamp__subtract_delta_and_set)
+{
+ using datetime::delta;
+ using datetime::timestamp;
+
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 40, 5555);
+ ts -= delta(30, 1234);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 21, 43, 10, 4321),
+ ts);
+ }
+ {
+ timestamp ts = timestamp::from_values(2014, 12, 11, 21, 43, 8, 400);
+ ATF_REQUIRE_EQ(timestamp::from_values(2014, 12, 11, 20, 43, 1, 300),
+ ts -= delta(3602, 5000100));
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__subtraction);
+ATF_TEST_CASE_BODY(timestamp__subtraction)
+{
+ const datetime::timestamp ts1 = datetime::timestamp::from_microseconds(
+ 1291970750123456LL);
+ const datetime::timestamp ts2 = datetime::timestamp::from_microseconds(
+ 1291970750123468LL);
+ const datetime::timestamp ts3 = datetime::timestamp::from_microseconds(
+ 1291970850123456LL);
+
+ ATF_REQUIRE_EQ(datetime::delta(0, 0), ts1 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(0, 12), ts2 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(100, 0), ts3 - ts1);
+ ATF_REQUIRE_EQ(datetime::delta(99, 999988), ts3 - ts2);
+
+ ATF_REQUIRE_THROW_RE(
+ std::runtime_error,
+ "Cannot subtract 1291970850123456us from 1291970750123468us "
+ ".*negative datetime::delta.*not supported",
+ ts2 - ts3);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(timestamp__output);
+ATF_TEST_CASE_BODY(timestamp__output)
+{
+ {
+ std::ostringstream str;
+ str << datetime::timestamp::from_microseconds(1291970750123456LL);
+ ATF_REQUIRE_EQ("1291970750123456us", str.str());
+ }
+ {
+ std::ostringstream str;
+ str << datetime::timestamp::from_microseconds(1028309798759812LL);
+ ATF_REQUIRE_EQ("1028309798759812us", str.str());
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, delta__defaults);
+ ATF_ADD_TEST_CASE(tcs, delta__overrides);
+ ATF_ADD_TEST_CASE(tcs, delta__from_microseconds);
+ ATF_ADD_TEST_CASE(tcs, delta__to_microseconds);
+ ATF_ADD_TEST_CASE(tcs, delta__equals);
+ ATF_ADD_TEST_CASE(tcs, delta__differs);
+ ATF_ADD_TEST_CASE(tcs, delta__sorting);
+ ATF_ADD_TEST_CASE(tcs, delta__addition);
+ ATF_ADD_TEST_CASE(tcs, delta__addition_and_set);
+ ATF_ADD_TEST_CASE(tcs, delta__scale);
+ ATF_ADD_TEST_CASE(tcs, delta__scale_and_set);
+ ATF_ADD_TEST_CASE(tcs, delta__output);
+
+ ATF_ADD_TEST_CASE(tcs, timestamp__copy);
+ ATF_ADD_TEST_CASE(tcs, timestamp__from_microseconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__mock);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__real);
+ ATF_ADD_TEST_CASE(tcs, timestamp__now__granularity);
+ ATF_ADD_TEST_CASE(tcs, timestamp__strftime);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_iso8601_in_utc);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_microseconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__to_seconds);
+ ATF_ADD_TEST_CASE(tcs, timestamp__leap_second);
+ ATF_ADD_TEST_CASE(tcs, timestamp__equals);
+ ATF_ADD_TEST_CASE(tcs, timestamp__differs);
+ ATF_ADD_TEST_CASE(tcs, timestamp__sorting);
+ ATF_ADD_TEST_CASE(tcs, timestamp__add_delta);
+ ATF_ADD_TEST_CASE(tcs, timestamp__add_delta_and_set);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtract_delta_and_set);
+ ATF_ADD_TEST_CASE(tcs, timestamp__subtraction);
+ ATF_ADD_TEST_CASE(tcs, timestamp__output);
+}
diff --git a/utils/defs.hpp.in b/utils/defs.hpp.in
new file mode 100644
index 000000000000..62fc50d0e525
--- /dev/null
+++ b/utils/defs.hpp.in
@@ -0,0 +1,57 @@
+// Copyright 2010 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/defs.hpp
+///
+/// Definitions for compiler and system features autodetected at configuration
+/// time.
+
+#if !defined(UTILS_DEFS_HPP)
+#define UTILS_DEFS_HPP
+
+
+/// Attribute to mark a function as non-returning.
+#define UTILS_NORETURN @ATTRIBUTE_NORETURN@
+
+
+/// Attribute to mark a function as pure.
+#define UTILS_PURE @ATTRIBUTE_PURE@
+
+
+/// Attribute to mark an entity as unused.
+#define UTILS_UNUSED @ATTRIBUTE_UNUSED@
+
+
+/// Unconstifies a pointer.
+///
+/// \param type The target type of the conversion.
+/// \param ptr The pointer to be unconstified.
+#define UTILS_UNCONST(type, ptr) ((type*)(unsigned long)(const void*)(ptr))
+
+
+#endif // !defined(UTILS_DEFS_HPP)
diff --git a/utils/env.cpp b/utils/env.cpp
new file mode 100644
index 000000000000..b0d995c0ff31
--- /dev/null
+++ b/utils/env.cpp
@@ -0,0 +1,200 @@
+// Copyright 2010 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/env.hpp"
+
+#if defined(HAVE_CONFIG_H)
+# include "config.h"
+#endif
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <stdexcept>
+
+#include "utils/format/macros.hpp"
+#include "utils/fs/exceptions.hpp"
+#include "utils/fs/path.hpp"
+#include "utils/logging/macros.hpp"
+#include "utils/optional.ipp"
+
+namespace fs = utils::fs;
+
+using utils::none;
+using utils::optional;
+
+
+extern "C" {
+ extern char** environ;
+}
+
+
+/// Gets all environment variables.
+///
+/// \return A mapping of (name, value) pairs describing the environment
+/// variables.
+std::map< std::string, std::string >
+utils::getallenv(void)
+{
+ std::map< std::string, std::string > allenv;
+ for (char** envp = environ; *envp != NULL; envp++) {
+ const std::string oneenv = *envp;
+ const std::string::size_type pos = oneenv.find('=');
+ const std::string name = oneenv.substr(0, pos);
+ const std::string value = oneenv.substr(pos + 1);
+
+ PRE(allenv.find(name) == allenv.end());
+ allenv[name] = value;
+ }
+ return allenv;
+}
+
+
+/// Gets the value of an environment variable.
+///
+/// \param name The name of the environment variable to query.
+///
+/// \return The value of the environment variable if it is defined, or none
+/// otherwise.
+optional< std::string >
+utils::getenv(const std::string& name)
+{
+ const char* value = std::getenv(name.c_str());
+ if (value == NULL) {
+ LD(F("Environment variable '%s' is not defined") % name);
+ return none;
+ } else {
+ LD(F("Environment variable '%s' is '%s'") % name % value);
+ return utils::make_optional(std::string(value));
+ }
+}
+
+
+/// Gets the value of an environment variable with a default fallback.
+///
+/// \param name The name of the environment variable to query.
+/// \param default_value The value to return if the variable is not defined.
+///
+/// \return The value of the environment variable.
+std::string
+utils::getenv_with_default(const std::string& name,
+ const std::string& default_value)
+{
+ const char* value = std::getenv(name.c_str());
+ if (value == NULL) {
+ LD(F("Environment variable '%s' is not defined; using default '%s'") %
+ name % default_value);
+ return default_value;
+ } else {
+ LD(F("Environment variable '%s' is '%s'") % name % value);
+ return value;
+ }
+}
+
+
+/// Gets the value of the HOME environment variable with path validation.
+///
+/// \return The value of the HOME environment variable if it is a valid path;
+/// none if it is not defined or if it contains an invalid path.
+optional< fs::path >
+utils::get_home(void)
+{
+ const optional< std::string > home = utils::getenv("HOME");
+ if (home) {
+ try {
+ return utils::make_optional(fs::path(home.get()));
+ } catch (const fs::error& e) {
+ LW(F("Invalid value '%s' in HOME environment variable: %s") %
+ home.get() % e.what());
+ return none;
+ }
+ } else {
+ return none;
+ }
+}
+
+
+/// Sets the value of an environment variable.
+///
+/// \param name The name of the environment variable to set.
+/// \param val The value to set the environment variable to. May be empty.
+///
+/// \throw std::runtime_error If there is an error setting the environment
+/// variable.
+void
+utils::setenv(const std::string& name, const std::string& val)
+{
+ LD(F("Setting environment variable '%s' to '%s'") % name % val);
+#if defined(HAVE_SETENV)
+ if (::setenv(name.c_str(), val.c_str(), 1) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to set environment variable '%s' to "
+ "'%s': %s") %
+ name % val % std::strerror(original_errno));
+ }
+#elif defined(HAVE_PUTENV)
+ if (::putenv((F("%s=%s") % name % val).c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to set environment variable '%s' to "
+ "'%s': %s") %
+ name % val % std::strerror(original_errno));
+ }
+#else
+# error "Don't know how to set an environment variable."
+#endif
+}
+
+
+/// Unsets an environment variable.
+///
+/// \param name The name of the environment variable to unset.
+///
+/// \throw std::runtime_error If there is an error unsetting the environment
+/// variable.
+void
+utils::unsetenv(const std::string& name)
+{
+ LD(F("Unsetting environment variable '%s'") % name);
+#if defined(HAVE_UNSETENV)
+ if (::unsetenv(name.c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to unset environment variable "
+ "'%s'") %
+ name % std::strerror(original_errno));
+ }
+#elif defined(HAVE_PUTENV)
+ if (::putenv((F("%s=") % name).c_str()) == -1) {
+ const int original_errno = errno;
+ throw std::runtime_error(F("Failed to unset environment variable "
+ "'%s'") %
+ name % std::strerror(original_errno));
+ }
+#else
+# error "Don't know how to unset an environment variable."
+#endif
+}
diff --git a/utils/env.hpp b/utils/env.hpp
new file mode 100644
index 000000000000..2370ee490dc1
--- /dev/null
+++ b/utils/env.hpp
@@ -0,0 +1,58 @@
+// Copyright 2010 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/env.hpp
+/// Querying and manipulation of environment variables.
+///
+/// These utility functions wrap the system functions to manipulate the
+/// environment in a portable way and expose their arguments and return values
+/// in a C++-friendly manner.
+
+#if !defined(UTILS_ENV_HPP)
+#define UTILS_ENV_HPP
+
+#include <map>
+#include <string>
+
+#include "utils/fs/path_fwd.hpp"
+#include "utils/optional_fwd.hpp"
+
+namespace utils {
+
+
+std::map< std::string, std::string > getallenv(void);
+optional< std::string > getenv(const std::string&);
+std::string getenv_with_default(const std::string&, const std::string&);
+optional< utils::fs::path > get_home(void);
+void setenv(const std::string&, const std::string&);
+void unsetenv(const std::string&);
+
+
+} // namespace utils
+
+#endif // !defined(UTILS_ENV_HPP)
diff --git a/utils/env_test.cpp b/utils/env_test.cpp
new file mode 100644
index 000000000000..1b16266443af
--- /dev/null
+++ b/utils/env_test.cpp
@@ -0,0 +1,167 @@
+// Copyright 2010 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/env.hpp"
+
+#include <atf-c++.hpp>
+
+#include "utils/fs/path.hpp"
+#include "utils/optional.ipp"
+
+namespace fs = utils::fs;
+
+using utils::optional;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getallenv);
+ATF_TEST_CASE_BODY(getallenv)
+{
+ utils::unsetenv("test-missing");
+ utils::setenv("test-empty", "");
+ utils::setenv("test-text", "some-value");
+
+ const std::map< std::string, std::string > allenv = utils::getallenv();
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-missing");
+ ATF_REQUIRE(iter == allenv.end());
+ }
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-empty");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE((*iter).second.empty());
+ }
+
+ {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("test-text");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE_EQ("some-value", (*iter).second);
+ }
+
+ if (utils::getenv("PATH")) {
+ const std::map< std::string, std::string >::const_iterator iter =
+ allenv.find("PATH");
+ ATF_REQUIRE(iter != allenv.end());
+ ATF_REQUIRE_EQ(utils::getenv("PATH").get(), (*iter).second);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getenv);
+ATF_TEST_CASE_BODY(getenv)
+{
+ const optional< std::string > path = utils::getenv("PATH");
+ ATF_REQUIRE(path);
+ ATF_REQUIRE(!path.get().empty());
+
+ ATF_REQUIRE(!utils::getenv("__UNDEFINED_VARIABLE__"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(getenv_with_default);
+ATF_TEST_CASE_BODY(getenv_with_default)
+{
+ ATF_REQUIRE("don't use" !=
+ utils::getenv_with_default("PATH", "don't use"));
+
+ ATF_REQUIRE_EQ("foo",
+ utils::getenv_with_default("__UNDEFINED_VARIABLE__", "foo"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__ok);
+ATF_TEST_CASE_BODY(get_home__ok)
+{
+ const fs::path home("/foo/bar");
+ utils::setenv("HOME", home.str());
+ const optional< fs::path > computed = utils::get_home();
+ ATF_REQUIRE(computed);
+ ATF_REQUIRE_EQ(home, computed.get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__missing);
+ATF_TEST_CASE_BODY(get_home__missing)
+{
+ utils::unsetenv("HOME");
+ ATF_REQUIRE(!utils::get_home());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(get_home__invalid);
+ATF_TEST_CASE_BODY(get_home__invalid)
+{
+ utils::setenv("HOME", "");
+ ATF_REQUIRE(!utils::get_home());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(setenv);
+ATF_TEST_CASE_BODY(setenv)
+{
+ ATF_REQUIRE(utils::getenv("PATH"));
+ const std::string oldval = utils::getenv("PATH").get();
+ utils::setenv("PATH", "foo-bar");
+ ATF_REQUIRE(utils::getenv("PATH").get() != oldval);
+ ATF_REQUIRE_EQ("foo-bar", utils::getenv("PATH").get());
+
+ ATF_REQUIRE(!utils::getenv("__UNDEFINED_VARIABLE__"));
+ utils::setenv("__UNDEFINED_VARIABLE__", "foo2-bar2");
+ ATF_REQUIRE_EQ("foo2-bar2", utils::getenv("__UNDEFINED_VARIABLE__").get());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(unsetenv);
+ATF_TEST_CASE_BODY(unsetenv)
+{
+ ATF_REQUIRE(utils::getenv("PATH"));
+ utils::unsetenv("PATH");
+ ATF_REQUIRE(!utils::getenv("PATH"));
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, getallenv);
+
+ ATF_ADD_TEST_CASE(tcs, getenv);
+
+ ATF_ADD_TEST_CASE(tcs, getenv_with_default);
+
+ ATF_ADD_TEST_CASE(tcs, get_home__ok);
+ ATF_ADD_TEST_CASE(tcs, get_home__missing);
+ ATF_ADD_TEST_CASE(tcs, get_home__invalid);
+
+ ATF_ADD_TEST_CASE(tcs, setenv);
+
+ ATF_ADD_TEST_CASE(tcs, unsetenv);
+}
diff --git a/utils/format/Kyuafile b/utils/format/Kyuafile
new file mode 100644
index 000000000000..344ae455422c
--- /dev/null
+++ b/utils/format/Kyuafile
@@ -0,0 +1,7 @@
+syntax(2)
+
+test_suite("kyua")
+
+atf_test_program{name="containers_test"}
+atf_test_program{name="exceptions_test"}
+atf_test_program{name="formatter_test"}
diff --git a/utils/format/Makefile.am.inc b/utils/format/Makefile.am.inc
new file mode 100644
index 000000000000..a37fc4057079
--- /dev/null
+++ b/utils/format/Makefile.am.inc
@@ -0,0 +1,59 @@
+# Copyright 2010 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/format/containers.hpp
+libutils_a_SOURCES += utils/format/containers.ipp
+libutils_a_SOURCES += utils/format/exceptions.cpp
+libutils_a_SOURCES += utils/format/exceptions.hpp
+libutils_a_SOURCES += utils/format/formatter.cpp
+libutils_a_SOURCES += utils/format/formatter.hpp
+libutils_a_SOURCES += utils/format/formatter_fwd.hpp
+libutils_a_SOURCES += utils/format/formatter.ipp
+libutils_a_SOURCES += utils/format/macros.hpp
+
+if WITH_ATF
+tests_utils_formatdir = $(pkgtestsdir)/utils/format
+
+tests_utils_format_DATA = utils/format/Kyuafile
+EXTRA_DIST += $(tests_utils_format_DATA)
+
+tests_utils_format_PROGRAMS = utils/format/containers_test
+utils_format_containers_test_SOURCES = utils/format/containers_test.cpp
+utils_format_containers_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_containers_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_format_PROGRAMS += utils/format/exceptions_test
+utils_format_exceptions_test_SOURCES = utils/format/exceptions_test.cpp
+utils_format_exceptions_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_exceptions_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+
+tests_utils_format_PROGRAMS += utils/format/formatter_test
+utils_format_formatter_test_SOURCES = utils/format/formatter_test.cpp
+utils_format_formatter_test_CXXFLAGS = $(UTILS_CFLAGS) $(ATF_CXX_CFLAGS)
+utils_format_formatter_test_LDADD = $(UTILS_LIBS) $(ATF_CXX_LIBS)
+endif
diff --git a/utils/format/containers.hpp b/utils/format/containers.hpp
new file mode 100644
index 000000000000..7334c250de4e
--- /dev/null
+++ b/utils/format/containers.hpp
@@ -0,0 +1,66 @@
+// 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/format/containers.hpp
+/// Overloads to support formatting various base container types.
+
+#if !defined(UTILS_FORMAT_CONTAINERS_HPP)
+#define UTILS_FORMAT_CONTAINERS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <utility>
+#include <vector>
+
+
+// This is ugly but necessary for C++ name resolution. Unsure if we'd do it
+// differently...
+namespace std {
+
+
+template< typename K, typename V >
+std::ostream& operator<<(std::ostream&, const std::map< K, V >&);
+
+template< typename T1, typename T2 >
+std::ostream& operator<<(std::ostream&, const std::pair< T1, T2 >&);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::shared_ptr< T >);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::set< T >&);
+
+template< typename T >
+std::ostream& operator<<(std::ostream&, const std::vector< T >&);
+
+
+} // namespace std
+
+#endif // !defined(UTILS_FORMAT_CONTAINERS_HPP)
diff --git a/utils/format/containers.ipp b/utils/format/containers.ipp
new file mode 100644
index 000000000000..11d8e2914149
--- /dev/null
+++ b/utils/format/containers.ipp
@@ -0,0 +1,138 @@
+// 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.
+
+#if !defined(UTILS_FORMAT_CONTAINERS_IPP)
+#define UTILS_FORMAT_CONTAINERS_IPP
+
+#include "utils/format/containers.hpp"
+
+#include <ostream>
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename K, typename V >
+std::ostream&
+std::operator<<(std::ostream& output, const std::map< K, V >& object)
+{
+ output << "map(";
+ typename std::map< K, V >::size_type counter = 0;
+ for (typename std::map< K, V >::const_iterator iter = object.begin();
+ iter != object.end(); ++iter, ++counter) {
+ if (counter != 0)
+ output << ", ";
+ output << (*iter).first << "=" << (*iter).second;
+ }
+ output << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T1, typename T2 >
+std::ostream&
+std::operator<<(std::ostream& output, const std::pair< T1, T2 >& object)
+{
+ output << "pair(" << object.first << ", " << object.second << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::shared_ptr< T > object)
+{
+ if (object.get() == NULL) {
+ output << "<NULL>";
+ } else {
+ output << *object;
+ }
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::set< T >& object)
+{
+ output << "set(";
+ typename std::set< T >::size_type counter = 0;
+ for (typename std::set< T >::const_iterator iter = object.begin();
+ iter != object.end(); ++iter, ++counter) {
+ if (counter != 0)
+ output << ", ";
+ output << (*iter);
+ }
+ output << ")";
+ return output;
+}
+
+
+/// Injects the object into a stream.
+///
+/// \param output The stream into which to inject the object.
+/// \param object The object to format.
+///
+/// \return The output stream.
+template< typename T >
+std::ostream&
+std::operator<<(std::ostream& output, const std::vector< T >& object)
+{
+ output << "[";
+ for (typename std::vector< T >::size_type i = 0; i < object.size(); ++i) {
+ if (i != 0)
+ output << ", ";
+ output << object[i];
+ }
+ output << "]";
+ return output;
+}
+
+
+#endif // !defined(UTILS_FORMAT_CONTAINERS_IPP)
diff --git a/utils/format/containers_test.cpp b/utils/format/containers_test.cpp
new file mode 100644
index 000000000000..e1c452da2df6
--- /dev/null
+++ b/utils/format/containers_test.cpp
@@ -0,0 +1,190 @@
+// 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/format/containers.ipp"
+
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <atf-c++.hpp>
+
+
+
+namespace {
+
+
+/// Formats a value and compares it to an expected string.
+///
+/// \tparam T The type of the value to format.
+/// \param expected Expected formatted text.
+/// \param actual The value to format.
+///
+/// \post Fails the test case if the formatted actual value does not match
+/// the provided expected string.
+template< typename T >
+static void
+do_check(const char* expected, const T& actual)
+{
+ std::ostringstream str;
+ str << actual;
+ ATF_REQUIRE_EQ(expected, str.str());
+}
+
+
+} // anonymous namespace
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_map__empty);
+ATF_TEST_CASE_BODY(std_map__empty)
+{
+ do_check("map()", std::map< char, char >());
+ do_check("map()", std::map< int, long >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_map__some);
+ATF_TEST_CASE_BODY(std_map__some)
+{
+ {
+ std::map< char, int > v;
+ v['b'] = 123;
+ v['z'] = 321;
+ do_check("map(b=123, z=321)", v);
+ }
+
+ {
+ std::map< int, std::string > v;
+ v[5] = "first";
+ v[2] = "second";
+ v[8] = "third";
+ do_check("map(2=second, 5=first, 8=third)", v);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_pair);
+ATF_TEST_CASE_BODY(std_pair)
+{
+ do_check("pair(5, b)", std::pair< int, char >(5, 'b'));
+ do_check("pair(foo bar, baz)",
+ std::pair< std::string, std::string >("foo bar", "baz"));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_shared_ptr__null);
+ATF_TEST_CASE_BODY(std_shared_ptr__null)
+{
+ do_check("<NULL>", std::shared_ptr< char >());
+ do_check("<NULL>", std::shared_ptr< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_shared_ptr__not_null);
+ATF_TEST_CASE_BODY(std_shared_ptr__not_null)
+{
+ do_check("f", std::shared_ptr< char >(new char('f')));
+ do_check("8", std::shared_ptr< int >(new int(8)));
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_set__empty);
+ATF_TEST_CASE_BODY(std_set__empty)
+{
+ do_check("set()", std::set< char >());
+ do_check("set()", std::set< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_set__some);
+ATF_TEST_CASE_BODY(std_set__some)
+{
+ {
+ std::set< char > v;
+ v.insert('b');
+ v.insert('z');
+ do_check("set(b, z)", v);
+ }
+
+ {
+ std::set< int > v;
+ v.insert(5);
+ v.insert(2);
+ v.insert(8);
+ do_check("set(2, 5, 8)", v);
+ }
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_vector__empty);
+ATF_TEST_CASE_BODY(std_vector__empty)
+{
+ do_check("[]", std::vector< char >());
+ do_check("[]", std::vector< int >());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(std_vector__some);
+ATF_TEST_CASE_BODY(std_vector__some)
+{
+ {
+ std::vector< char > v;
+ v.push_back('b');
+ v.push_back('z');
+ do_check("[b, z]", v);
+ }
+
+ {
+ std::vector< int > v;
+ v.push_back(5);
+ v.push_back(2);
+ v.push_back(8);
+ do_check("[5, 2, 8]", v);
+ }
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, std_map__empty);
+ ATF_ADD_TEST_CASE(tcs, std_map__some);
+
+ ATF_ADD_TEST_CASE(tcs, std_pair);
+
+ ATF_ADD_TEST_CASE(tcs, std_shared_ptr__null);
+ ATF_ADD_TEST_CASE(tcs, std_shared_ptr__not_null);
+
+ ATF_ADD_TEST_CASE(tcs, std_set__empty);
+ ATF_ADD_TEST_CASE(tcs, std_set__some);
+
+ ATF_ADD_TEST_CASE(tcs, std_vector__empty);
+ ATF_ADD_TEST_CASE(tcs, std_vector__some);
+}
diff --git a/utils/format/exceptions.cpp b/utils/format/exceptions.cpp
new file mode 100644
index 000000000000..299b1d23cd8d
--- /dev/null
+++ b/utils/format/exceptions.cpp
@@ -0,0 +1,110 @@
+// Copyright 2010 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/format/exceptions.hpp"
+
+using utils::format::bad_format_error;
+using utils::format::error;
+using utils::format::extra_args_error;
+
+
+/// Constructs a new error with a plain-text message.
+///
+/// \param message The plain-text error message.
+error::error(const std::string& message) :
+ std::runtime_error(message)
+{
+}
+
+
+/// Destructor for the error.
+error::~error(void) throw()
+{
+}
+
+
+/// Constructs a new bad_format_error.
+///
+/// \param format_ The invalid format string.
+/// \param message Description of the error in the format string.
+bad_format_error::bad_format_error(const std::string& format_,
+ const std::string& message) :
+ error("Invalid formatting string '" + format_ + "': " + message),
+ _format(format_)
+{
+}
+
+
+/// Destructor for the error.
+bad_format_error::~bad_format_error(void) throw()
+{
+}
+
+
+/// \return The format string that caused the error.
+const std::string&
+bad_format_error::format(void) const
+{
+ return _format;
+}
+
+
+/// Constructs a new extra_args_error.
+///
+/// \param format_ The format string.
+/// \param arg_ The first extra argument passed to the format string.
+extra_args_error::extra_args_error(const std::string& format_,
+ const std::string& arg_) :
+ error("Not enough fields in formatting string '" + format_ + "' to place "
+ "argument '" + arg_ + "'"),
+ _format(format_),
+ _arg(arg_)
+{
+}
+
+
+/// Destructor for the error.
+extra_args_error::~extra_args_error(void) throw()
+{
+}
+
+
+/// \return The format string that was passed too many arguments.
+const std::string&
+extra_args_error::format(void) const
+{
+ return _format;
+}
+
+
+/// \return The first argument that caused the error.
+const std::string&
+extra_args_error::arg(void) const
+{
+ return _arg;
+}
diff --git a/utils/format/exceptions.hpp b/utils/format/exceptions.hpp
new file mode 100644
index 000000000000..a28376df9c08
--- /dev/null
+++ b/utils/format/exceptions.hpp
@@ -0,0 +1,84 @@
+// Copyright 2010 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/format/exceptions.hpp
+/// Exception types raised by the format module.
+
+#if !defined(UTILS_FORMAT_EXCEPTIONS_HPP)
+#define UTILS_FORMAT_EXCEPTIONS_HPP
+
+#include <stdexcept>
+#include <string>
+
+namespace utils {
+namespace format {
+
+
+/// Base exception for format errors.
+class error : public std::runtime_error {
+public:
+ explicit error(const std::string&);
+ virtual ~error(void) throw();
+};
+
+
+/// Error denoting a bad format string.
+class bad_format_error : public error {
+ /// The format string that caused the error.
+ std::string _format;
+
+public:
+ explicit bad_format_error(const std::string&, const std::string&);
+ virtual ~bad_format_error(void) throw();
+
+ const std::string& format(void) const;
+};
+
+
+/// Error denoting too many arguments for the format string.
+class extra_args_error : public error {
+ /// The format string that was passed too many arguments.
+ std::string _format;
+
+ /// The first argument that caused the error.
+ std::string _arg;
+
+public:
+ explicit extra_args_error(const std::string&, const std::string&);
+ virtual ~extra_args_error(void) throw();
+
+ const std::string& format(void) const;
+ const std::string& arg(void) const;
+};
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_EXCEPTIONS_HPP)
diff --git a/utils/format/exceptions_test.cpp b/utils/format/exceptions_test.cpp
new file mode 100644
index 000000000000..28d401e57dad
--- /dev/null
+++ b/utils/format/exceptions_test.cpp
@@ -0,0 +1,74 @@
+// Copyright 2010 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/format/exceptions.hpp"
+
+#include <cstring>
+
+#include <atf-c++.hpp>
+
+using utils::format::bad_format_error;
+using utils::format::error;
+using utils::format::extra_args_error;
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(error);
+ATF_TEST_CASE_BODY(error)
+{
+ const error e("Some text");
+ ATF_REQUIRE(std::strcmp("Some text", e.what()) == 0);
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(bad_format_error);
+ATF_TEST_CASE_BODY(bad_format_error)
+{
+ const bad_format_error e("format-string", "the-error");
+ ATF_REQUIRE(std::strcmp("Invalid formatting string 'format-string': "
+ "the-error", e.what()) == 0);
+ ATF_REQUIRE_EQ("format-string", e.format());
+}
+
+
+ATF_TEST_CASE_WITHOUT_HEAD(extra_args_error);
+ATF_TEST_CASE_BODY(extra_args_error)
+{
+ const extra_args_error e("fmt", "extra");
+ ATF_REQUIRE(std::strcmp("Not enough fields in formatting string 'fmt' to "
+ "place argument 'extra'", e.what()) == 0);
+ ATF_REQUIRE_EQ("fmt", e.format());
+ ATF_REQUIRE_EQ("extra", e.arg());
+}
+
+
+ATF_INIT_TEST_CASES(tcs)
+{
+ ATF_ADD_TEST_CASE(tcs, error);
+ ATF_ADD_TEST_CASE(tcs, bad_format_error);
+ ATF_ADD_TEST_CASE(tcs, extra_args_error);
+}
diff --git a/utils/format/formatter.cpp b/utils/format/formatter.cpp
new file mode 100644
index 000000000000..99cfd40f03ab
--- /dev/null
+++ b/utils/format/formatter.cpp
@@ -0,0 +1,293 @@
+// Copyright 2010 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/format/formatter.hpp"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "utils/format/exceptions.hpp"
+#include "utils/sanity.hpp"
+#include "utils/text/exceptions.hpp"
+#include "utils/text/operations.ipp"
+
+namespace format = utils::format;
+namespace text = utils::text;
+
+
+namespace {
+
+
+/// Finds the next placeholder in a string.
+///
+/// \param format The original format string provided by the user; needed for
+/// error reporting purposes only.
+/// \param expansion The string containing the placeholder to look for. Any
+/// '%%' in the string will be skipped, and they must be stripped later by
+/// strip_double_percent().
+/// \param begin The position from which to start looking for the next
+/// placeholder.
+///
+/// \return The position in the string in which the placeholder is located and
+/// the placeholder itself. If there are no placeholders left, this returns
+/// the length of the string and an empty string.
+///
+/// \throw bad_format_error If the input string contains a trailing formatting
+/// character. We cannot detect any other kind of invalid formatter because
+/// we do not implement a full parser for them.
+static std::pair< std::string::size_type, std::string >
+find_next_placeholder(const std::string& format,
+ const std::string& expansion,
+ std::string::size_type begin)
+{
+ begin = expansion.find('%', begin);
+ while (begin != std::string::npos && expansion[begin + 1] == '%')
+ begin = expansion.find('%', begin + 2);
+ if (begin == std::string::npos)
+ return std::make_pair(expansion.length(), "");
+ if (begin == expansion.length() - 1)
+ throw format::bad_format_error(format, "Trailing %");
+
+ std::string::size_type end = begin + 1;
+ while (end < expansion.length() && expansion[end] != 's')
+ end++;
+ const std::string placeholder = expansion.substr(begin, end - begin + 1);
+ if (end == expansion.length() ||
+ placeholder.find('%', 1) != std::string::npos)
+ throw format::bad_format_error(format, "Unterminated placeholder '" +
+ placeholder + "'");
+ return std::make_pair(begin, placeholder);
+}
+
+
+/// Converts a string to an integer.
+///
+/// \param format The format string; for error reporting purposes only.
+/// \param str The string to conver.
+/// \param what The name of the field this integer belongs to; for error
+/// reporting purposes only.
+///
+/// \return An integer representing the input string.
+inline int
+to_int(const std::string& format, const std::string& str, const char* what)
+{
+ try {
+ return text::to_type< int >(str);
+ } catch (const text::value_error& e) {
+ throw format::bad_format_error(format, "Invalid " + std::string(what) +
+ "specifier");
+ }
+}
+
+
+/// Constructs an std::ostringstream based on a formatting placeholder.
+///
+/// \param format The format placeholder; may be empty.
+///
+/// \return A new std::ostringstream that is prepared to format a single
+/// object in the manner specified by the format placeholder.
+///
+/// \throw bad_format_error If the format string is bad. We do minimal
+/// validation on this string though.
+static std::ostringstream*
+new_ostringstream(const std::string& format)
+{
+ std::auto_ptr< std::ostringstream > output(new std::ostringstream());
+
+ if (format.length() <= 2) {
+ // If the format is empty, we create a new stream so that we don't have
+ // to check for NULLs later on. We rarely should hit this condition
+ // (and when we do it's a bug in the caller), so this is not a big deal.
+ //
+ // Otherwise, if the format is a regular '%s', then we don't have to do
+ // any processing for additional formatters. So this is just a "fast
+ // path".
+ } else {
+ std::string partial = format.substr(1, format.length() - 2);
+ if (partial[0] == '0') {
+ output->fill('0');
+ partial.erase(0, 1);
+ }
+ if (!partial.empty()) {
+ const std::string::size_type dot = partial.find('.');
+ if (dot != 0)
+ output->width(to_int(format, partial.substr(0, dot), "width"));
+ if (dot != std::string::npos) {
+ output->setf(std::ios::fixed, std::ios::floatfield);
+ output->precision(to_int(format, partial.substr(dot + 1),
+ "precision"));
+ }
+ }
+ }
+
+ return output.release();
+}
+
+
+/// Replaces '%%' by '%' in a given string range.
+///
+/// \param in The input string to be rewritten.
+/// \param begin The position at which to start the replacement.
+/// \param end The position at which to end the replacement.
+///
+/// \return The modified string and the amount of characters removed.
+static std::pair< std::string, int >
+strip_double_percent(const std::string& in, const std::string::size_type begin,
+ std::string::size_type end)
+{
+ std::string part = in.substr(begin, end - begin);
+
+ int removed = 0;
+ std::string::size_type pos = part.find("%%");
+ while (pos != std::string::npos) {
+ part.erase(pos, 1);
+ ++removed;
+ pos = part.find("%%", pos + 1);
+ }
+
+ return std::make_pair(in.substr(0, begin) + part + in.substr(end), removed);
+}
+
+
+} // anonymous namespace
+
+
+/// Performs internal initialization of the formatter.
+///
+/// This is separate from the constructor just because it is shared by different
+/// overloaded constructors.
+void
+format::formatter::init(void)
+{
+ const std::pair< std::string::size_type, std::string > placeholder =
+ find_next_placeholder(_format, _expansion, _last_pos);
+ const std::pair< std::string, int > no_percents =
+ strip_double_percent(_expansion, _last_pos, placeholder.first);
+
+ _oss = new_ostringstream(placeholder.second);
+
+ _expansion = no_percents.first;
+ _placeholder_pos = placeholder.first - no_percents.second;
+ _placeholder = placeholder.second;
+}
+
+
+/// Constructs a new formatter object (internal).
+///
+/// \param format The format string.
+/// \param expansion The format string with any replacements performed so far.
+/// \param last_pos The position from which to start looking for formatting
+/// placeholders. This must be maintained in case one of the replacements
+/// introduced a new placeholder, which must be ignored. Think, for
+/// example, replacing a "%s" string with "foo %s".
+format::formatter::formatter(const std::string& format,
+ const std::string& expansion,
+ const std::string::size_type last_pos) :
+ _format(format),
+ _expansion(expansion),
+ _last_pos(last_pos),
+ _oss(NULL)
+{
+ init();
+}
+
+
+/// Constructs a new formatter object.
+///
+/// \param format The format string. The formatters in the string are not
+/// validated during construction, but will cause errors when used later if
+/// they are invalid.
+format::formatter::formatter(const std::string& format) :
+ _format(format),
+ _expansion(format),
+ _last_pos(0),
+ _oss(NULL)
+{
+ init();
+}
+
+
+format::formatter::~formatter(void)
+{
+ delete _oss;
+}
+
+
+/// Returns the formatted string.
+///
+/// \return A string representation of the formatted string.
+const std::string&
+format::formatter::str(void) const
+{
+ return _expansion;
+}
+
+
+/// Automatic conversion of formatter objects to strings.
+///
+/// This is provided to allow painless injection of formatter objects into
+/// streams, without having to manually call the str() method.
+format::formatter::operator const std::string&(void) const
+{
+ return _expansion;
+}
+
+
+/// Specialization of operator% for booleans.
+///
+/// \param value The boolean to inject into the format string.
+///
+/// \return A new formatter that has one less format placeholder.
+format::formatter
+format::formatter::operator%(const bool& value) const
+{
+ (*_oss) << (value ? "true" : "false");
+ return replace(_oss->str());
+}
+
+
+/// Replaces the first formatting placeholder with a value.
+///
+/// \param arg The replacement string.
+///
+/// \return A new formatter in which the first formatting placeholder has been
+/// replaced by arg and is ready to replace the next item.
+///
+/// \throw utils::format::extra_args_error If there are no more formatting
+/// placeholders in the input string, or if the placeholder is invalid.
+format::formatter
+format::formatter::replace(const std::string& arg) const
+{
+ if (_placeholder_pos == _expansion.length())
+ throw format::extra_args_error(_format, arg);
+
+ const std::string expansion = _expansion.substr(0, _placeholder_pos)
+ + arg + _expansion.substr(_placeholder_pos + _placeholder.length());
+ return formatter(_format, expansion, _placeholder_pos + arg.length());
+}
diff --git a/utils/format/formatter.hpp b/utils/format/formatter.hpp
new file mode 100644
index 000000000000..8c6188745a2e
--- /dev/null
+++ b/utils/format/formatter.hpp
@@ -0,0 +1,123 @@
+// Copyright 2010 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/format/formatter.hpp
+/// Provides the definition of the utils::format::formatter class.
+///
+/// The utils::format::formatter class is a poor man's replacement for the
+/// Boost.Format library, as it is much simpler and has less dependencies.
+///
+/// Be aware that the formatting supported by this module is NOT compatible
+/// with printf(3) nor with Boost.Format. The general syntax for a
+/// placeholder in a formatting string is:
+///
+/// %[0][width][.precision]s
+///
+/// In particular, note that the only valid formatting specifier is %s: the
+/// library deduces what to print based on the type of the variable passed
+/// in, not based on what the format string says. Also, note that the only
+/// valid padding character is 0.
+
+#if !defined(UTILS_FORMAT_FORMATTER_HPP)
+#define UTILS_FORMAT_FORMATTER_HPP
+
+#include "utils/format/formatter_fwd.hpp"
+
+#include <sstream>
+#include <string>
+
+namespace utils {
+namespace format {
+
+
+/// Mechanism to format strings similar to printf.
+///
+/// A formatter always maintains the original format string but also holds a
+/// partial expansion. The partial expansion is immutable in the context of a
+/// formatter instance, but calls to operator% return new formatter objects with
+/// one less formatting placeholder.
+///
+/// In general, one can format a string in the following manner:
+///
+/// \code
+/// const std::string s = (formatter("%s %s") % "foo" % 5).str();
+/// \endcode
+///
+/// which, following the explanation above, would correspond to:
+///
+/// \code
+/// const formatter f1("%s %s");
+/// const formatter f2 = f1 % "foo";
+/// const formatter f3 = f2 % 5;
+/// const std::string s = f3.str();
+/// \endcode
+class formatter {
+ /// The original format string provided by the user.
+ std::string _format;
+
+ /// The current "expansion" of the format string.
+ ///
+ /// This field gets updated on every call to operator%() to have one less
+ /// formatting placeholder.
+ std::string _expansion;
+
+ /// The position of _expansion from which to scan for placeholders.
+ std::string::size_type _last_pos;
+
+ /// The position of the first placeholder in the current expansion.
+ std::string::size_type _placeholder_pos;
+
+ /// The first placeholder in the current expansion.
+ std::string _placeholder;
+
+ /// Stream used to format any possible argument supplied by operator%().
+ std::ostringstream* _oss;
+
+ formatter replace(const std::string&) const;
+
+ void init(void);
+ formatter(const std::string&, const std::string&,
+ const std::string::size_type);
+
+public:
+ explicit formatter(const std::string&);
+ ~formatter(void);
+
+ const std::string& str(void) const;
+ operator const std::string&(void) const;
+
+ template< typename Type > formatter operator%(const Type&) const;
+ formatter operator%(const bool&) const;
+};
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_FORMATTER_HPP)
diff --git a/utils/format/formatter.ipp b/utils/format/formatter.ipp
new file mode 100644
index 000000000000..6fad024b704f
--- /dev/null
+++ b/utils/format/formatter.ipp
@@ -0,0 +1,76 @@
+// Copyright 2010 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_FORMAT_FORMATTER_IPP)
+#define UTILS_FORMAT_FORMATTER_IPP
+
+#include <ostream>
+
+#include "utils/format/formatter.hpp"
+
+namespace utils {
+namespace format {
+
+
+/// Replaces the first format placeholder in a formatter.
+///
+/// Constructs a new formatter object that has one less formatting placeholder,
+/// as this has been replaced by the provided argument. Calling this operator
+/// N times, where N is the number of formatting placeholders, effectively
+/// formats the string.
+///
+/// \param arg The argument to use as replacement for the format placeholder.
+///
+/// \return A new formatter that has one less format placeholder.
+template< typename Type >
+inline formatter
+formatter::operator%(const Type& arg) const
+{
+ (*_oss) << arg;
+ return replace(_oss->str());
+}
+
+
+/// Inserts a formatter string into a stream.
+///
+/// \param os The output stream.
+/// \param f The formatter to process and inject into the stream.
+///
+/// \return The output stream os.
+inline std::ostream&
+operator<<(std::ostream& os, const formatter& f)
+{
+ return (os << f.str());
+}
+
+
+} // namespace format
+} // namespace utils
+
+
+#endif // !defined(UTILS_FORMAT_FORMATTER_IPP)
diff --git a/utils/format/formatter_fwd.hpp b/utils/format/formatter_fwd.hpp
new file mode 100644
index 000000000000..72c9e5ebf196
--- /dev/null
+++ b/utils/format/formatter_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 contribut