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 }
|