Path: blob/main/contrib/kyua/utils/cmdline/parser.cpp
48178 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())90long_option.has_arg = required_argument;91else92long_option.has_arg = no_argument;9394int id = -1;95if (option->has_short_name()) {96data.short_options += option->short_name();97if (option->needs_arg())98data.short_options += ':';99id = option->short_name();100} else {101id = cur_id++;102}103long_option.flag = NULL;104long_option.val = id;105data.ids[id] = option;106}107108::option& last_long_option = data.long_options[options.size()];109last_long_option.name = NULL;110last_long_option.has_arg = 0;111last_long_option.flag = NULL;112last_long_option.val = 0;113}114115116/// Converts an argc/argv pair to an args_vector.117///118/// \param argc The value of argc as passed to main().119/// \param argv The value of argv as passed to main().120///121/// \return An args_vector with the same contents of argc/argv.122static cmdline::args_vector123argv_to_vector(int argc, const char* const argv[])124{125PRE(argv[argc] == NULL);126cmdline::args_vector args;127for (int i = 0; i < argc; i++)128args.push_back(argv[i]);129return args;130}131132133/// Creates a mutable version of argv.134///135/// \param argc The value of argc as passed to main().136/// \param argv The value of argv as passed to main().137///138/// \return A new argv, with mutable buffers. The returned array must be139/// released using the free_mutable_argv() function.140static char**141make_mutable_argv(const int argc, const char* const* argv)142{143char** mutable_argv = new char*[argc + 1];144for (int i = 0; i < argc; i++)145mutable_argv[i] = ::strdup(argv[i]);146mutable_argv[argc] = NULL;147return mutable_argv;148}149150151/// Releases the object returned by make_mutable_argv().152///153/// \param argv A dynamically-allocated argv as returned by make_mutable_argv().154static void155free_mutable_argv(char** argv)156{157char** ptr = argv;158while (*ptr != NULL) {159::free(*ptr);160ptr++;161}162delete [] argv;163}164165166/// Finds the name of the offending option after a getopt_long error.167///168/// \param data Our internal getopt data used for the call to getopt_long.169/// \param getopt_optopt The value of getopt(3)'s optopt after the error.170/// \param argv The argv passed to getopt_long.171/// \param getopt_optind The value of getopt(3)'s optind after the error.172///173/// \return A fully-specified option name (i.e. an option name prefixed by174/// either '-' or '--').175static std::string176find_option_name(const getopt_data& data, const int getopt_optopt,177char** argv, const int getopt_optind)178{179PRE(getopt_optopt >= 0);180181if (getopt_optopt == 0) {182return argv[getopt_optind - 1];183} else if (getopt_optopt < std::numeric_limits< char >::max()) {184INV(getopt_optopt > 0);185const char ch = static_cast< char >(getopt_optopt);186return F("-%s") % ch;187} else {188for (const ::option* opt = &data.long_options[0]; opt->name != NULL;189opt++) {190if (opt->val == getopt_optopt)191return F("--%s") % opt->name;192}193UNREACHABLE;194}195}196197198} // anonymous namespace199200201/// Constructs a new parsed_cmdline.202///203/// Use the cmdline::parse() free functions to construct.204///205/// \param option_values_ A mapping of long option names to values. This206/// contains a representation of the options provided by the user. Note207/// that each value is actually a collection values: a user may specify a208/// flag multiple times, and depending on the case we want to honor one or209/// the other. For those options that support no argument, the argument210/// value is the empty string.211/// \param arguments_ The list of non-option arguments in the command line.212cmdline::parsed_cmdline::parsed_cmdline(213const std::map< std::string, std::vector< std::string > >& option_values_,214const cmdline::args_vector& arguments_) :215_option_values(option_values_),216_arguments(arguments_)217{218}219220221/// Checks if the given option has been given in the command line.222///223/// \param name The long option name to check for presence.224///225/// \return True if the option has been given; false otherwise.226bool227cmdline::parsed_cmdline::has_option(const std::string& name) const228{229return _option_values.find(name) != _option_values.end();230}231232233/// Gets the raw value of an option.234///235/// The raw value of an option is a collection of strings that represent all the236/// values passed to the option on the command line. It is up to the consumer237/// if he wants to honor only the last value or all of them.238///239/// The caller has to use get_option() instead; this function is internal.240///241/// \pre has_option(name) must be true.242///243/// \param name The option to query.244///245/// \return The value of the option as a plain string.246const std::vector< std::string >&247cmdline::parsed_cmdline::get_option_raw(const std::string& name) const248{249std::map< std::string, std::vector< std::string > >::const_iterator iter =250_option_values.find(name);251INV_MSG(iter != _option_values.end(), F("Undefined option --%s") % name);252return (*iter).second;253}254255256/// Returns the non-option arguments found in the command line.257///258/// \return The arguments, if any.259const cmdline::args_vector&260cmdline::parsed_cmdline::arguments(void) const261{262return _arguments;263}264265266/// Parses a command line.267///268/// \param args The command line to parse, broken down by words.269/// \param options The description of the supported options.270///271/// \return The parsed command line.272///273/// \pre args[0] must be the program or command name.274///275/// \throw cmdline::error See the description of parse(argc, argv, options) for276/// more details on the raised errors.277cmdline::parsed_cmdline278cmdline::parse(const cmdline::args_vector& args,279const cmdline::options_vector& options)280{281PRE_MSG(args.size() >= 1, "No progname or command name found");282283utils::auto_array< const char* > argv(new const char*[args.size() + 1]);284for (args_vector::size_type i = 0; i < args.size(); i++)285argv[i] = args[i].c_str();286argv[args.size()] = NULL;287return parse(static_cast< int >(args.size()), argv.get(), options);288}289290291/// Parses a command line.292///293/// \param argc The number of arguments in argv, without counting the294/// terminating NULL.295/// \param argv The arguments to parse. The array is NULL-terminated.296/// \param options The description of the supported options.297///298/// \return The parsed command line.299///300/// \pre args[0] must be the program or command name.301///302/// \throw cmdline::missing_option_argument_error If the user specified an303/// option that requires an argument, but no argument was provided.304/// \throw cmdline::unknown_option_error If the user specified an unknown305/// option (i.e. an option not defined in options).306/// \throw cmdline::option_argument_value_error If the user passed an invalid307/// argument to a supported option.308cmdline::parsed_cmdline309cmdline::parse(const int argc, const char* const* argv,310const cmdline::options_vector& options)311{312PRE_MSG(argc >= 1, "No progname or command name found");313314getopt_data data;315options_to_getopt_data(options, data);316317std::map< std::string, std::vector< std::string > > option_values;318319for (cmdline::options_vector::const_iterator iter = options.begin();320iter != options.end(); iter++) {321const cmdline::base_option* option = *iter;322if (option->needs_arg() && option->has_default_value())323option_values[option->long_name()].push_back(324option->default_value());325}326327args_vector args;328329int mutable_argc = argc;330char** mutable_argv = make_mutable_argv(argc, argv);331const int old_opterr = ::opterr;332try {333int ch;334335::opterr = 0;336337while ((ch = ::getopt_long(mutable_argc, mutable_argv,338("+:" + data.short_options).c_str(),339data.long_options.get(), NULL)) != -1) {340if (ch == ':' ) {341const std::string name = find_option_name(342data, ::optopt, mutable_argv, ::optind);343throw cmdline::missing_option_argument_error(name);344} else if (ch == '?') {345const std::string name = find_option_name(346data, ::optopt, mutable_argv, ::optind);347throw cmdline::unknown_option_error(name);348}349350const std::map< int, const cmdline::base_option* >::const_iterator351id = data.ids.find(ch);352INV(id != data.ids.end());353const cmdline::base_option* option = (*id).second;354355if (option->needs_arg()) {356if (::optarg != NULL) {357option->validate(::optarg);358option_values[option->long_name()].push_back(::optarg);359} else360INV(option->has_default_value());361} else {362option_values[option->long_name()].push_back("");363}364}365args = argv_to_vector(mutable_argc - optind, mutable_argv + optind);366367::opterr = old_opterr;368::optind = GETOPT_OPTIND_RESET_VALUE;369#if defined(HAVE_GETOPT_WITH_OPTRESET)370::optreset = 1;371#endif372} catch (...) {373free_mutable_argv(mutable_argv);374::opterr = old_opterr;375::optind = GETOPT_OPTIND_RESET_VALUE;376#if defined(HAVE_GETOPT_WITH_OPTRESET)377::optreset = 1;378#endif379throw;380}381free_mutable_argv(mutable_argv);382383return parsed_cmdline(option_values, args);384}385386387