diff options
Diffstat (limited to 'utils/cmdline/parser.cpp')
-rw-r--r-- | utils/cmdline/parser.cpp | 385 |
1 files changed, 385 insertions, 0 deletions
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); +} |