]> git.donarmstrong.com Git - bamtools.git/blob - bamtools_getopt.h
Reorganization of toolkit. Split subtools out to own headers. Added custom getopt...
[bamtools.git] / bamtools_getopt.h
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 // ***************************************************************************
10
11 #ifndef BAMTOOLS_GETOPT_H
12 #define BAMTOOLS_GETOPT_H
13
14 // C includes
15 #include <cassert>
16 #include <cstdlib>
17
18 // C++ includes
19 #include <iostream>
20 #include <map>
21 #include <string>
22 #include <vector>
23
24 namespace BamTools { 
25
26 class GetOpt {
27   
28     // ctors & dtor
29     public:
30       
31         // ctor: takes the 'standard' command line args (optional offset)
32         GetOpt(int argc, char* argv[], int offset = 0);
33         
34         // d-tor
35         ~GetOpt(void);
36   
37     // set rules for bare word arguments
38     public:
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); 
42         
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);
46       
47     // set rules for key=>value options  
48     public:
49         // add standard option with arguments ( -Wall, -O2, --type=foo )
50         void addOption(const char shortName, const std::string& longName, std::string* value);
51         
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);
56         
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   
61         
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);
64         
65     // set rules for on/off switch    
66     public:
67         // on/off switch ( --verbose --searchOnly ) only long names supported for now
68         void addSwitch(const std::string& longName, bool* ok);
69         
70     // parse and query methods
71     public:
72       
73         // get application name
74         const std::string& applicationName(void) const;
75       
76         // query if particular 'bare-word' argument is set
77         bool isSet(const std::string& name) const;
78       
79         // runs parser (does validation and assign values to arguments) 
80         // returns success/fail
81         bool parse(void);
82         
83         void print(void);
84         
85     // define Option-related types & enums
86     private:
87         enum OptionType { OptUnknown = 0
88                         , OptEnd
89                         , OptSwitch
90                         , OptArg1
91                         , OptOptional
92                         , OptRepeat
93                         , OptVariable
94                         };
95                         
96         // define Option 
97         struct Option {
98           
99             // ctor
100             Option(OptionType t = OptUnknown, const char shortName = 0, const std::string& longName = "")
101                 : Type(t)
102                 , ShortName(shortName)
103                 , LongName(longName)
104                 , BoolValue(0)
105             { }
106             
107             // data members
108             OptionType Type;
109             char ShortName;
110             std::string LongName;
111             union { 
112                 bool* BoolValue; 
113                 std::string* StringValue; 
114                 std::vector<std::string>* ListValue;
115             };
116             std::string Default;
117         };
118     
119     // internal methods
120     private:
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);
124         
125     // data members
126     private:
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;
131         
132         int m_numberRequiredArguments;
133         int m_numberOptionalArguments;
134         Option m_requiredArgument;
135         Option m_optionalArgument;
136         
137         int m_currentArgument;
138 };
139
140 inline
141 GetOpt::GetOpt(int argc, char* argv[], int offset) 
142 {
143     init(argc, argv, offset);
144 }
145
146 inline
147 GetOpt::~GetOpt(void) { }      
148
149 // add an optional 'bare word' argument (eg 'help')
150 // 'name' is not used on the command line, but for reporting
151 inline
152 void GetOpt::addOptionalArgument(const std::string& name, std::string* value) {
153   
154     Option opt( OptUnknown, 0, name );
155     opt.StringValue = value;
156     m_optionalArgument = opt;
157     ++m_numberOptionalArguments;
158     *value = std::string();
159 }
160
161 // add a required 'bare word' argument (eg input data file)
162 // 'name' is not used on the command line, but for reporting
163 inline
164 void GetOpt::addRequiredArgument(const std::string& name, std::string* value) {
165     
166     Option opt( OptUnknown, 0, name );
167     opt.StringValue = value;
168     m_requiredArgument = opt;
169     ++m_numberRequiredArguments;
170     *value = std::string();
171 }
172
173 // add standard option with arguments ( -Wall, -O2, --type=foo )
174 inline
175 void GetOpt::addOption(const char shortName, const std::string& longName, std::string* value) {
176     
177     Option opt( OptArg1, shortName, longName );
178     opt.StringValue = value;
179     saveOption(opt);
180     *value = std::string();
181 }
182
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
186 inline
187 void GetOpt::addOptionalOption(const char shortName, const std::string& longName, std::string* value, const std::string& defaultValue) {
188   
189     Option opt( OptOptional, shortName, longName );
190     opt.StringValue = value;
191     opt.Default = defaultValue;
192     saveOption(opt);
193     *value = std::string();
194 }
195
196 // long name only version
197 inline
198 void GetOpt::addOptionalOption(const std::string& longName, std::string* value, const std::string& defaultValue) {
199     addOptionalOption(0, longName, value, defaultValue);
200 }
201
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
205 inline
206 void GetOpt::addRepeatableOption(const char shortName, std::vector<std::string>* values) {
207   
208     Option opt( OptRepeat, shortName, std::string() );
209     opt.ListValue = values;
210     saveOption(opt);
211     *values = std::vector<std::string>();
212 }
213
214 // long name only version
215 inline
216 void GetOpt::addRepeatableOption(const std::string& longName, std::vector<std::string>* values) {
217   
218     Option opt( OptRepeat, 0, longName );
219     opt.ListValue = values;
220     saveOption(opt);
221     *values = std::vector<std::string>();
222 }
223
224 // add an option that takes a variable number of arguments ( --files f1 f2 f3 f4... )
225 inline
226 void GetOpt::addVariableLengthOption(const std::string& longName, std::vector<std::string>* values) {
227   
228     Option opt( OptVariable, 0, longName );
229     opt.ListValue = values;
230     saveOption(opt);
231     *values = std::vector<std::string>();
232 }
233
234 // on/off switch ( --verbose --searchOnly ) only long names supported for now
235 inline
236 void GetOpt::addSwitch(const std::string& longName, bool* ok) {
237
238     Option opt( OptSwitch, 0, longName );
239     opt.BoolValue = ok;
240     saveOption(opt);
241     *ok = false;
242 }
243
244 inline
245 const std::string& GetOpt::applicationName(void) const {
246     return m_appname;
247 }
248
249 inline
250 void GetOpt::init(int argc, char* argv[], int offset) {
251   
252     m_numberRequiredArguments = 0;
253     m_numberOptionalArguments = 0;
254     m_currentArgument         = 1;
255     
256     if ( argc > 0 ) {
257       
258         // store app name
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);
262         
263         // store remaining arguments from offset to end
264         for (int i = offset + 1; i < argc; ++i) {
265             m_args.push_back( argv[i] );
266         }
267         
268     } else {
269         std::cerr << "GetOpt ERROR: No arguments given." << std::endl;
270         exit(1);
271     }
272 }
273
274 // query if particular 'bare-word' argument is set
275 inline
276 bool GetOpt::isSet(const std::string& name) const {
277     return ( m_setOptions.find(name) != m_setOptions.end() );
278 }
279
280 // runs parser (does validation and assign values to arguments) 
281 // returns success/fail
282 inline
283 bool GetOpt::parse(void) {
284   
285     // initialize argument stack (reversed input args)
286     std::vector<std::string> argStack( m_args.rbegin(), m_args.rend() );
287     
288     // initialize state
289     enum State { StartingState, ExpectingState, OptionalState };
290     State state = StartingState;
291     
292     // initialize token types
293     enum TokenType { LongOpt, ShortOpt, Arg, End };
294     TokenType token = End;
295     TokenType currentType = End;
296     
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();
300     
301     // declare currentOption
302     Option currentOption;
303   
304     // we're going to fake an 'End' argument
305     bool isExtraLoopNeeded = true;
306     
307     // iterate through stack contents & do one extra loop for the fake 'End'
308     while ( !argStack.empty() || isExtraLoopNeeded ) {
309       
310         std::string arg;
311         std::string originalArg;  // store the original arg because we're going to mangle 'arg'
312         
313         // if contents on the arg stack
314         if ( !argStack.empty() ) {
315           
316             arg = argStack.back();
317             argStack.pop_back();
318             ++m_currentArgument;
319             originalArg = arg;
320           
321             // long option version
322             if ( arg.substr(0,2) == "--" ) {
323               
324                 // set token type
325                 token = LongOpt;
326               
327                 // strip the '--'
328                 arg = arg.substr(2);
329                 
330                 // make sure there's still somthing there
331                 if ( arg.empty() ) {
332                     std::cerr << "'--' feature is not supported, yet." << std::endl;
333                     exit(1);
334                 }
335                 
336                 // split any key=value style args
337                 size_t foundEqual = arg.find('=');
338                 if ( foundEqual != std::string::npos ) {
339                   
340                     // push value back onto stack
341                     argStack.push_back( arg.substr(foundEqual+1) );
342                     --m_currentArgument;
343                     
344                     // save key as current arg
345                     arg = arg.substr(0, foundEqual);
346                 }
347               
348             }
349             
350             // short option version
351             else if ( arg.at(0) == '-' ) {
352               
353                 // set token type
354                 token = ShortOpt;
355                 
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) );
359                     --m_currentArgument;
360                 }
361                 
362                 // strip the '-'
363                 arg = arg[1];
364             } 
365             
366             // bare-word argument
367             else { token = Arg; }
368         } 
369         
370         // in fake End iteration
371         else { token = End;  }
372       
373         // look up arg in list of known options, modify token type if necessary
374         Option opt;
375         if ( token != End ) {
376           
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) ) {
383                     opt = o;
384                     break; 
385                 } 
386             }
387             
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;
392                     return false;
393                 } else { 
394                   token = Arg; 
395                 }
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;
399                     return false;
400                 } else { 
401                   token = Arg; 
402                 } 
403             } 
404         } else { opt = Option(OptEnd); }
405         
406         
407          // interpret result
408          switch ( state ) {
409             
410             case ( StartingState ) :
411               
412                 if ( opt.Type == OptSwitch ) {
413                     setSwitch(opt);
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;
418                     currentOption = opt;
419                     currentType = token;
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;
424                     currentOption = opt;
425                     currentType = token;
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;
434                         } else {
435                             std::cerr << "Too many bare arguments" << std::endl;
436                             return false;
437                         }
438                     } 
439                     
440                     else if ( m_numberOptionalArguments > 0 ) {
441                         if ( (*m_optionalArgument.StringValue).empty() ) {
442                             *m_optionalArgument.StringValue = arg;
443                         } else {
444                             std::cerr << "Too many bare arguments" << std::endl;
445                             return false;
446                         }
447                     }
448                 } else {
449                     std::cerr << "GetOpt ERROR: Unhandled StartingState case: " << opt.Type << std::endl;
450                     exit(1);
451                 }
452                 
453                 break;
454               
455             case ( ExpectingState ) :
456              
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;
464                     } else {
465                         std::cerr << "GetOpt ERROR: Unhandled ExpectingState case: " << currentOption.Type << std::endl;
466                         exit(1);
467                     }
468                 } else {
469                     std::string name = (currentType == LongOpt) ? currentOption.LongName : (const char*)&currentOption.ShortName;
470                     std::cerr << "GetOpt ERROR: Expected an argument after option: " << name << std::endl;
471                     exit(1);
472                 }
473         
474                 break;
475                 
476             case ( OptionalState ) : 
477               
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
485                     } else {
486                         std::cerr << "GetOpt ERROR: Unhandled OptionalState case: " << currentOption.Type << std::endl;
487                         exit(1);
488                     }
489                 } else {
490                   
491                     // optional argument not specified
492                     if ( currentOption.Type == OptOptional ) {
493                         *currentOption.StringValue = currentOption.Default;
494                     }
495                     
496                     if ( token != End ) { 
497                         // re-evaluate current argument
498                         argStack.push_back( originalArg );
499                         --m_currentArgument;
500                     }
501                     
502                     state = StartingState;
503                 }
504         
505                 break;
506         }
507         
508         if ( token == End ) {
509             isExtraLoopNeeded = false;
510         }
511     }
512     
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;
516         return false;
517     }
518   
519     return true;
520 }
521
522 inline
523 void GetOpt::print(void) {
524  
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) << " ";
533     }
534     std::cout << std::endl;
535 }    
536     
537 inline
538 void GetOpt::saveOption(const Option& opt) {
539     // check for conflicts (duplicating options) ??
540     m_options.push_back(opt);
541
542
543 inline
544 void GetOpt::setSwitch(const Option& opt) {
545     assert( opt.Type == OptSwitch );
546     *opt.BoolValue =  true;
547 }
548
549 } // namespace BamTools 
550
551 #endif // BAMTOOLS_GETOPT_H