httpup/argparser.cpp

422 lines
11 KiB
C++

////////////////////////////////////////////////////////////////////////
// FILE: argparser.cpp
// AUTHOR: Johannes Winkelmann, jw@tks6.net
// COPYRIGHT: (c) 2004 by Johannes Winkelmann
// ---------------------------------------------------------------------
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
////////////////////////////////////////////////////////////////////////
#include <iostream>
#include <sstream>
#include <cassert>
#include <libgen.h>
#include <cstring>
#include <cstdlib>
#include "argparser.h"
using namespace std;
ArgParser::ArgParser()
: m_cmdIdCounter(0),
m_optIdCounter(0)
{
}
ArgParser::~ArgParser()
{
map<int, Option*>::iterator oit = m_options.begin();
for (; oit != m_options.end(); ++oit) {
delete oit->second;
}
map<string, Command*>::iterator cit = m_commands.begin();
for (; cit != m_commands.end(); ++cit) {
delete cit->second;
}
}
int ArgParser::addCommand(APCmd& cmd,
const std::string& name,
const std::string& description,
ArgNumberCheck argNumberCheck,
int argNumber,
const std::string& otherArguments)
{
Command* command = new Command;
++m_cmdIdCounter;
cmd.id = m_cmdIdCounter;
command->apCmd = &cmd;
command->id = m_cmdIdCounter;
command->name = name;
command->argNumber = argNumber;
command->argNumberCheck = argNumberCheck;
command->description = description;
command->otherArguments = otherArguments;
m_commands[name] = command;
m_commandIdMap[cmd.id] = command;
APCmd apcmd;
apcmd.id = m_cmdIdCounter;
PREDEFINED_CMD_HELP.init("help", 'h', "Print this help message");
// add predefined commands
addOption(cmd, PREDEFINED_CMD_HELP, false);
return 0;
}
int ArgParser::addOption(const APCmd& commandKey,
APOpt& key,
bool required)
{
// TODO: check for null cmd
if (m_commandIdMap.find(commandKey.id) == m_commandIdMap.end()) {
return -1;
}
Option* o = 0;
if (key.id != -1 && m_options.find(key.id) != m_options.end()) {
o = m_options.find(key.id)->second;
}
if (!o) {
assert(key.m_initialized == true);
o = new Option();
++m_optIdCounter;
key.id = m_optIdCounter;
o->id = key.id;
o->description = key.m_description;
o->requiresValue = key.m_valueRequired;
o->shortName = key.m_shortName;
o->longName = key.m_longName;
o->valueName = key.m_valueName;
if (key.m_shortName != 0) {
m_optionsByShortName[key.m_shortName] = o;
}
if (key.m_longName != "") {
m_optionsByLongName[key.m_longName] = o;
}
m_options[key.id] = o;
}
Command* cmd = m_commandIdMap[commandKey.id];
if (required) {
cmd->mandatoryOptions[key.id] = o;
} else {
cmd->options[key.id] = o;
}
return 0;
}
void ArgParser::parse(int argc, char** argv)
{
bool commandFound = false;
string command = "";
Command* cmd = 0;
int cmdPos = 0;
m_appName = basename(argv[0]);
for (int i = 1; i < argc; ++i) {
if (argv[i][0] != '-') {
if (!commandFound) {
if (m_commands.find(argv[i]) == m_commands.end()) {
parseError("Non option / Non command argument '" +
string(argv[i]) + "'");
}
cmd = m_commands[argv[i]];
m_command.id = cmd->apCmd->id;
commandFound = true;
cmdPos = i;
break;
}
} else {
// TODO: add proper handling for global options
string arg = argv[i];
if (arg == "-h" || arg == "--help") {
cout << generateUsage() << endl;
exit(0);
}
}
}
if (!commandFound) {
parseError("No command used");
exit(-1);
}
for (int i = 1; i < argc; ++i) {
if (i == cmdPos) {
continue;
}
if (argv[i][0] == '-') {
if (argv[i][1] == '\0') {
parseError("Illegal token: '-'", cmd->name);
} else if (argv[i][1] == '-') {
char* valPtr = strchr(argv[i]+2, '=');
if (valPtr) {
*valPtr = '\0';
++valPtr;
}
if (m_optionsByLongName.find(argv[i]+2) ==
m_optionsByLongName.end()) {
parseError("unknown option:" + string(argv[i]+2),
cmd->name);
}
Option* o = m_optionsByLongName[argv[i]+2];
string val = "";
if (o->requiresValue) {
if (valPtr == NULL || *valPtr == 0) {
parseError("Value required for option '" +
string(argv[i]+2), cmd->name);
} else {
val = valPtr;
}
}
m_setOptions[o->id] = val;
} else {
if (argv[i][2] != '\0') {
parseError("invalid short option '" +
string(argv[i]+1) + "'", cmd->name);
}
if (m_optionsByShortName.find(argv[i][1]) ==
m_optionsByShortName.end()) {
parseError("unknown short option:" + string(argv[i]+1),
cmd->name);
}
Option* o = m_optionsByShortName[argv[i][1]];
string val = "";
if (o->requiresValue) {
if (i+1 == argc) {
parseError("Option required for option '" +
string(argv[i]+1), cmd->name);
} else {
val = argv[i+1];
++i;
}
}
m_setOptions[o->id] = val;
}
} else {
m_otherArguments.push_back(string(argv[i]));
}
}
if (isSet(PREDEFINED_CMD_HELP)) {
cout << generateHelpForCommand(cmd->name) << endl;
exit(0);
} else {
// make sure all required options of a command are set
std::map<int, Option*>::iterator it;
it = cmd->mandatoryOptions.begin();
for (; it != cmd->mandatoryOptions.end(); ++it) {
if (!isSet(it->second->id)) {
parseError("Command '" + cmd->name +
"' requires option " +
string("-") + it->second->shortName +
string(" | ") +
string("--") + it->second->longName + " not found",
cmd->name);
}
}
}
switch (cmd->argNumberCheck)
{
case EQ:
if (m_otherArguments.size() != cmd->argNumber) {
ostringstream ostr;
ostr << cmd->name
<< " takes exactly "
<< cmd->argNumber
<< (cmd->argNumber == 1 ? " argument." : " arguments.");
parseError(ostr.str(), cmd->name);
}
break;
case MIN:
if (m_otherArguments.size() < cmd->argNumber) {
ostringstream ostr;
ostr << cmd->name
<< " takes at least "
<< cmd->argNumber
<< (cmd->argNumber == 1 ? " argument." : " arguments.");
parseError(ostr.str(), cmd->name);
}
break;
case MAX:
if (m_otherArguments.size() > cmd->argNumber) {
ostringstream ostr;
ostr << cmd->name
<< " takes at most "
<< cmd->argNumber
<< (cmd->argNumber == 1 ? " argument." : " arguments.");
parseError(ostr.str(), cmd->name);
}
break;
case NONE:
default:
break;
}
}
void ArgParser::parseError(const string& error, const string& cmd) const
{
cerr << "Parse error: " << error << endl;
if (cmd != "") {
cerr << generateHelpForCommand(cmd) << endl;
} else {
cerr << generateUsage() << endl;
}
exit(-1);
}
ArgParser::APCmd ArgParser::command() const
{
return m_command;
}
bool ArgParser::isSet(const APOpt& key) const
{
return isSet(key.id);
}
bool ArgParser::isSet(int key) const
{
return m_setOptions.find(key) != m_setOptions.end();
}
std::string ArgParser::getOptionValue(const APOpt& key) const
{
return m_setOptions.find(key.id)->second;
}
std::string ArgParser::appName() const
{
return m_appName;
}
std::string ArgParser::generateHelpForCommand(const std::string& command) const
{
std::map<std::string, Command*>::const_iterator cit =
m_commands.find(command);
if (cit == m_commands.end()) {
return "";
}
const Command * const cmd = cit->second;
string help = "";;
help += "command '" + cmd->name + " " + cmd->otherArguments + "'\n";
help += " " + cmd->description;
help += "\n\n";
std::map<int, Option*>::const_iterator it = cmd->mandatoryOptions.begin();
if (it != cmd->mandatoryOptions.end()) {
help += " Required: \n";
for (; it != cmd->mandatoryOptions.end(); ++it) {
help += generateOptionString(it->second);
}
}
it = cmd->options.begin();
if (it != cmd->options.end()) {
help += " Optional: \n";
for (; it != cmd->options.end(); ++it) {
help += generateOptionString(it->second);
}
}
return help;
}
string ArgParser::generateOptionString(Option* o) const
{
string help = " ";
if (o->shortName) {
help += "-";
help += o->shortName;
if (o->requiresValue && o->valueName != "") {
help += " " + o->valueName;
}
help += " | ";
}
if (o->longName != "") {
help += "--";
help += o->longName;
if (o->requiresValue && o->valueName != "") {
help += "=" + o->valueName;
}
help += " ";
help += o->description;
help += "\n";
}
return help;
}
std::string ArgParser::generateUsage() const
{
string usage = getAppIdentification() +
"USAGE: " + m_appName +
" [OPTIONS] command <arguments>\n\n";
usage += " Where command is one of the following:\n";
std::map<std::string, Command*>::const_iterator it;
it = m_commands.begin();
for (; it != m_commands.end(); ++it) {
usage += " " + it->first + "\t\t" +
it->second->description + "\n";
}
return usage;
}
const std::vector<std::string>& ArgParser::otherArguments() const
{
return m_otherArguments;
}