/* Copyright (C) 2009 Red Hat, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see . */ #ifdef HAVE_CONFIG_H #include #endif #include "common.h" #include #include #include "cmd_line_parser.h" #include "utils.h" #include "debug.h" #define DISABLE_ABBREVIATE CmdLineParser::Option::Option(int in_id, const std::string& in_name, char in_short_name, OptionType in_type, const std::string& in_help, const std::string& in_arg_name) : id (in_id) , name (in_name) , arg_name (in_arg_name) , type (in_type) , short_name (in_short_name) , help (in_help) , optional (true) , is_set (false) , separator (0) { } CmdLineParser::CmdLineParser(std::string description, bool allow_positional_args) : _description (description) , _short_options ("+") , _argc (0) , _argv (NULL) , _multi_args (NULL) , _multi_next (NULL) , _multi_separator (0) , _positional_args (allow_positional_args) , _done (false) { //Enables multiple instantiations. One at a time, not thread-safe. optind = 1; opterr = 1; optopt = 0; optarg = 0; } CmdLineParser::~CmdLineParser() { Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { delete *iter; } delete[] _multi_args; } void CmdLineParser::add_private(int id, const std::string& name, char short_name, OptionType type, const std::string& help, const std::string& arg_name) { if (_argv) { THROW("unexpected"); } if (find(id)) { THROW("exist"); } if (name.size() == 0) { THROW("invalid name"); } if (find(name)) { THROW("name exist"); } if (short_name != 0) { if (!isalnum(short_name) || short_name == 'W') { THROW("invalid short name"); } if (find(short_name)) { THROW("short name exist"); } } if (help.size() == 0) { THROW("invalid help string"); } if (help.find_first_of('\t') != std::string::npos) { THROW("tab is not allow in help string"); } _options.push_back(new Option(id, name, short_name, type, help, arg_name)); } void CmdLineParser::add(int id, const std::string& name, const std::string& help, char short_name) { if (id < OPTION_FIRST_AVAILABLE) { THROW("invalid id"); } add_private(id, name, short_name, NO_ARGUMENT, help, ""); } void CmdLineParser::add(int id, const std::string& name, const std::string& help, const std::string& arg_name, bool required_arg, char short_name) { if (id < OPTION_FIRST_AVAILABLE) { THROW("invalid id"); } if (arg_name.size() == 0) { THROW("invalid arg name"); } add_private(id, name, short_name, required_arg ? REQUIRED_ARGUMENT : OPTIONAL_ARGUMENT, help, arg_name); } void CmdLineParser::set_multi(int id, char separator) { if (_argv) { THROW("unexpected"); } if (!ispunct(separator)) { THROW("invalid separator"); } Option* opt = find(id); if (!opt) { THROW("not found"); } if (opt->type == NO_ARGUMENT) { THROW("can't set multi for option without argument"); } opt->separator = separator; } void CmdLineParser::set_required(int id) { if (_argv) { THROW("unexpected"); } Option* opt = find(id); if (!opt) { THROW("not found"); } opt->optional = false; } CmdLineParser::Option* CmdLineParser::find(int id) { Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { if ((*iter)->id == id) { return *iter; } } return NULL; } bool CmdLineParser::is_set(int id) { Option *opt = find(id); if (!opt) { THROW("not found"); } return opt->is_set; } CmdLineParser::Option* CmdLineParser::find(const std::string& name) { Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { if ((*iter)->name == name) { return *iter; } } return NULL; } CmdLineParser::Option* CmdLineParser::find(char short_name) { if (short_name == 0) { return NULL; } Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { if ((*iter)->short_name == short_name) { return *iter; } } return NULL; } CmdLineParser::Option* CmdLineParser::find_missing_opt() { Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { CmdLineParser::Option* opt = *iter; if (!opt->optional && !opt->is_set) { return opt; } } return NULL; } void CmdLineParser::build() { Options::iterator iter = _options.begin(); _long_options.resize(_options.size() + 1); for (int i = 0; iter != _options.end(); ++iter, i++) { CmdLineParser::Option* opt = *iter; struct option& long_option = _long_options[i]; long_option.name = opt->name.c_str(); switch (opt->type) { case NO_ARGUMENT: long_option.has_arg = no_argument; break; case OPTIONAL_ARGUMENT: long_option.has_arg = optional_argument; break; case REQUIRED_ARGUMENT: long_option.has_arg = required_argument; break; } long_option.flag = &long_option.val; long_option.val = opt->id; if (opt->short_name != 0) { _short_options += opt->short_name; switch (opt->type) { case OPTIONAL_ARGUMENT: _short_options += "::"; break; case REQUIRED_ARGUMENT: _short_options += ":"; break; case NO_ARGUMENT: break; } } } struct option& long_option = _long_options[_long_options.size() - 1]; long_option.flag = 0; long_option.has_arg = 0; long_option.name = NULL; long_option.val = 0; } void CmdLineParser::begin(int argc, char** argv) { if (_argv) { THROW("unexpected"); } if (!argv || argc < 1) { THROW("invalid args"); } add_private(CmdLineParser::OPTION_HELP, "help", 0, NO_ARGUMENT, "show command help", ""); opterr = 0; _argv = argv; _argc = argc; build(); } char* CmdLineParser::start_multi(char *optarg, char separator) { if (!optarg) { return NULL; } _multi_args = new char[strlen(optarg) + 1]; _multi_separator = separator; strcpy(_multi_args, optarg); if ((_multi_next = strchr(_multi_args, _multi_separator))) { *(_multi_next++) = 0; } return _multi_args; } char* CmdLineParser::next_multi() { if (!_multi_next) { _multi_separator = 0; delete[] _multi_args; _multi_args = NULL; return NULL; } char* ret = _multi_next; if ((_multi_next = strchr(_multi_next, _multi_separator))) { *(_multi_next++) = 0; } return ret; } int CmdLineParser::get_option(char** val) { CmdLineParser::Option* opt_obj; if (!_argv) { THROW("unexpected"); } if (_multi_args) { THROW("in multi args mode"); } if (_done) { THROW("is done"); } int long_index; int opt = getopt_long(_argc, _argv, _short_options.c_str(), &_long_options[0], &long_index); switch (opt) { case 0: { if (!(opt_obj = find(_long_options[long_index].val))) { THROW("long option no found"); } #ifdef DISABLE_ABBREVIATE int name_pos = (opt_obj->type == REQUIRED_ARGUMENT && optarg[-1] != '=') ? optind - 2 : optind - 1; std::string cmd_name(_argv[name_pos] + 2); if (cmd_name.find(opt_obj->name) != 0) { Platform::term_printf("%s: invalid abbreviated option '--%s'\n", _argv[0], cmd_name.c_str()); return OPTION_ERROR; } #endif if (opt_obj->separator) { *val = start_multi(optarg, opt_obj->separator); } else { *val = optarg; } opt_obj->is_set = true; return opt_obj->id; } case -1: { *val = NULL; if (!_positional_args && optind != _argc) { Platform::term_printf("%s: unexpected positional arguments\n", _argv[0]); return OPTION_ERROR; } if ((opt_obj = find_missing_opt())) { Platform::term_printf("%s: option --%s is required\n", _argv[0], opt_obj->name.c_str()); return OPTION_ERROR; } _done = true; return OPTION_DONE; } case '?': if (optopt >= 255) { opt_obj = find(optopt); ASSERT(opt_obj); #ifdef DISABLE_ABBREVIATE std::string cmd_name(_argv[optind - 1] + 2); if (cmd_name.find(opt_obj->name) != 0) { Platform::term_printf("%s: invalid option '--%s'\n", _argv[0], cmd_name.c_str()); return OPTION_ERROR; } #endif Platform::term_printf("%s: option --%s requires an argument\n", _argv[0], opt_obj->name.c_str()); } else if (optopt == 0) { Platform::term_printf("%s: invalid option '%s'\n", _argv[0], _argv[optind - 1]); } else if ((opt_obj = find((char)optopt))) { Platform::term_printf("%s: option '-%c' requires an argument\n", _argv[0], opt_obj->short_name); } else { Platform::term_printf("%s: invalid option '-%c'\n", _argv[0], char(optopt)); } return OPTION_ERROR; default: if (opt > 255 || !(opt_obj = find((char)opt))) { *val = NULL; return OPTION_ERROR; } if (opt_obj->separator) { *val = start_multi(optarg, opt_obj->separator); } else { *val = optarg; } opt_obj->is_set = true; return opt_obj->id; } } char* CmdLineParser::next_argument() { if (!_argv) { THROW("unexpected"); } if (_multi_args) { return next_multi(); } if (!_done) { THROW("not done"); } if (optind == _argc) { return NULL; } return _argv[optind++]; } void CmdLineParser::show_help() { static const int HELP_START_POS = 30; static const unsigned HELP_WIDTH = 80 - HELP_START_POS; std::ostringstream os; os << _argv[0] << " - " << _description.c_str() << "\n\noptions:\n\n"; Options::iterator iter = _options.begin(); for (; iter != _options.end(); ++iter) { CmdLineParser::Option* opt = *iter; if (opt->short_name) { os << " -" << opt->short_name << ", "; } else { os << " "; } os << "--" << opt->name; if (opt->type == OPTIONAL_ARGUMENT) { os << "[="; } else if (opt->type == REQUIRED_ARGUMENT) { os << " <"; } if (opt->type == OPTIONAL_ARGUMENT || opt->type == REQUIRED_ARGUMENT) { if (opt->separator) { os << opt->arg_name << opt->separator << opt->arg_name << "..."; } else { os << opt->arg_name; } } if (opt->type == OPTIONAL_ARGUMENT) { os << "]"; } else if (opt->type == REQUIRED_ARGUMENT) { os << ">"; } int skip = HELP_START_POS - os.str().size(); if (skip < 2) { os << "\n "; } else { while (skip--) { os << " "; } } int line_count = 0; std::istringstream is(opt->help); std::string line; std::getline(is, line); do { if (line_count++) { os << " "; } if (line.size() > HELP_WIDTH) { size_t last_space, now = HELP_WIDTH; std::string sub; sub.append(line, 0, now); if ((last_space = sub.find_last_of(' ')) != std::string::npos) { now = last_space; sub.resize(now++); } os << sub << "\n"; line = line.substr(now, line.size() - now); } else { os << line << "\n"; line.clear(); } } while (line.size() || std::getline(is, line)); } os << "\n"; Platform::term_printf("%s", os.str().c_str()); }