1 /* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield
2  *
3  * This library is open source and may be redistributed and/or modified under
4  * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
5  * (at your option) any later version.  The full license is in LICENSE file
6  * included with this distribution, and on the openscenegraph.org website.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  * OpenSceneGraph Public License for more details.
12 */
13 
14 #include <stdlib.h>
15 #include <string.h>
16 
17 #include <osg/ArgumentParser>
18 #include <osg/ApplicationUsage>
19 #include <osg/Math>
20 #include <osg/Notify>
21 
22 #include <set>
23 #include <iostream>
24 
25 using namespace osg;
26 
isOption(const char * str)27 bool ArgumentParser::isOption(const char* str)
28 {
29     return str && str[0]=='-';
30 }
31 
isString(const char * str)32 bool ArgumentParser::isString(const char* str)
33 {
34     if (!str) return false;
35     return true;
36     //return !isOption(str);
37 }
38 
isBool(const char * str)39 bool ArgumentParser::isBool(const char* str)
40 {
41     if (!str) return false;
42 
43     return (strcmp(str,"True")==0 || strcmp(str,"true")==0 || strcmp(str,"TRUE")==0 ||
44             strcmp(str,"False")==0 || strcmp(str,"false")==0 || strcmp(str,"FALSE")==0 ||
45             strcmp(str,"0")==0 || strcmp(str,"1")==0);
46 }
47 
isNumber(const char * str)48 bool ArgumentParser::isNumber(const char* str)
49 {
50     if (!str) return false;
51 
52     bool hadPlusMinus = false;
53     bool hadDecimalPlace = false;
54     bool hadExponent = false;
55     bool couldBeInt = true;
56     bool couldBeFloat = true;
57     int noZeroToNine = 0;
58 
59     const char* ptr = str;
60 
61     // check if could be a hex number.
62     if (strncmp(ptr,"0x",2)==0)
63     {
64         // skip over leading 0x, and then go through rest of string
65         // checking to make sure all values are 0...9 or a..f.
66         ptr+=2;
67         while (
68                *ptr!=0 &&
69                ((*ptr>='0' && *ptr<='9') ||
70                 (*ptr>='a' && *ptr<='f') ||
71                 (*ptr>='A' && *ptr<='F'))
72               )
73         {
74             ++ptr;
75         }
76 
77         // got to end of string without failure, therefore must be a hex integer.
78         if (*ptr==0) return true;
79     }
80 
81     ptr = str;
82 
83     // check if a float or an int.
84     while (*ptr!=0 && couldBeFloat)
85     {
86         if (*ptr=='+' || *ptr=='-')
87         {
88             if (hadPlusMinus)
89             {
90                 couldBeInt = false;
91                 couldBeFloat = false;
92             } else hadPlusMinus = true;
93         }
94         else if (*ptr>='0' && *ptr<='9')
95         {
96             noZeroToNine++;
97         }
98         else if (*ptr=='.')
99         {
100             if (hadDecimalPlace)
101             {
102                 couldBeInt = false;
103                 couldBeFloat = false;
104             }
105             else
106             {
107                 hadDecimalPlace = true;
108                 couldBeInt = false;
109             }
110         }
111         else if (*ptr=='e' || *ptr=='E')
112         {
113             if (hadExponent || noZeroToNine==0)
114             {
115                 couldBeInt = false;
116                 couldBeFloat = false;
117             }
118             else
119             {
120                 hadExponent = true;
121                 couldBeInt = false;
122                 hadDecimalPlace = false;
123                 hadPlusMinus = false;
124                 noZeroToNine=0;
125             }
126         }
127         else
128         {
129             couldBeInt = false;
130             couldBeFloat = false;
131         }
132         ++ptr;
133     }
134 
135     if (couldBeInt && noZeroToNine>0) return true;
136     if (couldBeFloat && noZeroToNine>0) return true;
137 
138     return false;
139 
140 }
141 
valid(const char * str) const142 bool ArgumentParser::Parameter::valid(const char* str) const
143 {
144     switch(_type)
145     {
146     case Parameter::BOOL_PARAMETER:         return isBool(str); break;
147     case Parameter::FLOAT_PARAMETER:        return isNumber(str); break;
148     case Parameter::DOUBLE_PARAMETER:       return isNumber(str); break;
149     case Parameter::INT_PARAMETER:          return isNumber(str); break;
150     case Parameter::UNSIGNED_INT_PARAMETER: return isNumber(str); break;
151     case Parameter::STRING_PARAMETER:       return isString(str); break;
152     }
153     return false;
154 }
155 
assign(const char * str)156 bool ArgumentParser::Parameter::assign(const char* str)
157 {
158     if (valid(str))
159     {
160         switch(_type)
161         {
162         case Parameter::BOOL_PARAMETER:
163         {
164             *_value._bool =  (strcmp(str,"True")==0 || strcmp(str,"true")==0 || strcmp(str,"TRUE")==0);
165             break;
166         }
167         case Parameter::FLOAT_PARAMETER:        *_value._float = osg::asciiToFloat(str); break;
168         case Parameter::DOUBLE_PARAMETER:       *_value._double = osg::asciiToDouble(str); break;
169         case Parameter::INT_PARAMETER:          *_value._int = atoi(str); break;
170         case Parameter::UNSIGNED_INT_PARAMETER: *_value._uint = atoi(str); break;
171         case Parameter::STRING_PARAMETER:       *_value._string = str; break;
172         }
173         return true;
174     }
175     else
176     {
177         return false;
178     }
179 }
180 
181 
182 
ArgumentParser(int * argc,char ** argv)183 ArgumentParser::ArgumentParser(int* argc,char **argv):
184     _argc(argc),
185     _argv(argv),
186     _usage(ApplicationUsage::instance())
187 {
188 #ifdef __APPLE__
189     //On OSX, any -psn arguments need to be removed because they will
190     // confuse the application. -psn plus a concatenated argument are
191     // passed by the finder to application bundles
192     for(int pos=1;pos<this->argc();++pos)
193     {
194         if (std::string(_argv[pos]).compare(0, 4, std::string("-psn")) == 0)
195         {
196             remove(pos, 1);
197         }
198     }
199 #endif
200 
201 #ifdef WIN32
202     // Remove linefeed from last argument if it exist
203     char* lastline = argc==0 ? 0 : _argv[*argc-1];
204     if (lastline)
205     {
206         int len = strlen(lastline);
207         if (len>0 && lastline[len-1] == '\n') lastline[len-1]= '\0';
208     }
209 #endif
210 }
211 
getApplicationName() const212 std::string ArgumentParser::getApplicationName() const
213 {
214     if (_argc && *_argc>0 ) return std::string(_argv[0]);
215     return "";
216 }
217 
218 
isOption(int pos) const219 bool ArgumentParser::isOption(int pos) const
220 {
221     return pos<*_argc && isOption(_argv[pos]);
222 }
223 
isString(int pos) const224 bool ArgumentParser::isString(int pos) const
225 {
226     return pos < *_argc && isString(_argv[pos]);
227 }
228 
isNumber(int pos) const229 bool ArgumentParser::isNumber(int pos) const
230 {
231     return pos < *_argc && isNumber(_argv[pos]);
232 }
233 
234 
find(const std::string & str) const235 int ArgumentParser::find(const std::string& str) const
236 {
237     for(int pos=1;pos<*_argc;++pos)
238     {
239         if (str==_argv[pos])
240         {
241             return pos;
242         }
243     }
244     return -1;
245 }
246 
match(int pos,const std::string & str) const247 bool ArgumentParser::match(int pos, const std::string& str) const
248 {
249     return pos<*_argc && str==_argv[pos];
250 }
251 
252 
containsOptions() const253 bool ArgumentParser::containsOptions() const
254 {
255     for(int pos=1;pos<*_argc;++pos)
256     {
257         if (isOption(pos)) return true;
258     }
259     return false;
260 }
261 
262 
remove(int pos,int num)263 void ArgumentParser::remove(int pos,int num)
264 {
265     if (num==0) return;
266 
267     for(;pos+num<*_argc;++pos)
268     {
269         _argv[pos]=_argv[pos+num];
270     }
271     for(;pos<*_argc;++pos)
272     {
273         _argv[pos]=0;
274     }
275     *_argc-=num;
276 }
277 
read(const std::string & str)278 bool ArgumentParser::read(const std::string& str)
279 {
280     int pos=find(str);
281     if (pos<=0) return false;
282     remove(pos);
283     return true;
284 }
285 
read(const std::string & str,Parameter value1)286 bool ArgumentParser::read(const std::string& str, Parameter value1)
287 {
288     int pos=find(str);
289     if (pos<=0) return false;
290     return read(pos,str,value1);
291 }
292 
read(const std::string & str,Parameter value1,Parameter value2)293 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2)
294 {
295     int pos=find(str);
296     if (pos<=0) return false;
297     return read(pos,str,value1, value2);
298 }
299 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3)300 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3)
301 {
302     int pos=find(str);
303     if (pos<=0) return false;
304     return read(pos,str,value1, value2, value3);
305 }
306 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4)307 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4)
308 {
309     int pos=find(str);
310     if (pos<=0) return false;
311     return read(pos,str,value1, value2, value3, value4);
312 }
313 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5)314 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5)
315 {
316     int pos=find(str);
317     if (pos<=0) return false;
318     return read(pos,str,value1, value2, value3, value4, value5);
319 }
320 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6)321 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5, Parameter value6)
322 {
323     int pos=find(str);
324     if (pos<=0) return false;
325     return read(pos,str,value1, value2, value3, value4, value5, value6);
326 }
327 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6,Parameter value7)328 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5, Parameter value6, Parameter value7)
329 {
330     int pos=find(str);
331     if (pos<=0) return false;
332     return read(pos,str,value1, value2, value3, value4, value5, value6, value7);
333 }
334 
read(const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6,Parameter value7,Parameter value8)335 bool ArgumentParser::read(const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5, Parameter value6, Parameter value7, Parameter value8)
336 {
337     int pos=find(str);
338     if (pos<=0) return false;
339     return read(pos,str,value1, value2, value3, value4, value5, value6, value7, value8);
340 }
341 
342 /** if the argument value at the position pos matches specified string, and subsequent
343   * Parameters are also matched then set the Parameter values and remove the from the list of arguments.*/
read(int pos,const std::string & str)344 bool ArgumentParser::read(int pos, const std::string& str)
345 {
346     if (match(pos,str))
347     {
348         remove(pos,1);
349         return true;
350     }
351     return false;
352 }
353 
read(int pos,const std::string & str,Parameter value1)354 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1)
355 {
356     if (match(pos,str))
357     {
358         if ((pos+1)<*_argc)
359         {
360             if (value1.valid(_argv[pos+1]))
361             {
362                 value1.assign(_argv[pos+1]);
363                 remove(pos,2);
364                 return true;
365             }
366             reportError("argument to `"+str+"` is not valid");
367             return false;
368         }
369         reportError("argument to `"+str+"` is missing");
370         return false;
371     }
372     return false;
373 }
374 
read(int pos,const std::string & str,Parameter value1,Parameter value2)375 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2)
376 {
377     if (match(pos,str))
378     {
379         if ((pos+2)<*_argc)
380         {
381             if (value1.valid(_argv[pos+1]) &&
382                 value2.valid(_argv[pos+2]))
383             {
384                 value1.assign(_argv[pos+1]);
385                 value2.assign(_argv[pos+2]);
386                 remove(pos,3);
387                 return true;
388             }
389             reportError("argument to `"+str+"` is not valid");
390             return false;
391         }
392         reportError("argument to `"+str+"` is missing");
393         return false;
394     }
395     return false;
396 }
397 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3)398 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3)
399 {
400     if (match(pos,str))
401     {
402         if ((pos+3)<*_argc)
403         {
404             if (value1.valid(_argv[pos+1]) &&
405                 value2.valid(_argv[pos+2]) &&
406                 value3.valid(_argv[pos+3]))
407             {
408                 value1.assign(_argv[pos+1]);
409                 value2.assign(_argv[pos+2]);
410                 value3.assign(_argv[pos+3]);
411                 remove(pos,4);
412                 return true;
413             }
414             reportError("argument to `"+str+"` is not valid");
415             return false;
416         }
417         reportError("argument to `"+str+"` is missing");
418         return false;
419     }
420     return false;
421 }
422 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4)423 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4)
424 {
425     if (match(pos,str))
426     {
427         if ((pos+4)<*_argc)
428         {
429             if (value1.valid(_argv[pos+1]) &&
430                 value2.valid(_argv[pos+2]) &&
431                 value3.valid(_argv[pos+3]) &&
432                 value4.valid(_argv[pos+4]))
433             {
434                 value1.assign(_argv[pos+1]);
435                 value2.assign(_argv[pos+2]);
436                 value3.assign(_argv[pos+3]);
437                 value4.assign(_argv[pos+4]);
438                 remove(pos,5);
439                 return true;
440             }
441             reportError("argument to `"+str+"` is not valid");
442             return false;
443         }
444         reportError("argument to `"+str+"` is missing");
445         return false;
446     }
447     return false;
448 }
449 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5)450 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5)
451 {
452     if (match(pos,str))
453     {
454         if ((pos+5)<*_argc)
455         {
456             if (value1.valid(_argv[pos+1]) &&
457                 value2.valid(_argv[pos+2]) &&
458                 value3.valid(_argv[pos+3]) &&
459                 value4.valid(_argv[pos+4]) &&
460                 value5.valid(_argv[pos+5]))
461             {
462                 value1.assign(_argv[pos+1]);
463                 value2.assign(_argv[pos+2]);
464                 value3.assign(_argv[pos+3]);
465                 value4.assign(_argv[pos+4]);
466                 value5.assign(_argv[pos+5]);
467                 remove(pos,6);
468                 return true;
469             }
470             reportError("argument to `"+str+"` is not valid");
471             return false;
472         }
473         reportError("argument to `"+str+"` is missing");
474         return false;
475     }
476     return false;
477 }
478 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6)479 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5, Parameter value6)
480 {
481     if (match(pos,str))
482     {
483         if ((pos+6)<*_argc)
484         {
485             if (value1.valid(_argv[pos+1]) &&
486                 value2.valid(_argv[pos+2]) &&
487                 value3.valid(_argv[pos+3]) &&
488                 value4.valid(_argv[pos+4]) &&
489                 value5.valid(_argv[pos+5]) &&
490                 value6.valid(_argv[pos+6]))
491             {
492                 value1.assign(_argv[pos+1]);
493                 value2.assign(_argv[pos+2]);
494                 value3.assign(_argv[pos+3]);
495                 value4.assign(_argv[pos+4]);
496                 value5.assign(_argv[pos+5]);
497                 value6.assign(_argv[pos+6]);
498                 remove(pos,7);
499                 return true;
500             }
501             reportError("argument to `"+str+"` is not valid");
502             return false;
503         }
504         reportError("argument to `"+str+"` is missing");
505         return false;
506     }
507     return false;
508 }
509 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6,Parameter value7)510 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5,  Parameter value6,  Parameter value7)
511 {
512     if (match(pos,str))
513     {
514         if ((pos+7)<*_argc)
515         {
516             if (value1.valid(_argv[pos+1]) &&
517                 value2.valid(_argv[pos+2]) &&
518                 value3.valid(_argv[pos+3]) &&
519                 value4.valid(_argv[pos+4]) &&
520                 value5.valid(_argv[pos+5]) &&
521                 value6.valid(_argv[pos+6]) &&
522                 value7.valid(_argv[pos+7]))
523             {
524                 value1.assign(_argv[pos+1]);
525                 value2.assign(_argv[pos+2]);
526                 value3.assign(_argv[pos+3]);
527                 value4.assign(_argv[pos+4]);
528                 value5.assign(_argv[pos+5]);
529                 value6.assign(_argv[pos+6]);
530                 value7.assign(_argv[pos+7]);
531                 remove(pos,8);
532                 return true;
533             }
534             reportError("argument to `"+str+"` is not valid");
535             return false;
536         }
537         reportError("argument to `"+str+"` is missing");
538         return false;
539     }
540     return false;
541 }
542 
read(int pos,const std::string & str,Parameter value1,Parameter value2,Parameter value3,Parameter value4,Parameter value5,Parameter value6,Parameter value7,Parameter value8)543 bool ArgumentParser::read(int pos, const std::string& str, Parameter value1, Parameter value2, Parameter value3, Parameter value4, Parameter value5,  Parameter value6,  Parameter value7,  Parameter value8)
544 {
545     if (match(pos,str))
546     {
547         if ((pos+8)<*_argc)
548         {
549             if (value1.valid(_argv[pos+1]) &&
550                 value2.valid(_argv[pos+2]) &&
551                 value3.valid(_argv[pos+3]) &&
552                 value4.valid(_argv[pos+4]) &&
553                 value5.valid(_argv[pos+5]) &&
554                 value6.valid(_argv[pos+6]) &&
555                 value7.valid(_argv[pos+7]) &&
556                 value8.valid(_argv[pos+8]))
557             {
558                 value1.assign(_argv[pos+1]);
559                 value2.assign(_argv[pos+2]);
560                 value3.assign(_argv[pos+3]);
561                 value4.assign(_argv[pos+4]);
562                 value5.assign(_argv[pos+5]);
563                 value6.assign(_argv[pos+6]);
564                 value7.assign(_argv[pos+7]);
565                 value8.assign(_argv[pos+8]);
566                 remove(pos,9);
567                 return true;
568             }
569             reportError("argument to `"+str+"` is not valid");
570             return false;
571         }
572         reportError("argument to `"+str+"` is missing");
573         return false;
574     }
575     return false;
576 }
577 
errors(ErrorSeverity severity) const578 bool ArgumentParser::errors(ErrorSeverity severity) const
579 {
580     for(ErrorMessageMap::const_iterator itr=_errorMessageMap.begin();
581         itr!=_errorMessageMap.end();
582         ++itr)
583     {
584         if (itr->second>=severity) return true;
585     }
586     return false;
587 }
588 
reportError(const std::string & message,ErrorSeverity severity)589 void ArgumentParser::reportError(const std::string& message,ErrorSeverity severity)
590 {
591     _errorMessageMap[message]=severity;
592 }
593 
reportRemainingOptionsAsUnrecognized(ErrorSeverity severity)594 void ArgumentParser::reportRemainingOptionsAsUnrecognized(ErrorSeverity severity)
595 {
596     std::set<std::string> options;
597     if (_usage.valid())
598     {
599         // parse the usage options to get all the option that the application can potential handle.
600         for(ApplicationUsage::UsageMap::const_iterator itr=_usage->getCommandLineOptions().begin();
601             itr!=_usage->getCommandLineOptions().end();
602             ++itr)
603         {
604             const std::string& option = itr->first;
605             std::string::size_type prevpos = 0, pos = 0;
606             while ((pos=option.find(' ',prevpos))!=std::string::npos)
607             {
608                 if (option[prevpos]=='-')
609                 {
610                     options.insert(std::string(option,prevpos,pos-prevpos));
611                 }
612                 prevpos=pos+1;
613             }
614             if (option[prevpos]=='-')
615             {
616 
617                 options.insert(std::string(option,prevpos,std::string::npos));
618             }
619         }
620 
621     }
622 
623     for(int pos=1;pos<argc();++pos)
624     {
625         // if an option and haven't been previous querried for report as unrecognized.
626         if (isOption(pos) && options.find(_argv[pos])==options.end())
627         {
628             reportError(std::string("unrecognized option ")+std::string(_argv[pos]),severity);
629         }
630     }
631 }
writeErrorMessages(std::ostream & output,ErrorSeverity severity)632 void ArgumentParser::writeErrorMessages(std::ostream& output,ErrorSeverity severity)
633 {
634     for(ErrorMessageMap::iterator itr=_errorMessageMap.begin();
635         itr!=_errorMessageMap.end();
636         ++itr)
637     {
638         if (itr->second>=severity)
639         {
640             output<< getApplicationName() << ": " << itr->first << std::endl;
641         }
642     }
643 }
644 
readHelpType()645 ApplicationUsage::Type ArgumentParser::readHelpType()
646 {
647     getApplicationUsage()->addCommandLineOption("-h or --help","Display command line parameters");
648     getApplicationUsage()->addCommandLineOption("--help-env","Display environmental variables available");
649     getApplicationUsage()->addCommandLineOption("--help-keys","Display keyboard & mouse bindings available");
650     getApplicationUsage()->addCommandLineOption("--help-all","Display all command line, env vars and keyboard & mouse bindings.");
651 
652     // if user request help write it out to cout.
653     if (read("--help-all"))             return ApplicationUsage::HELP_ALL;
654     if (read("-h") || read("--help"))   return ApplicationUsage::COMMAND_LINE_OPTION;
655     if (read("--help-env"))             return ApplicationUsage::ENVIRONMENTAL_VARIABLE;
656     if (read("--help-keys"))            return ApplicationUsage::KEYBOARD_MOUSE_BINDING;
657 
658     return ApplicationUsage::NO_HELP;
659 }
660