1 //==============================================================================
2 //
3 //  This file is part of GPSTk, the GPS Toolkit.
4 //
5 //  The GPSTk is free software; you can redistribute it and/or modify
6 //  it under the terms of the GNU Lesser General Public License as published
7 //  by the Free Software Foundation; either version 3.0 of the License, or
8 //  any later version.
9 //
10 //  The GPSTk is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU Lesser General Public License for more details.
14 //
15 //  You should have received a copy of the GNU Lesser General Public
16 //  License along with GPSTk; if not, write to the Free Software Foundation,
17 //  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
18 //
19 //  This software was developed by Applied Research Laboratories at the
20 //  University of Texas at Austin.
21 //  Copyright 2004-2020, The Board of Regents of The University of Texas System
22 //
23 //==============================================================================
24 
25 //==============================================================================
26 //
27 //  This software was developed by Applied Research Laboratories at the
28 //  University of Texas at Austin, under contract to an agency or agencies
29 //  within the U.S. Department of Defense. The U.S. Government retains all
30 //  rights to use, duplicate, distribute, disclose, or release this software.
31 //
32 //  Pursuant to DoD Directive 523024
33 //
34 //  DISTRIBUTION STATEMENT A: This software has been approved for public
35 //                            release, distribution is unlimited.
36 //
37 //==============================================================================
38 
39 /**
40  * @file CommandOption.cpp
41  * Command line options
42  */
43 
44 #include "CommandOption.hpp"
45 #include "CommandOptionParser.hpp"
46 #include "StringUtils.hpp"
47 
48 #include <sstream>
49 
50 using namespace std;
51 using namespace gpstk::StringUtils;
52 
53 namespace gpstk
54 {
55    CommandOptionVec defaultCommandOptionList;
56 
CommandOption(const CommandOption::CommandOptionFlag of,const CommandOption::CommandOptionType ot,const char shOpt,const std::string & loOpt,const std::string & desc,const bool req,CommandOptionVec & optVectorList)57    CommandOption::CommandOption(
58       const CommandOption::CommandOptionFlag of,
59       const CommandOption::CommandOptionType ot,
60       const char shOpt,
61       const std::string& loOpt,
62       const std::string& desc,
63       const bool req,
64       CommandOptionVec& optVectorList)
65          : optFlag(of),  optType(ot),
66            shortOpt(shOpt), longOpt(loOpt), description(desc),
67            required(req), count(0), maxCount(0), order(0), parser(NULL)
68    {
69       if (ot == CommandOption::stdType)
70       {
71          if ( (shOpt == 0) && (loOpt.size() == 0) )
72          {
73             InvalidParameter  exc("A short or long command option must be specified");
74             GPSTK_THROW(exc);
75          }
76             // if short option is specified, allow only printable, non-space characters
77          if ( (shOpt != 0) && !isgraph(shOpt) )
78          {
79             InvalidParameter  exc("Invalid short command option character");
80             GPSTK_THROW(exc);
81          }
82             // if long option is specified, allow only printable, non-space characters
83          for ( size_t i = longOpt.size(); i > 0; --i )
84          {
85             if ( !isgraph(longOpt[i - 1]) )
86             {
87                InvalidParameter  exc("Invalid long command option character");
88                GPSTK_THROW(exc);
89             }
90          }
91       }
92       optVectorList.push_back(this);
93    }
94 
95       // Prints out short options with a leading '-' and long ones with '--'.
96       // Puts a '|' between them if it has both.
getOptionString() const97    string CommandOption::getOptionString() const
98    {
99       string toReturn;
100       if (shortOpt != 0)
101       {
102          toReturn += string("-") + string(1, shortOpt);
103          if (!longOpt.empty())
104             toReturn += string(" | --") + longOpt;
105       }
106       else
107       {
108          toReturn += string("--") + longOpt;
109       }
110       return toReturn;
111    }
112 
113       // Prints out short options with a leading '-' and long ones with '--'.
114       // Puts a ',' between them if it has both.
getFullOptionString() const115    string CommandOption::getFullOptionString() const
116    {
117       string toReturn("  ");
118       if (shortOpt != 0)
119       {
120          toReturn += string("-") + string(1, shortOpt);
121          if (!longOpt.empty())
122          {
123             toReturn += string(", --") + longOpt;
124             if (optFlag == hasArgument)
125                toReturn += "=" + getArgString();
126          }
127          else
128          {
129             if (optFlag == hasArgument)
130                toReturn += "  " + getArgString();
131          }
132       }
133       else
134       {
135          toReturn += string("    --") + longOpt;
136          if (optFlag == hasArgument)
137             toReturn += "=" + getArgString();
138       }
139       return toReturn;
140    }
141 
142       // creates the struct option for getopt_long
toGetoptLongOption() const143    struct option CommandOption::toGetoptLongOption() const
144    {
145       struct option o = {longOpt.c_str(), optFlag, NULL, 0};
146       return o;
147    }
148 
149       // makes the string for getopt
toGetoptShortOption() const150    std::string CommandOption::toGetoptShortOption() const
151    {
152       std::string opt(1, shortOpt);
153       if (optFlag == hasArgument) opt += ":";
154       return opt;
155    }
156 
157       // get the order of the specified instance of this command option
getOrder(unsigned long idx) const158    unsigned long CommandOption::getOrder(unsigned long idx) const
159    {
160       if (order.size() == 0)
161          return 0;
162 
163       if (idx == (unsigned long)-1)
164          return order[order.size()-1];
165 
166       if (idx >= order.size())
167          return 0;
168 
169       return order[idx];
170    }
171 
172       // writes out the vector of values for this command option
dumpValue(std::ostream & out) const173    std::ostream& CommandOption::dumpValue(std::ostream& out) const
174    {
175       std::vector<std::string>::const_iterator itr = value.begin();
176       while(itr != value.end())
177       {
178          out << *itr << std::endl;
179          itr++;
180       }
181       return out;
182    }
183 
184       // returns a string like this:
185       //
186       //   -f | --foo  <arg>
187       //        this is the description
188       //
getDescription() const189    std::string CommandOption::getDescription() const
190    {
191       ostringstream out;
192          // do the option itself first
193       out << '\t';
194       if (shortOpt != 0)
195       {
196          out << '-' << shortOpt;
197          if (!longOpt.empty())
198             out << " | ";
199          else
200             out << '\t';
201       }
202       if (! longOpt.empty())
203       {
204          out << "--" << longOpt;
205       }
206       if (optFlag == hasArgument)
207       {
208          out << " " << getArgString();
209       }
210          // and the description goes on a new line
211       out << endl << prettyPrint(description,
212                                  "\n",
213                                  "                  ",
214                                  "               ");
215       if (maxCount != 0)
216       {
217          out << "\t\tUp to " << maxCount << " may be used on the command line."
218              << endl;
219       }
220       return out.str();
221    }
222 
223       // this checks if it expects number or string type arguments.
224       // it returns a string describing the error, if any.
checkArguments()225    string CommandOption::checkArguments()
226    {
227       if (required && (count == 0))
228          return "Required option " + getOptionString() + " was not found.";
229 
230       return string();
231    }
232 
checkArguments()233    string CommandOptionRest::checkArguments()
234    {
235       if (required && (count == 0))
236          return "Required trailing argument was not found.";
237 
238       return string();
239    }
240 
checkArguments()241    string CommandOptionWithNumberArg::checkArguments()
242    {
243       string errstr = CommandOption::checkArguments();
244 
245       if (!errstr.empty())
246          return errstr;
247 
248       vector<string>::size_type vecindex;
249       for(vecindex = 0; vecindex < value.size(); vecindex++)
250       {
251          if (!isDigitString(value[vecindex]))
252          {
253             string errstr("Argument for ");
254             errstr += getOptionString();
255             errstr += string(" should be a digit string.");
256             return errstr;
257          }
258       }
259 
260       return string();
261    }
262 
checkArguments()263    string CommandOptionWithDecimalArg::checkArguments()
264    {
265       string errstr = CommandOption::checkArguments();
266 
267       if (!errstr.empty())
268          return errstr;
269 
270       vector<string>::size_type vecindex;
271       for(vecindex = 0; vecindex < value.size(); vecindex++)
272       {
273          if (!isDecimalString(value[vecindex]))
274          {
275             string errstr("Argument for ");
276             errstr += getOptionString();
277             errstr += string(" should be a decimal string.");
278             return errstr;
279          }
280       }
281 
282       return string();
283    }
284 
checkArguments()285    string CommandOptionWithStringArg::checkArguments()
286    {
287       string errstr = CommandOption::checkArguments();
288 
289       if (!errstr.empty())
290          return errstr;
291 
292       vector<string>::size_type vecindex;
293       for(vecindex = 0; vecindex < value.size(); vecindex++)
294       {
295          if (!isAlphaString(value[vecindex]))
296          {
297             string errstr("Argument for ");
298             errstr += getOptionString();
299             errstr += string(" should be an alphabetic string.");
300             return errstr;
301          }
302       }
303       return errstr;
304    }
305 
checkArguments()306    string CommandOptionMutex::checkArguments()
307    {
308       if (doOneOfChecking)
309       {
310          string oo = CommandOptionOneOf::checkArguments();
311          if (oo != string())
312             return oo;
313       }
314 
315          // mutex doesn't call CommandOption::checkArguments because
316          // it uses "required" differently
317       string errstr("Only one of the following options may be specified: ");
318       int firstSpec = -1;
319       bool touched = false;
320 
321       for (size_t i = 0; i < optionVec.size(); i++)
322       {
323          CommandOption *opt = optionVec[i];
324 
325          if (i)
326             errstr += ", ";
327          errstr += opt->getOptionString();
328          if (opt->getCount())
329          {
330             if (firstSpec != -1)
331                touched = true;
332             else
333                firstSpec = i;
334          }
335       }
336 
337       if (touched)
338          return errstr;
339 
340       return string();
341    }
342 
addOption(CommandOption * opt)343    void CommandOptionNOf::addOption(CommandOption* opt)
344    {
345       if (NULL == opt)
346       {
347          InvalidParameter  exc("Invalid option address");
348          GPSTK_THROW(exc);
349       }
350       optionVec.push_back(opt);
351    }
352 
checkArguments()353    string CommandOptionNOf::checkArguments()
354    {
355          // n-of doesn't call CommandOption::checkArguments because
356          // it doesn't use "required"
357       string fewerrstr("At least " + StringUtils::asString(N));
358 
359       string manyerrstr("No more than " + StringUtils::asString(maxCount));
360       string errstr(" of the following options must be specified: ");
361 
362       bool found = false;
363       unsigned long n = 0;
364 
365       for (CommandOptionVec::size_type i = 0; i < optionVec.size(); i++)
366       {
367          n += optionVec[i]->getCount();
368          if (i > 0)
369             errstr += ", ";
370          errstr += optionVec[i]->getOptionString();
371       }
372 
373       if (n < N)
374          return fewerrstr + errstr;
375       if (n > maxCount)
376          return manyerrstr + errstr;
377 
378       return string();
379    }
380 
which() const381    std::vector<CommandOption*> CommandOptionNOf::which() const
382    {
383       std::vector<CommandOption*> rv;
384 
385       for (CommandOptionVec::size_type i = 0; i < optionVec.size(); i++)
386       {
387          if (optionVec[i]->getCount())
388          {
389             rv.push_back(optionVec[i]);
390          }
391       }
392 
393       return rv;
394    }
395 
addOption(CommandOption * opt)396    void CommandOptionOneOf::addOption(CommandOption* opt)
397    {
398       if (NULL == opt)
399       {
400          InvalidParameter  exc("Invalid option address");
401          GPSTK_THROW(exc);
402       }
403       optionVec.push_back(opt);
404    }
405 
checkArguments()406    string CommandOptionOneOf::checkArguments()
407    {
408          // one-of doesn't call CommandOption::checkArguments because
409          // it doesn't use "required"
410       string errstr("One of the following options must be specified: ");
411       bool found = false;
412 
413       for (size_t i = 0; i < optionVec.size(); i++)
414       {
415          if (optionVec[i]->getCount())
416             found = true;
417          if (i > 0)
418             errstr += ", ";
419          errstr += optionVec[i]->getOptionString();
420       }
421 
422       if (!found)
423          return errstr;
424 
425       return string();
426    }
427 
whichOne() const428    CommandOption* CommandOptionOneOf::whichOne() const
429    {
430       CommandOption *rv = NULL;
431 
432       for (size_t i = 0; i < optionVec.size(); i++)
433       {
434          if (optionVec[i]->getCount())
435          {
436             rv = optionVec[i];
437             break;
438          }
439       }
440 
441       return rv;
442    }
443 
checkArguments()444    string CommandOptionAllOf::checkArguments()
445    {
446       string errstr("The following options must be used together: ");
447       bool found = false, notFound = false;
448 
449       for (size_t i = 0; i < optionVec.size(); i++)
450       {
451          if (optionVec[i]->getCount())
452             found = true;
453          else
454             notFound = true;
455          if (i > 0)
456             errstr += ", ";
457          errstr += optionVec[i]->getOptionString();
458       }
459 
460       if (found && notFound)
461          return errstr;
462 
463       return string();
464    }
465 
getCount() const466    unsigned long CommandOptionAllOf::getCount() const
467    {
468       unsigned long rv = 0;
469       for (size_t i = 0; i < optionVec.size(); i++)
470       {
471          if (optionVec[i]->getCount() == 0)
472             return 0;
473          rv += optionVec[i]->getCount();
474       }
475       return rv;
476    }
477 
CommandOptionDependent(const CommandOption * parent,const CommandOption * child)478    CommandOptionDependent::CommandOptionDependent(
479       const CommandOption* parent,
480       const CommandOption* child)
481          : CommandOption(noArgument, metaType, 0, "", "")
482    {
483       if (NULL == parent)
484       {
485          InvalidParameter  exc("Invalid parent address");
486          GPSTK_THROW(exc);
487       }
488       if (NULL == child)
489       {
490          InvalidParameter  exc("Invalid child address");
491          GPSTK_THROW(exc);
492       }
493       requiree = parent;
494       requirer = child;
495    }
496 
checkArguments()497    string CommandOptionDependent::checkArguments()
498    {
499          // dependent doesn't call CommandOption::checkArguments because
500          // it doesn't use "required"
501       string errstr;
502 
503       if (!requiree)
504          errstr = "Null requiree (parent) for CommandOptionDependent";
505       if (!requirer)
506          errstr = "Null requirer (child) for CommandOptionDependent";
507 
508       if (requirer->getCount() && !requiree->getCount())
509          errstr = "Option " + requirer->getOptionString() + " requires " +
510             requiree->getOptionString();
511 
512       return errstr;
513    }
514 
getOptionString() const515    string CommandOptionGroupOr::getOptionString() const
516    {
517       string rv;
518       if (optionVec.size() > 1)
519          rv += "(";
520       for (size_t i = 0; i < optionVec.size(); i++)
521       {
522          if (i) rv += ",";
523          rv += optionVec[i]->getOptionString();
524       }
525       if (optionVec.size() > 1)
526          rv += ")";
527 
528       return rv;
529    }
530 
getCount() const531    unsigned long CommandOptionGroupOr::getCount() const
532    {
533       unsigned long rv = 0;
534       for (size_t i = 0; i < optionVec.size(); i++)
535          rv += optionVec[i]->getCount();
536 
537       return rv;
538    }
539 
getCount() const540    unsigned long CommandOptionGroupAnd::getCount() const
541    {
542       unsigned long rv = 0;
543       for (size_t i = 0; i < optionVec.size(); i++)
544       {
545          if (optionVec[i]->getCount() == 0)
546             return 0;
547          rv += optionVec[i]->getCount();
548       }
549       return rv;
550    }
551 
printHelp(std::ostream & out,bool pretty)552    void CommandOptionHelpUsage::printHelp(std::ostream& out, bool pretty)
553    {
554       GPSTK_ASSERT(parser != NULL);
555       if (getCount())
556          parser->displayUsage(out, pretty);
557    }
558 
559 } // namespace gpstk
560