Library of Assembled Shared Sources
arg_parser.cpp
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2011 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43#include "lass_common.h"
44#include "arg_parser.h"
45
46#include <locale>
47
48namespace lass
49{
50namespace io
51{
52
53// --- ArgParser ------------------------------------------------------------------------------
54
55/** Construct a quiet parser.
56 * A quiet parser won't write anything to the output (it will still log though), and it will
57 * throw as few exceptions as possible
58 */
60 programName_(""),
61 programVersion_(""),
62 positionals_(""),
63 isQuiet_(true)
64{
65}
66
67
68
69/** Construct a not so quiet parser.
70 * On wrong input, it will write info to the standard input and will throw some more exceptions.
71 */
72ArgParser::ArgParser(const std::string& iProgramName,
73 const std::string& iProgramVersion,
74 const std::string& iPositionalArguments):
75 programName_(iProgramName),
76 programVersion_(iProgramVersion),
77 positionals_(iPositionalArguments),
78 isQuiet_(false)
79{
80}
81
82
83
84/** parse arguments
85 * @note in contrary like @c argc/argv , you shouldn't store the program name as the first
86 * argument, since the parser will start with @c iArguments[0] . Hence, the parser would
87 * see the program name as the first positional if you did so.
88 */
89bool ArgParser::parse(const TArguments& iArguments, TArguments* oPositionals)
90{
91 bool result = true;
92 bool allPositionals = false;
93
94 for (TArguments::size_type i = 0; i < iArguments.size(); ++i)
95 {
96 std::string arg = iArguments[i];
97 if (arg.length() > 0)
98 {
99 if (arg[0] == '-' && !allPositionals)
100 {
101 if (arg.length() > 1)
102 {
103 if (arg[1] == '-')
104 {
105 if (arg.length() > 2)
106 {
107 // --longOption
108 result &= parseLong(iArguments, i);
109 }
110 else
111 {
112 // --
113 allPositionals = true;
114 }
115 }
116 else
117 {
118 // -shortOptions
119 result &= parseShort(iArguments, i);
120 }
121 }
122 else
123 {
124 // - what qbout it?
125 }
126 }
127 else
128 {
129 if (oPositionals)
130 {
131 oPositionals->push_back(arg);
132 }
133 }
134 }
135 }
136
137 return result;
138}
139
140
141
142/** parse arguments from commandline
143 * @note we suppose @c iArgv[0] is the program name and will skipped by the parser.
144 * the parser will only take care of @c iArgv[1] and the following!
145 */
146bool ArgParser::parse(int iArgc, char* iArgv[], TArguments* oPositionals)
147{
148 TArguments arguments;
149 std::copy(iArgv + 1, iArgv + iArgc, std::back_inserter(arguments));
150
151 return parse(arguments, oPositionals);
152}
153
154
155
156/** parse arguments that come by a string.
157 * break string based on whitespace unless it's surrounded by quotes.
158 * @note You shouldn't start the string with the program name like argc/argv do. The string
159 * will be parsed from the beginning, so the program name would be seen as the first
160 * positional if you did.
161 */
162bool ArgParser::parse(const std::string& iArguments, TArguments* oPositionals)
163{
164 const std::string whitespace = " \t\n";
165 const std::string quotes = "\"";
166 const std::string escapes = "\\";
167
168 //LASS_EVAL(iArguments);
169
170 TArguments result;
171
172 std::string token;
173 bool isQuoted = false;
174 char quote = '#';
175 bool isEscaped = false;
176 char escape = '#';
177
178 for (std::string::const_iterator ch = iArguments.begin(); ch != iArguments.end(); ++ch)
179 {
180 if (whitespace.find(*ch) != std::string::npos && !isQuoted)
181 {
182 if (!token.empty())
183 {
184 result.push_back(token);
185 token.clear();
186 }
187 }
188 else if (quotes.find(*ch) != std::string::npos)
189 {
190 if (isEscaped)
191 {
192 token.append(1, *ch);
193 isEscaped = false;
194 }
195 else
196 {
197 if (isQuoted)
198 {
199 if (quote == *ch)
200 {
201 isQuoted = false;
202 }
203 }
204 else
205 {
206 quote = *ch;
207 isQuoted = true;
208 }
209 }
210 }
211 else if (escapes.find(*ch) != std::string::npos)
212 {
213 if (isEscaped)
214 {
215 token.append(1, *ch);
216 isEscaped = false;
217 }
218 else
219 {
220 escape = *ch;
221 isEscaped = true;
222 }
223 }
224 else
225 {
226 if (isEscaped)
227 {
228 token.append(1, escape);
229 isEscaped = false;
230 }
231 token.append(1, *ch);
232 }
233 }
234
235 result.push_back(token);
236 return parse(result, oPositionals);
237}
238
239
240
241std::string ArgParser::usage() const
242{
243 std::ostringstream result;
244
245 result << "usage:";
246 if (!programName_.empty())
247 {
248 result << " " << programName_;
249 }
250
251 result << " [-v|--version] [-h|--help]";
252
253 for (TParameters::const_iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
254 {
255 result << " " << (*pit)->format();
256 }
257
258 if (!positionals_.empty())
259 {
260 result << " " << positionals_;
261 }
262
263 return result.str();
264}
265
266
267
268
269void ArgParser::subscribe(ArgParameter& iParameter)
270{
271 const std::string& shortName = iParameter.shortName();
272 const std::string& longName = iParameter.longName();
273
274 // we need at least a short or a long option name.
275 //
276 if (shortName.empty() && longName.empty())
277 {
278 LASS_THROW("Subscribing parameter failed because both the short and the long names are "
279 "empty what makes it impossible to identify the parameter.");
280 }
281
282 // short names cannot be long and must be alphanumerical
283 //
284 if (!shortName.empty())
285 {
286 if (shortName.length() != 1 || !std::isalnum(shortName[0], std::locale()))
287 {
288 LASS_THROW("Subscribing parameter failed because the short name is not a single "
289 "alpha-numeric character.");
290 }
291 }
292
293 if (!isValidLongName(longName))
294 {
295 LASS_THROW("Subscribing parameter failed because the long name isn't valid.");
296 }
297
298 // both short and long names must be unique (if their not zero length)
299 //
300 for (TParameters::const_iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
301 {
302 ArgParameter* param = *pit;
303 if ((!shortName.empty() && param->shortName() == shortName) ||
304 (!longName.empty() && param->longName() == longName))
305 {
306 LASS_THROW("Subscribing parameter failed because there is already an parameter with "
307 "the same short or long name.");
308 }
309 }
310
311 // all is well, subscribe!
312 parameters_.push_back(&iParameter);
313}
314
315
316
317bool ArgParser::parseShort(const TArguments& iArguments, TSize& ioIndex)
318{
319 const std::string& arg = iArguments[ioIndex];
320 LASS_ASSERT(arg.length() > 1); // arg is of form '-abcdef'
321
322 // first one can have a value. scan all parameters to see if you can find it
323 //
324 std::string shortName(arg, 1, 1); // in above example shortName is 'a'
325 LASS_ASSERT(shortName.length() == 1);
326 LASS_ASSERT(shortName[0] == arg[1]);
327 if (writeVersionOrHelp(shortName))
328 {
329 return false;
330 }
331 for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
332 {
333 ArgParameter* param = *pit;
334 if (param->shortName() == shortName)
335 {
336 if (!(param->mode() & amNoValue)) // param can have value?
337 {
338 std::string value = "";
339 if (arg.length() > 2)
340 {
341 value = arg.substr(2); // the rest of arg is the value
342 }
343 else
344 {
345 // if more arguments follow, the following is the value (but it can't start
346 // with a '-' !
347 //
348 if (ioIndex + 1 < iArguments.size())
349 {
350 std::string candidate = iArguments[ioIndex + 1];
351 if (candidate.length() > 0 && candidate[0] != '-')
352 {
353 value = candidate;
354 ++ioIndex;
355 }
356 }
357 }
358 param->setValue(value);
359 return true;
360 }
361 else
362 {
363 param->setValue("");
364 }
365 }
366 }
367
368 // if first one couldn't have a value (it was a flag), then other flags may follow
369 //
370 for (std::string::size_type i = 2; i < arg.length(); ++i)
371 {
372 shortName = arg.substr(i, 1);
373 LASS_ASSERT(shortName.length() == 1);
374 LASS_ASSERT(shortName[0] == arg[i]);
375 if (writeVersionOrHelp(shortName))
376 {
377 return false;
378 }
379 for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
380 {
381 ArgParameter* param = *pit;
382 if (param->shortName() == shortName)
383 {
384 if (param->mode() & amNoValue) // only params without values may follow
385 {
386 param->setValue("");
387 }
388 else
389 {
390 if (!isQuiet_)
391 {
392 LASS_COUT << "Bad program arguments: the argument '" << shortName
393 << "' can take a value. You cannot group it with other arguments like "
394 << "in '" << arg << "'.\n" << usage() << "\n";
395 }
396 return false;
397 }
398 }
399 }
400 }
401 return true;
402}
403
404
405
406bool ArgParser::parseLong(const TArguments& iArguments, TSize iIndex)
407{
408 const std::string& arg = iArguments[iIndex];
409 LASS_ASSERT(arg.length() > 2); // arg is of form '--longName=value'
410
411 // split name and value
412 //
413 std::string longName;
414 std::string value;
415 std::string::size_type equalPosition = arg.find('=');
416 if (equalPosition == std::string::npos)
417 {
418 longName = arg.substr(2);
419 value = "";
420 }
421 else
422 {
423 longName = arg.substr(2, equalPosition - 2);
424 value = arg.substr(equalPosition + 1);
425 }
426
427 // check syntax of name.
428 //
429 if (!isValidLongName(longName))
430 {
431 if (!isQuiet_)
432 {
433 LASS_COUT << "Bad program arguments: the argument '--" << longName << "' is not a "
434 << "valid long parameter name.\n" << usage() << "\n";
435 }
436 return false;
437 }
438
439 if (writeVersionOrHelp(longName))
440 {
441 return false;
442 }
443
444 TParameters::iterator match = parameters_.end(); // no match yet.
445
446 // try to find an exact match
447 //
448 for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
449 {
450 ArgParameter* param = *pit;
451 if (param->longName() == longName)
452 {
453 match = pit; // huray, we have one!
454 break;
455 }
456 }
457
458 // otherwise, try to find a unique best match
459 //
460 if (match == parameters_.end())
461 {
462 for (std::string::size_type length = longName.length(); length > 0; --length)
463 {
464 TParameters::iterator candidate = parameters_.end();
465 bool isUnique = true;
466
467 for (TParameters::iterator pit = parameters_.begin(); pit != parameters_.end(); ++pit)
468 {
469 ArgParameter* param = *pit;
470 if (param->longName().length() >= length)
471 {
472 if (param->longName().substr(1, length) == longName.substr(1, length))
473 {
474 isUnique = candidate == parameters_.end();
475 candidate = pit;
476 }
477 }
478 }
479
480 if (candidate != parameters_.end() && isUnique == true)
481 {
482 match = candidate; // huray too!
483 break;
484 }
485 }
486 }
487
488 // by now, we have to have a match
489 //
490 if (match == parameters_.end())
491 {
492 if (!isQuiet_)
493 {
494 LASS_COUT << "Bad program arguments: the argument '--" << longName << "' is not as a "
495 << "long parameter name, nor is it a unique abbrevation of one.\n" << usage()
496 << "\n";
497 }
498 return false;
499 }
500
501 // we have one, let's deal with it.
502 //
503 (*match)->setValue(value); // even works if no value is expected
504 return true;
505}
506
507
508
509/** long names can only exists of alphanumerics and some extra characters '-'.
510 */
511bool ArgParser::isValidLongName(const std::string& iLongName) const
512{
513 std::string extra = "-";
514
515 for (std::string::size_type i = 0; i < iLongName.length(); ++i)
516 {
517 const char ch = iLongName[i];
518 if (!std::isalnum(ch, std::locale()) && extra.find(ch) == std::string::npos)
519 {
520 return false;
521 }
522 }
523 return true;
524}
525
526
527
528bool ArgParser::writeVersionOrHelp(const std::string& iArgument) const
529{
530 if (iArgument == "v" || iArgument == "version")
531 {
532 writeVersion();
533 return true;
534 }
535 if (iArgument == "h" || iArgument == "help")
536 {
537 writeHelp();
538 return true;
539 }
540 return false;
541}
542
543
544
545void ArgParser::writeVersion() const
546{
547 LASS_COUT << programName_ << " version " << programVersion_ << "\n";
548}
549
550
551
552void ArgParser::writeHelp() const
553{
554 writeVersion();
555 LASS_COUT << usage() << "\n";
556}
557
558
559
560// --- ArgParameter -------------------------------------------------------------------------------------
561
562ArgParameter::~ArgParameter()
563{
564}
565
566
567
568const std::string& ArgParameter::shortName() const
569{
570 return shortName_;
571}
572
573
574
575const std::string& ArgParameter::longName() const
576{
577 return longName_;
578}
579
580
581
582int ArgParameter::mode() const
583{
584 return mode_;
585}
586
587
588
589bool ArgParameter::operator!() const
590{
591 return !isSet_;
592}
593
594
595
596ArgParameter::operator bool() const
597{
598 return isSet_;
599}
600
601
602
603ArgParameter::ArgParameter(ArgParser& iParser,
604 const std::string& iShortName,
605 const std::string& iLongName,
606 int iArgMode):
607 parser_(iParser),
608 shortName_(iShortName),
609 longName_(iLongName),
610 mode_(iArgMode),
611 isSet_(false)
612{
613 parser_.subscribe(*this);
614}
615
616
617
618const std::string ArgParameter::names() const
619{
620 std::ostringstream result;
621
622 if (!shortName_.empty())
623 {
624 result << "-" << shortName_;
625 }
626 if (!longName_.empty())
627 {
628 if (!shortName_.empty())
629 {
630 result << "|";
631 }
632 result << "--" << longName_;
633 }
634
635 return result.str();
636}
637
638
639
640bool ArgParameter::parserIsQuiet() const
641{
642 return parser_.isQuiet_;
643}
644
645
646
647void ArgParameter::set()
648{
649 isSet_ = true;
650}
651
652
653
654const std::string ArgParameter::format() const
655{
656 return doFormat();
657}
658
659
660
661bool ArgParameter::setValue(const std::string& iValue)
662{
663 return doSetValue(iValue);
664}
665
666
667
668const std::string ArgParameter::doFormat() const
669{
670 std::ostringstream result;
671 result << "[" << names() << "]";
672 return result.str();
673}
674
675
676
677bool ArgParameter::doSetValue(const std::string& /* iValue */)
678{
679 //LASS_LOG("parameter '" << names() << "' is set.");
680 set();
681 return true;
682}
683
684
685
686// --- ArgFlag -------------------------------------------------------------------------------------
687
688ArgFlag::ArgFlag(ArgParser& iParser,
689 const std::string& iShortName,
690 const std::string& iLongName):
691 ArgParameter(iParser, iShortName, iLongName, amNoValue)
692{
693}
694
695
696
697ArgFlag::ArgFlag(ArgParser& iParser,
698 const ArgFormat& iFormat):
699 ArgParameter(iParser, iFormat.shortName, iFormat.longName, amNoValue)
700{
701}
702
703
704
705}
706
707}
708
709// EOF
the parser itself
Definition arg_parser.h:99
bool parse(const std::string &iArguments, TArguments *oPositionals=0)
parse arguments that come by a string.
ArgParser()
Construct a quiet parser.
@ amNoValue
argument takes no value (argument is flag)
Definition arg_parser.h:70
#define LASS_COUT
return reference to 'cout' proxy stream of the lass::io::ProxyMan singleton.
Definition io_fwd.h:69
streams, binary streams, vrmlstreams, ...
Library for Assembled Shared Sources.
Definition config.h:53
structure to store ArgFlag and ArgValue formats
Definition arg_parser.h:84