aboutsummaryrefslogtreecommitdiffstats
path: root/utils/cmdline
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/cmdline
downloadsrc-vendor/kyua.tar.gz
src-vendor/kyua.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/cmdline')
-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
32 files changed, 5662 insertions, 0 deletions
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);
+}