diff options
Diffstat (limited to 'utils/cmdline')
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); +} |