Path: blob/main/contrib/kyua/utils/cmdline/parser.cpp
102292 views
// Copyright 2010 The Kyua Authors.1// All rights reserved.2//3// Redistribution and use in source and binary forms, with or without4// modification, are permitted provided that the following conditions are5// met:6//7// * Redistributions of source code must retain the above copyright8// notice, this list of conditions and the following disclaimer.9// * Redistributions in binary form must reproduce the above copyright10// notice, this list of conditions and the following disclaimer in the11// documentation and/or other materials provided with the distribution.12// * Neither the name of Google Inc. nor the names of its contributors13// may be used to endorse or promote products derived from this software14// without specific prior written permission.15//16// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS17// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT18// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR19// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT20// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,21// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT22// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,23// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY24// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT25// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE26// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.2728#include "utils/cmdline/parser.hpp"2930#if defined(HAVE_CONFIG_H)31# include "config.h"32#endif3334extern "C" {35#include <getopt.h>36}3738#include <cstdlib>39#include <cstring>40#include <limits>4142#include "utils/auto_array.ipp"43#include "utils/cmdline/exceptions.hpp"44#include "utils/cmdline/options.hpp"45#include "utils/format/macros.hpp"46#include "utils/noncopyable.hpp"47#include "utils/sanity.hpp"4849namespace cmdline = utils::cmdline;5051namespace {525354/// Auxiliary data to call getopt_long(3).55struct getopt_data : utils::noncopyable {56/// Plain-text representation of the short options.57///58/// This string follows the syntax expected by getopt_long(3) in the59/// argument to describe the short options.60std::string short_options;6162/// Representation of the long options as expected by getopt_long(3).63utils::auto_array< ::option > long_options;6465/// Auto-generated identifiers to be able to parse long options.66std::map< int, const cmdline::base_option* > ids;67};686970/// Converts a cmdline::options_vector to a getopt_data.71///72/// \param options The high-level definition of the options.73/// \param [out] data An object containing the necessary data to call74/// getopt_long(3) and interpret its results.75static void76options_to_getopt_data(const cmdline::options_vector& options,77getopt_data& data)78{79data.short_options.clear();80data.long_options.reset(new ::option[options.size() + 1]);8182int cur_id = 512;8384for (cmdline::options_vector::size_type i = 0; i < options.size(); i++) {85const cmdline::base_option* option = options[i];86::option& long_option = data.long_options[i];8788long_option.name = option->long_name().c_str();89if (option->needs_arg())90if (option->arg_is_optional())91long_option.has_arg = optional_argument;92else93long_option.has_arg = required_argument;94else95long_option.has_arg = no_argument;9697int id = -1;98if (option->has_short_name()) {99data.short_options += option->short_name();100if (option->needs_arg())101data.short_options += option->arg_is_optional() ? "::" : ":";102id = option->short_name();103} else {104id = cur_id++;105}106long_option.flag = NULL;107long_option.val = id;108data.ids[id] = option;109}110111::option& last_long_option = data.long_options[options.size()];112last_long_option.name = NULL;113last_long_option.has_arg = 0;114last_long_option.flag = NULL;115last_long_option.val = 0;116}117118119/// Converts an argc/argv pair to an args_vector.120///121/// \param argc The value of argc as passed to main().122/// \param argv The value of argv as passed to main().123///124/// \return An args_vector with the same contents of argc/argv.125static cmdline::args_vector126argv_to_vector(int argc, const char* const argv[])127{128PRE(argv[argc] == NULL);129cmdline::args_vector args;130for (int i = 0; i < argc; i++)131args.push_back(argv[i]);132return args;133}134135136/// Creates a mutable version of argv.137///138/// \param argc The value of argc as passed to main().139/// \param argv The value of argv as passed to main().140///141/// \return A new argv, with mutable buffers. The returned array must be142/// released using the free_mutable_argv() function.143static char**144make_mutable_argv(const int argc, const char* const* argv)145{146char** mutable_argv = new char*[argc + 1];147for (int i = 0; i < argc; i++)148mutable_argv[i] = ::strdup(argv[i]);149mutable_argv[argc] = NULL;150return mutable_argv;151}152153154/// Releases the object returned by make_mutable_argv().155///156/// \param argv A dynamically-allocated argv as returned by make_mutable_argv().157static void158free_mutable_argv(char** argv)159{160char** ptr = argv;161while (*ptr != NULL) {162::free(*ptr);163ptr++;164}165delete [] argv;166}167168169/// Finds the name of the offending option after a getopt_long error.170///171/// \param data Our internal getopt data used for the call to getopt_long.172/// \param getopt_optopt The value of getopt(3)'s optopt after the error.173/// \param argv The argv passed to getopt_long.174/// \param getopt_optind The value of getopt(3)'s optind after the error.175///176/// \return A fully-specified option name (i.e. an option name prefixed by177/// either '-' or '--').178static std::string179find_option_name(const getopt_data& data, const int getopt_optopt,180char** argv, const int getopt_optind)181{182PRE(getopt_optopt >= 0);183184if (getopt_optopt == 0) {185return argv[getopt_optind - 1];186} else if (getopt_optopt < std::numeric_limits< char >::max()) {187INV(getopt_optopt > 0);188const char ch = static_cast< char >(getopt_optopt);189return F("-%s") % ch;190} else {191for (const ::option* opt = &data.long_options[0]; opt->name != NULL;192opt++) {193if (opt->val == getopt_optopt)194return F("--%s") % opt->name;195}196UNREACHABLE;197}198}199200201} // anonymous namespace202203204/// Constructs a new parsed_cmdline.205///206/// Use the cmdline::parse() free functions to construct.207///208/// \param option_values_ A mapping of long option names to values. This209/// contains a representation of the options provided by the user. Note210/// that each value is actually a collection values: a user may specify a211/// flag multiple times, and depending on the case we want to honor one or212/// the other. For those options that support no argument, the argument213/// value is the empty string.214/// \param arguments_ The list of non-option arguments in the command line.215cmdline::parsed_cmdline::parsed_cmdline(216const std::map< std::string, std::vector< std::string > >& option_values_,217const cmdline::args_vector& arguments_) :218_option_values(option_values_),219_arguments(arguments_)220{221}222223224/// Checks if the given option has been given in the command line.225///226/// \param name The long option name to check for presence.227///228/// \return True if the option has been given; false otherwise.229bool230cmdline::parsed_cmdline::has_option(const std::string& name) const231{232return _option_values.find(name) != _option_values.end();233}234235236/// Gets the raw value of an option.237///238/// The raw value of an option is a collection of strings that represent all the239/// values passed to the option on the command line. It is up to the consumer240/// if he wants to honor only the last value or all of them.241///242/// The caller has to use get_option() instead; this function is internal.243///244/// \pre has_option(name) must be true.245///246/// \param name The option to query.247///248/// \return The value of the option as a plain string.249const std::vector< std::string >&250cmdline::parsed_cmdline::get_option_raw(const std::string& name) const251{252std::map< std::string, std::vector< std::string > >::const_iterator iter =253_option_values.find(name);254INV_MSG(iter != _option_values.end(), F("Undefined option --%s") % name);255return (*iter).second;256}257258259/// Returns the non-option arguments found in the command line.260///261/// \return The arguments, if any.262const cmdline::args_vector&263cmdline::parsed_cmdline::arguments(void) const264{265return _arguments;266}267268269/// Parses a command line.270///271/// \param args The command line to parse, broken down by words.272/// \param options The description of the supported options.273///274/// \return The parsed command line.275///276/// \pre args[0] must be the program or command name.277///278/// \throw cmdline::error See the description of parse(argc, argv, options) for279/// more details on the raised errors.280cmdline::parsed_cmdline281cmdline::parse(const cmdline::args_vector& args,282const cmdline::options_vector& options)283{284PRE_MSG(args.size() >= 1, "No progname or command name found");285286utils::auto_array< const char* > argv(new const char*[args.size() + 1]);287for (args_vector::size_type i = 0; i < args.size(); i++)288argv[i] = args[i].c_str();289argv[args.size()] = NULL;290return parse(static_cast< int >(args.size()), argv.get(), options);291}292293294/// Parses a command line.295///296/// \param argc The number of arguments in argv, without counting the297/// terminating NULL.298/// \param argv The arguments to parse. The array is NULL-terminated.299/// \param options The description of the supported options.300///301/// \return The parsed command line.302///303/// \pre args[0] must be the program or command name.304///305/// \throw cmdline::missing_option_argument_error If the user specified an306/// option that requires an argument, but no argument was provided.307/// \throw cmdline::unknown_option_error If the user specified an unknown308/// option (i.e. an option not defined in options).309/// \throw cmdline::option_argument_value_error If the user passed an invalid310/// argument to a supported option.311cmdline::parsed_cmdline312cmdline::parse(const int argc, const char* const* argv,313const cmdline::options_vector& options)314{315PRE_MSG(argc >= 1, "No progname or command name found");316317getopt_data data;318options_to_getopt_data(options, data);319320std::map< std::string, std::vector< std::string > > option_values;321322for (cmdline::options_vector::const_iterator iter = options.begin();323iter != options.end(); iter++) {324const cmdline::base_option* option = *iter;325if (option->needs_arg() && option->has_default_value() &&326!option->arg_is_optional()) {327option_values[option->long_name()].push_back(328option->default_value());329}330}331332args_vector args;333334int mutable_argc = argc;335char** mutable_argv = make_mutable_argv(argc, argv);336const int old_opterr = ::opterr;337try {338int ch;339340::opterr = 0;341342while ((ch = ::getopt_long(mutable_argc, mutable_argv,343("+:" + data.short_options).c_str(),344data.long_options.get(), NULL)) != -1) {345if (ch == ':' ) {346const std::string name = find_option_name(347data, ::optopt, mutable_argv, ::optind);348throw cmdline::missing_option_argument_error(name);349} else if (ch == '?') {350const std::string name = find_option_name(351data, ::optopt, mutable_argv, ::optind);352throw cmdline::unknown_option_error(name);353}354355const std::map< int, const cmdline::base_option* >::const_iterator356id = data.ids.find(ch);357INV(id != data.ids.end());358const cmdline::base_option* option = (*id).second;359360if (option->needs_arg()) {361if (::optarg != NULL) {362option->validate(::optarg);363option_values[option->long_name()].push_back(::optarg);364} else {365if (option->arg_is_optional())366option_values[option->long_name()].push_back(367option->default_value());368else369INV(option->has_default_value());370}371} else {372option_values[option->long_name()].push_back("");373}374}375args = argv_to_vector(mutable_argc - optind, mutable_argv + optind);376377::opterr = old_opterr;378::optind = GETOPT_OPTIND_RESET_VALUE;379#if defined(HAVE_GETOPT_WITH_OPTRESET)380::optreset = 1;381#endif382} catch (...) {383free_mutable_argv(mutable_argv);384::opterr = old_opterr;385::optind = GETOPT_OPTIND_RESET_VALUE;386#if defined(HAVE_GETOPT_WITH_OPTRESET)387::optreset = 1;388#endif389throw;390}391free_mutable_argv(mutable_argv);392393return parsed_cmdline(option_values, args);394}395396397