summaryrefslogtreecommitdiff
path: root/argparser.cpp
blob: f6be2864c79060ab24238e5fcc55ac43e94bd082 (plain)
    1 ////////////////////////////////////////////////////////////////////////
    2 // FILE:        argparser.cpp
    3 // AUTHOR:      Johannes Winkelmann, jw@tks6.net
    4 // COPYRIGHT:   (c) 2004 by Johannes Winkelmann
    5 // ---------------------------------------------------------------------
    6 //  This program is free software; you can redistribute it and/or modify
    7 //  it under the terms of the GNU General Public License as published by
    8 //  the Free Software Foundation; either version 2 of the License, or
    9 //  (at your option) any later version.
   10 ////////////////////////////////////////////////////////////////////////
   11 
   12 #include <iostream>
   13 #include <sstream>
   14 #include <cassert>
   15 #include <libgen.h>
   16 #include <cstring>
   17 #include <cstdlib>
   18 
   19 #include "argparser.h"
   20 
   21 using namespace std;
   22 
   23 ArgParser::ArgParser()
   24     : m_cmdIdCounter(0),
   25       m_optIdCounter(0)
   26 {
   27 }
   28 
   29 ArgParser::~ArgParser()
   30 {
   31     map<int, Option*>::iterator oit = m_options.begin();
   32     for (; oit != m_options.end(); ++oit) {
   33         delete oit->second;
   34     }
   35 
   36     map<string, Command*>::iterator cit = m_commands.begin();
   37     for (; cit != m_commands.end(); ++cit) {
   38         delete cit->second;
   39     }
   40 }
   41 
   42 int ArgParser::addCommand(APCmd& cmd,
   43                           const std::string& name,
   44                           const std::string& description,
   45                           ArgNumberCheck argNumberCheck,
   46                           int argNumber,
   47                           const std::string& otherArguments)
   48 {
   49     Command* command = new Command;
   50 
   51     ++m_cmdIdCounter;
   52     cmd.id = m_cmdIdCounter;
   53 
   54     command->apCmd = &cmd;
   55     command->id = m_cmdIdCounter;
   56     command->name = name;
   57     command->argNumber = argNumber;
   58     command->argNumberCheck = argNumberCheck;
   59 
   60     command->description = description;
   61     command->otherArguments = otherArguments;
   62 
   63     m_commands[name] = command;
   64     m_commandIdMap[cmd.id] = command;
   65 
   66 
   67     APCmd apcmd;
   68     apcmd.id = m_cmdIdCounter;
   69 
   70     PREDEFINED_CMD_HELP.init("help", 'h', "Print this help message");
   71 
   72     // add predefined commands
   73     addOption(cmd, PREDEFINED_CMD_HELP, false);
   74 
   75     return 0;
   76 }
   77 
   78 int ArgParser::addOption(const APCmd& commandKey,
   79                          APOpt& key,
   80                          bool required)
   81 {
   82     // TODO: check for null cmd
   83     if (m_commandIdMap.find(commandKey.id) == m_commandIdMap.end()) {
   84         return -1;
   85     }
   86 
   87     Option* o = 0;
   88     if (key.id != -1 && m_options.find(key.id) != m_options.end()) {
   89         o = m_options.find(key.id)->second;
   90     }
   91 
   92     if (!o) {
   93 
   94         assert(key.m_initialized == true);
   95 
   96         o = new Option();
   97         ++m_optIdCounter;
   98         key.id = m_optIdCounter;
   99 
  100         o->id = key.id;
  101         o->description = key.m_description;
  102         o->requiresValue = key.m_valueRequired;
  103         o->shortName = key.m_shortName;
  104         o->longName = key.m_longName;
  105         o->valueName = key.m_valueName;
  106 
  107         if (key.m_shortName != 0) {
  108             m_optionsByShortName[key.m_shortName] = o;
  109         }
  110         if (key.m_longName != "") {
  111             m_optionsByLongName[key.m_longName] = o;
  112         }
  113 
  114         m_options[key.id] = o;
  115 
  116     }
  117 
  118 
  119     Command* cmd = m_commandIdMap[commandKey.id];
  120     if (required) {
  121         cmd->mandatoryOptions[key.id] = o;
  122     } else {
  123         cmd->options[key.id] = o;
  124     }
  125 
  126 
  127     return 0;
  128 }
  129 
  130 
  131 void ArgParser::parse(int argc, char** argv)
  132 {
  133     bool commandFound = false;
  134     string command = "";
  135     Command* cmd = 0;
  136     int cmdPos = 0;
  137 
  138     m_appName = basename(argv[0]);
  139 
  140     for (int i = 1; i < argc; ++i) {
  141         if (argv[i][0] != '-') {
  142             if (!commandFound) {
  143                 if (m_commands.find(argv[i]) == m_commands.end()) {
  144                     parseError("Non option / Non command argument '" +
  145                                string(argv[i]) + "'");
  146                 }
  147 
  148                 cmd = m_commands[argv[i]];
  149                 m_command.id = cmd->apCmd->id;
  150                 commandFound = true;
  151                 cmdPos = i;
  152                 break;
  153             }
  154         } else {
  155             // TODO: add proper handling for global options
  156 
  157             string arg = argv[i];
  158             if (arg == "-h" || arg == "--help") {
  159                 cout << generateUsage() << endl;
  160                 exit(0);
  161             }
  162         }
  163 
  164     }
  165 
  166     if (!commandFound) {
  167         parseError("No command used");
  168         exit(-1);
  169     }
  170 
  171     for (int i = 1; i < argc; ++i) {
  172 
  173         if (i == cmdPos) {
  174             continue;
  175         }
  176 
  177         if (argv[i][0] == '-') {
  178             if (argv[i][1] == '\0') {
  179                 parseError("Illegal token: '-'", cmd->name);
  180             } else if (argv[i][1] == '-') {
  181 
  182                 char* valPtr = strchr(argv[i]+2, '=');
  183                 if (valPtr) {
  184                     *valPtr = '\0';
  185                     ++valPtr;
  186                 }
  187 
  188                 if (m_optionsByLongName.find(argv[i]+2) ==
  189                     m_optionsByLongName.end()) {
  190                     parseError("unknown option:" + string(argv[i]+2),
  191                                cmd->name);
  192                 }
  193 
  194                 Option* o = m_optionsByLongName[argv[i]+2];
  195                 string val = "";
  196                 if (o->requiresValue) {
  197                     if (valPtr == NULL || *valPtr == 0) {
  198                         parseError("Value required for option '" +
  199                                    string(argv[i]+2), cmd->name);
  200                     } else {
  201                         val = valPtr;
  202                     }
  203                 }
  204                 m_setOptions[o->id] = val;
  205             } else {
  206                 if (argv[i][2] != '\0') {
  207                     parseError("invalid short option '" +
  208                                string(argv[i]+1) + "'", cmd->name);
  209                 }
  210 
  211                 if (m_optionsByShortName.find(argv[i][1]) ==
  212                     m_optionsByShortName.end()) {
  213                     parseError("unknown short option:" + string(argv[i]+1),
  214                                cmd->name);
  215                 }
  216 
  217                 Option* o = m_optionsByShortName[argv[i][1]];
  218                 string val = "";
  219                 if (o->requiresValue) {
  220                     if (i+1 == argc) {
  221                         parseError("Option required for option '" +
  222                                    string(argv[i]+1), cmd->name);
  223                     } else {
  224                         val = argv[i+1];
  225                         ++i;
  226                     }
  227                 }
  228                 m_setOptions[o->id] = val;
  229             }
  230         } else {
  231             m_otherArguments.push_back(string(argv[i]));
  232         }
  233     }
  234 
  235     if (isSet(PREDEFINED_CMD_HELP)) {
  236         cout << generateHelpForCommand(cmd->name) << endl;
  237         exit(0);
  238     } else {
  239 
  240         // make sure all required options of a command are set
  241 
  242         std::map<int, Option*>::iterator it;
  243         it = cmd->mandatoryOptions.begin();
  244         for (; it != cmd->mandatoryOptions.end(); ++it) {
  245             if (!isSet(it->second->id)) {
  246                 parseError("Command '" + cmd->name +
  247                            "' requires option " +
  248                            string("-") + it->second->shortName +
  249                            string(" | ") +
  250                            string("--") + it->second->longName + " not found",
  251                            cmd->name);
  252             }
  253         }
  254     }
  255 
  256     switch (cmd->argNumberCheck)
  257     {
  258         case EQ:
  259             if (m_otherArguments.size() != cmd->argNumber) {
  260                 ostringstream ostr;
  261                 ostr << cmd->name
  262                      << " takes exactly "
  263                      << cmd->argNumber
  264                      << (cmd->argNumber == 1 ? " argument." : " arguments.");
  265 
  266                 parseError(ostr.str(), cmd->name);
  267             }
  268             break;
  269         case MIN:
  270             if (m_otherArguments.size() < cmd->argNumber) {
  271                 ostringstream ostr;
  272                 ostr << cmd->name
  273                      << " takes at least "
  274                      << cmd->argNumber
  275                      << (cmd->argNumber == 1 ? " argument." : " arguments.");
  276 
  277                 parseError(ostr.str(), cmd->name);
  278             }
  279             break;
  280         case MAX:
  281             if (m_otherArguments.size() > cmd->argNumber) {
  282                 ostringstream ostr;
  283                 ostr << cmd->name
  284                      << " takes at most "
  285                      << cmd->argNumber
  286                      << (cmd->argNumber == 1 ? " argument." : " arguments.");
  287 
  288                 parseError(ostr.str(), cmd->name);
  289             }
  290             break;
  291         case NONE:
  292         default:
  293             break;
  294     }
  295 }
  296 
  297 void ArgParser::parseError(const string& error, const string& cmd) const
  298 {
  299     cerr << "Parse error: " << error << endl;
  300     if (cmd != "") {
  301         cerr << generateHelpForCommand(cmd) << endl;
  302     } else {
  303         cerr << generateUsage() << endl;
  304     }
  305     exit(-1);
  306 }
  307 
  308 ArgParser::APCmd ArgParser::command() const
  309 {
  310     return m_command;
  311 }
  312 
  313 bool ArgParser::isSet(const APOpt& key) const
  314 {
  315     return isSet(key.id);
  316 }
  317 
  318 bool ArgParser::isSet(int key) const
  319 {
  320     return m_setOptions.find(key) != m_setOptions.end();
  321 }
  322 
  323 
  324 std::string ArgParser::getOptionValue(const APOpt& key) const
  325 {
  326     return m_setOptions.find(key.id)->second;
  327 }
  328 
  329 std::string ArgParser::appName() const
  330 {
  331     return m_appName;
  332 }
  333 
  334 std::string ArgParser::generateHelpForCommand(const std::string& command) const
  335 {
  336     std::map<std::string, Command*>::const_iterator cit =
  337         m_commands.find(command);
  338 
  339     if (cit == m_commands.end()) {
  340         return "";
  341     }
  342 
  343     const Command * const  cmd = cit->second;
  344     string help = "";;
  345 
  346     help += "command '" + cmd->name + " " + cmd->otherArguments + "'\n";
  347     help += "  " + cmd->description;
  348     help += "\n\n";
  349 
  350 
  351     std::map<int, Option*>::const_iterator it = cmd->mandatoryOptions.begin();
  352     if (it != cmd->mandatoryOptions.end()) {
  353         help += "  Required: \n";
  354         for (; it != cmd->mandatoryOptions.end(); ++it) {
  355             help += generateOptionString(it->second);
  356         }
  357     }
  358 
  359     it = cmd->options.begin();
  360     if (it != cmd->options.end()) {
  361         help += "  Optional: \n";
  362         for (; it != cmd->options.end(); ++it) {
  363             help += generateOptionString(it->second);
  364         }
  365     }
  366 
  367     return help;
  368 }
  369 
  370 string ArgParser::generateOptionString(Option* o) const
  371 {
  372     string help = "    ";
  373 
  374     if (o->shortName) {
  375         help += "-";
  376         help += o->shortName;
  377 
  378         if (o->requiresValue && o->valueName != "") {
  379             help += " " + o->valueName;
  380         }
  381 
  382         help += " | ";
  383     }
  384 
  385     if (o->longName != "") {
  386         help += "--";
  387 
  388         help += o->longName;
  389         if (o->requiresValue && o->valueName != "") {
  390             help += "=" + o->valueName;
  391         }
  392 
  393         help += "    ";
  394         help += o->description;
  395         help += "\n";
  396     }
  397 
  398     return help;
  399 }
  400 
  401 std::string ArgParser::generateUsage() const
  402 {
  403     string usage = getAppIdentification() +
  404         "USAGE: " + m_appName +
  405         " [OPTIONS] command <arguments>\n\n";
  406     usage += "  Where command is one of the following:\n";
  407 
  408     std::map<std::string, Command*>::const_iterator it;
  409     it = m_commands.begin();
  410     for (; it != m_commands.end(); ++it) {
  411         usage += "    " + it->first + "\t\t" +
  412             it->second->description + "\n";
  413     }
  414 
  415     return usage;
  416 }
  417 
  418 const std::vector<std::string>& ArgParser::otherArguments() const
  419 {
  420     return m_otherArguments;
  421 }

Generated by cgit