1 // ***************************************************************************
2 // bamtools_getopt.h (c) 2010 Derek Barnett, Erik Garrison
3 // Marth Lab, Department of Biology, Boston College
4 // All rights reserved.
5 // ---------------------------------------------------------------------------
6 // Last modified: 26 May 2010
7 // ---------------------------------------------------------------------------
8 // Provides a configurable commandline parser used by the BamTools subtools
9 // ***************************************************************************
11 #ifndef BAMTOOLS_GETOPT_H
12 #define BAMTOOLS_GETOPT_H
31 // ctor: takes the 'standard' command line args (optional offset)
32 GetOpt(int argc, char* argv[], int offset = 0);
37 // set rules for bare word arguments
39 // add an optional 'bare word' argument (eg 'help')
40 // 'name' is not used on the command line, but for reporting
41 void addOptionalArgument(const std::string& name, std::string* value);
43 // add a required 'bare word' argument (eg input data file)
44 // 'name' is not used on the command line, but for reporting
45 void addRequiredArgument(const std::string& name, std::string* value);
47 // set rules for key=>value options
49 // add standard option with arguments ( -Wall, -O2, --type=foo )
50 void addOption(const char shortName, const std::string& longName, std::string* value);
52 // add an option whose argument is optional (eg --log may default to dumping to stderr, unless a file is specified )
53 // must provide a default string
54 void addOptionalOption(const char shortName, const std::string& longName, std::string* value, const std::string& defaultValue);
55 void addOptionalOption(const std::string& longName, std::string* value, const std::string& defaultValue);
57 // add a repeatable option (like compiler includes -I/path/ -I/path2/ etc)
58 // only supporting one type of name (short/long) for this option for now
59 void addRepeatableOption(const char shortName, std::vector<std::string>* values); // single char version
60 void addRepeatableOption(const std::string& longName, std::vector<std::string>* values); // long name version
62 // add an option that takes a variable number of arguments ( --files f1 f2 f3 f4... )
63 void addVariableLengthOption(const std::string& longName, std::vector<std::string>* values);
65 // set rules for on/off switch
67 // on/off switch ( --verbose --searchOnly ) only long names supported for now
68 void addSwitch(const std::string& longName, bool* ok);
70 // parse and query methods
73 // get application name
74 const std::string& applicationName(void) const;
76 // query if particular 'bare-word' argument is set
77 bool isSet(const std::string& name) const;
79 // runs parser (does validation and assign values to arguments)
80 // returns success/fail
85 // define Option-related types & enums
87 enum OptionType { OptUnknown = 0
100 Option(OptionType t = OptUnknown, const char shortName = 0, const std::string& longName = "")
102 , ShortName(shortName)
110 std::string LongName;
113 std::string* StringValue;
114 std::vector<std::string>* ListValue;
121 void init(int argc, char* argv[], int offset);
122 void saveOption(const Option& opt); // const & ?? he doesnt use it - why?
123 void setSwitch(const Option& opt);
127 std::vector<Option> m_options;
128 std::map<std::string, int> m_setOptions;
129 std::vector<std::string> m_args;
130 std::string m_appname;
132 int m_numberRequiredArguments;
133 int m_numberOptionalArguments;
134 Option m_requiredArgument;
135 Option m_optionalArgument;
137 int m_currentArgument;
141 GetOpt::GetOpt(int argc, char* argv[], int offset)
143 init(argc, argv, offset);
147 GetOpt::~GetOpt(void) { }
149 // add an optional 'bare word' argument (eg 'help')
150 // 'name' is not used on the command line, but for reporting
152 void GetOpt::addOptionalArgument(const std::string& name, std::string* value) {
154 Option opt( OptUnknown, 0, name );
155 opt.StringValue = value;
156 m_optionalArgument = opt;
157 ++m_numberOptionalArguments;
158 *value = std::string();
161 // add a required 'bare word' argument (eg input data file)
162 // 'name' is not used on the command line, but for reporting
164 void GetOpt::addRequiredArgument(const std::string& name, std::string* value) {
166 Option opt( OptUnknown, 0, name );
167 opt.StringValue = value;
168 m_requiredArgument = opt;
169 ++m_numberRequiredArguments;
170 *value = std::string();
173 // add standard option with arguments ( -Wall, -O2, --type=foo )
175 void GetOpt::addOption(const char shortName, const std::string& longName, std::string* value) {
177 Option opt( OptArg1, shortName, longName );
178 opt.StringValue = value;
180 *value = std::string();
183 // add an option whose argument is optional (eg --log may default to dumping to stderr, unless a file is specified )
184 // must provide a default string
185 // short & long name version
187 void GetOpt::addOptionalOption(const char shortName, const std::string& longName, std::string* value, const std::string& defaultValue) {
189 Option opt( OptOptional, shortName, longName );
190 opt.StringValue = value;
191 opt.Default = defaultValue;
193 *value = std::string();
196 // long name only version
198 void GetOpt::addOptionalOption(const std::string& longName, std::string* value, const std::string& defaultValue) {
199 addOptionalOption(0, longName, value, defaultValue);
202 // add a repeatable option (like compiler includes -I/path/ -I/path2/ etc)
203 // only supporting one type of name (short/long) for this option for now
204 // short name only version
206 void GetOpt::addRepeatableOption(const char shortName, std::vector<std::string>* values) {
208 Option opt( OptRepeat, shortName, std::string() );
209 opt.ListValue = values;
211 *values = std::vector<std::string>();
214 // long name only version
216 void GetOpt::addRepeatableOption(const std::string& longName, std::vector<std::string>* values) {
218 Option opt( OptRepeat, 0, longName );
219 opt.ListValue = values;
221 *values = std::vector<std::string>();
224 // add an option that takes a variable number of arguments ( --files f1 f2 f3 f4... )
226 void GetOpt::addVariableLengthOption(const std::string& longName, std::vector<std::string>* values) {
228 Option opt( OptVariable, 0, longName );
229 opt.ListValue = values;
231 *values = std::vector<std::string>();
234 // on/off switch ( --verbose --searchOnly ) only long names supported for now
236 void GetOpt::addSwitch(const std::string& longName, bool* ok) {
238 Option opt( OptSwitch, 0, longName );
245 const std::string& GetOpt::applicationName(void) const {
250 void GetOpt::init(int argc, char* argv[], int offset) {
252 m_numberRequiredArguments = 0;
253 m_numberOptionalArguments = 0;
254 m_currentArgument = 1;
259 std::string fullPath = argv[0];
260 size_t lastSlash = fullPath.find_last_of("/\\"); // should work on Unix- and Windows-style paths
261 m_appname = fullPath.substr(lastSlash + 1);
263 // store remaining arguments from offset to end
264 for (int i = offset + 1; i < argc; ++i) {
265 m_args.push_back( argv[i] );
269 std::cerr << "GetOpt ERROR: No arguments given." << std::endl;
274 // query if particular 'bare-word' argument is set
276 bool GetOpt::isSet(const std::string& name) const {
277 return ( m_setOptions.find(name) != m_setOptions.end() );
280 // runs parser (does validation and assign values to arguments)
281 // returns success/fail
283 bool GetOpt::parse(void) {
285 // initialize argument stack (reversed input args)
286 std::vector<std::string> argStack( m_args.rbegin(), m_args.rend() );
289 enum State { StartingState, ExpectingState, OptionalState };
290 State state = StartingState;
292 // initialize token types
293 enum TokenType { LongOpt, ShortOpt, Arg, End };
294 TokenType token = End;
295 TokenType currentType = End;
297 // store option list bounds
298 std::vector<Option>::const_iterator optBegin = m_options.begin();
299 std::vector<Option>::const_iterator optEnd = m_options.end();
301 // declare currentOption
302 Option currentOption;
304 // we're going to fake an 'End' argument
305 bool isExtraLoopNeeded = true;
307 // iterate through stack contents & do one extra loop for the fake 'End'
308 while ( !argStack.empty() || isExtraLoopNeeded ) {
311 std::string originalArg; // store the original arg because we're going to mangle 'arg'
313 // if contents on the arg stack
314 if ( !argStack.empty() ) {
316 arg = argStack.back();
321 // long option version
322 if ( arg.substr(0,2) == "--" ) {
330 // make sure there's still somthing there
332 std::cerr << "'--' feature is not supported, yet." << std::endl;
336 // split any key=value style args
337 size_t foundEqual = arg.find('=');
338 if ( foundEqual != std::string::npos ) {
340 // push value back onto stack
341 argStack.push_back( arg.substr(foundEqual+1) );
344 // save key as current arg
345 arg = arg.substr(0, foundEqual);
350 // short option version
351 else if ( arg.at(0) == '-' ) {
356 // if option is directly followed by argument (eg -Wall), push that arg back onto stack
357 if ( arg.length() > 2 ) {
358 argStack.push_back( arg.substr(2) );
366 // bare-word argument
367 else { token = Arg; }
370 // in fake End iteration
371 else { token = End; }
373 // look up arg in list of known options, modify token type if necessary
375 if ( token != End ) {
377 // look up arg in option list
378 std::vector<Option>::const_iterator optIter = optBegin;
379 for ( ; optIter != optEnd; ++optIter ) {
380 const Option& o = (*optIter);
381 if ( (token == LongOpt && arg == o.LongName) ||
382 (token == ShortOpt && arg.at(0) == o.ShortName) ) {
388 // modify token type if needed
389 if ( token == LongOpt && opt.Type == OptUnknown ) {
390 if ( currentOption.Type != OptVariable ) {
391 std::cerr << "GetOpt ERROR: Unknown option --" << arg << std::endl;
396 } else if ( token == ShortOpt && opt.Type == OptUnknown ) {
397 if ( currentOption.Type != OptVariable ) {
398 std::cerr << "GetOpt ERROR: Unknown option -" << arg.at(0) << std::endl;
404 } else { opt = Option(OptEnd); }
410 case ( StartingState ) :
412 if ( opt.Type == OptSwitch ) {
414 m_setOptions.insert( std::pair<std::string, int>(opt.LongName, 1) );
415 m_setOptions.insert( std::pair<std::string, int>((const char*)&opt.ShortName, 1) );
416 } else if ( opt.Type == OptArg1 || opt.Type == OptRepeat ) {
417 state = ExpectingState;
420 m_setOptions.insert( std::pair<std::string, int>(opt.LongName, 1) );
421 m_setOptions.insert( std::pair<std::string, int>((const char*)&opt.ShortName, 1) );
422 } else if ( opt.Type == OptOptional || opt.Type == OptVariable ) {
423 state = OptionalState;
426 m_setOptions.insert( std::pair<std::string, int>(opt.LongName, 1) );
427 m_setOptions.insert( std::pair<std::string, int>((const char*)&opt.ShortName, 1) );
428 } else if ( opt.Type == OptEnd ) {
429 // do nothing (we're almost done here)
430 } else if ( opt.Type == OptUnknown && token == Arg ) {
431 if ( m_numberRequiredArguments > 0 ) {
432 if ( (*m_requiredArgument.StringValue).empty() ) {
433 *m_requiredArgument.StringValue = arg;
435 std::cerr << "Too many bare arguments" << std::endl;
440 else if ( m_numberOptionalArguments > 0 ) {
441 if ( (*m_optionalArgument.StringValue).empty() ) {
442 *m_optionalArgument.StringValue = arg;
444 std::cerr << "Too many bare arguments" << std::endl;
449 std::cerr << "GetOpt ERROR: Unhandled StartingState case: " << opt.Type << std::endl;
455 case ( ExpectingState ) :
457 if ( token == Arg ) {
458 if ( currentOption.Type == OptArg1 ) {
459 *currentOption.StringValue = arg;
460 state = StartingState;
461 } else if ( currentOption.Type == OptRepeat ) {
462 currentOption.ListValue->push_back(arg);
463 state = StartingState;
465 std::cerr << "GetOpt ERROR: Unhandled ExpectingState case: " << currentOption.Type << std::endl;
469 std::string name = (currentType == LongOpt) ? currentOption.LongName : (const char*)¤tOption.ShortName;
470 std::cerr << "GetOpt ERROR: Expected an argument after option: " << name << std::endl;
476 case ( OptionalState ) :
478 if ( token == Arg ) {
479 if ( currentOption.Type == OptOptional ) {
480 *currentOption.StringValue = arg;
481 state = StartingState;
482 } else if ( currentOption.Type == OptVariable ) {
483 currentOption.ListValue->push_back(originalArg);
484 // stay in this state
486 std::cerr << "GetOpt ERROR: Unhandled OptionalState case: " << currentOption.Type << std::endl;
491 // optional argument not specified
492 if ( currentOption.Type == OptOptional ) {
493 *currentOption.StringValue = currentOption.Default;
496 if ( token != End ) {
497 // re-evaluate current argument
498 argStack.push_back( originalArg );
502 state = StartingState;
508 if ( token == End ) {
509 isExtraLoopNeeded = false;
513 // check that required argument has been satisfied
514 if ( m_numberRequiredArguments > 0 && (*m_requiredArgument.StringValue).empty() ) {
515 std::cerr << "Lacking required argument" << std::endl;
523 void GetOpt::print(void) {
525 std::cout << "---------------------------------" << std::endl;
526 std::cout << "Options for app: " << m_appname << std::endl;
527 std::cout << std::endl;
528 std::cout << "Args: ";
529 std::vector<std::string>::const_iterator argIter = m_args.begin();
530 std::vector<std::string>::const_iterator argEnd = m_args.end();
531 for ( ; argIter != argEnd; ++argIter ) {
532 std::cout << (*argIter) << " ";
534 std::cout << std::endl;
538 void GetOpt::saveOption(const Option& opt) {
539 // check for conflicts (duplicating options) ??
540 m_options.push_back(opt);
544 void GetOpt::setSwitch(const Option& opt) {
545 assert( opt.Type == OptSwitch );
546 *opt.BoolValue = true;
549 } // namespace BamTools
551 #endif // BAMTOOLS_GETOPT_H