ewmscp  ..
shellScriptOptionParser.cpp
Go to the documentation of this file.
1 /*
2  Option parser C++(11) library for parsing command line options
3  Copyright (C) 2016 Juergen Hannappel
4 
5  This program is free software: you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation, either version 3 of the License, or
8  (at your option) any later version.
9 
10  This program 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 General Public License for more details.
14 
15  You should have received a copy of the GNU General Public License
16  along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "OptionsChrono.h"
19 #include <limits>
20 #include <ratio>
21 #include <set>
22 #include <unistd.h>
23 
24 template <typename T> class arrayOption: public options::container<T> {
25  public:
26  template <class ... Types> arrayOption(Types ... args) :
27  options::container<T>(args...) {
28  };
29  void fWriteValue(std::ostream& aStream) const override {
30  aStream << "(";
31  for (const auto& item : *this) {
32  aStream << ' ';
33  using options::escapedIO::operator<<;
34  aStream << item;
35  }
36  aStream << ")";
37  };
38 };
39 
40 template <typename T> class mapOption: public options::map<T> {
41  public:
42  template <class ... Types> mapOption(Types ... args) :
43  options::map<T>(args...) {
44  };
45  void fWriteValue(std::ostream& aStream) const override {
46  aStream << "; unset " << this->fGetLongName() << "; declare -A " << this->fGetLongName() << "=(";
47  for (const auto& item : *this) {
48  aStream << " [" << item.first << "]=";
49  using options::escapedIO::operator<<;
50  aStream << item.second;
51  }
52  aStream << " )";
53  };
54 };
55 
56 template <typename T> class listOption: public options::container<T> {
57  public:
58  template <class ... Types> listOption(Types ... args) :
59  options::container<T>(args...) {
60  };
61  void fWriteValue(std::ostream& aStream) const override {
62  aStream << "\"";
63  bool start = true;
64  for (const auto& item : *this) {
65  if (!start) {
66  aStream << ' ';
67  }
68  start = false;
69  using options::escapedIO::operator<<;
70  aStream << item;
71  }
72  aStream << "\"";
73  };
74 };
75 
81 };
82 
83 template <typename T> options::base* fOptionFromStream(std::istream &aStream, T defaultValue, typeModifierType aAsWhat) {
84  char shortName;
85  std::string longName;
86  std::string description;
87 
88  aStream >> shortName;
89  aStream >> longName;
90  aStream.ignore(std::numeric_limits<std::streamsize>::max(), ' ');
91  std::getline(aStream, description);
92 
93  if (shortName == '-') {
94  shortName = '\0';
95  }
96  switch (aAsWhat) {
97  case kSimple:
98  return new options::single<T>(shortName, longName, description, defaultValue);
99  case kAsArray:
100  return new arrayOption<T>(shortName, longName, description);
101  case kAsMap:
102  return new mapOption<T>(shortName, longName, description);
103  case kAsList:
104  return new listOption<T>(shortName, longName, description);
105  }
106  return nullptr;
107 }
108 
109 
110 int main(int argc, const char *argv[]) {
111  std::string description;
112  if (isatty(0) != 1) {
113  while (std::cin.good()) {
114  std::string line;
115  if (std::cin.eof()) {
116  break;
117  }
118  std::getline(std::cin, line);
119  if (line.compare("options:") == 0) {
120  break;
121  }
122  description += line;
123  description += "\n";
124  }
125  }
126  if (description.empty() || description == "\n") {
127  std::cout << argv[0] << ": shell script option parser.\n"
128  "\treads description of options from stdandard input and writes shell\n"
129  "\tcommands that set variables to the parsed options to standard output\n"
130  "typical invocation looks like this:\n"
131  ". <(shellScriptOptionParser $\"(cd \"$(dirname \"$0\")\"; echo \"$(pwd)/$(basename \"$0\")\")\" \"$@\"<<EOF\n"
132  "description of script\n"
133  "options:\n"
134  "<options>\n"
135  "trailer:\n"
136  "trailing rest of explanation\n"
137  "EOF\n"
138  "test $? != 0 && echo exit\n"
139  ")\n"
140  "Option sytax is:\n"
141  "[export|array|map|list][positional number] type shortOpt longOpt descripton\n"
142  "\ttype may be one of 'int', 'uint', 'bool' or 'string'\n"
143  "\tfor (file)sizes the type 'size' which understands kMG.. postfixes\n"
144  "\tfor durations the type 'seconds' is provided\n"
145  "\tfor short durations the type 'milliseconds' is provided\n"
146  "\tfor very short durations the type 'microseconds' is provided\n"
147  "\tfor time stamps the type 'date' is provided with fractional seconds\n"
148  "\tfor time stamps the type 'idate' is provided with integer seconds\n"
149  "\tshortOpt is the one-letter variant, use '-' to have none\n"
150  "\tlongOpt is the long variant and the name of the shell variable\n"
151  "\tthe rest of the line is the description\n"
152  "\tif 'array' is set the option will be a shell array, which can be\n"
153  "\t expanded with \"${longOpt[@]}\" which will produce words preserving spaces\n"
154  "\tif 'map' is set the option will be a shell array, which can be\n"
155  "\t expanded with \"${longOpt[@]}\" which will produce words preserving spaces\n"
156  "\t other than the 'array' variant maps have string subscripts\n"
157  "\tif 'list' is set the option will be a list, i.e. a variable with the\n"
158  "\t values separated by spaces, i.e. values may not contain spaces\n"
159  "\tif 'export' is set the shell variable will be exported\n"
160  "\tif 'positional' is set the variable will be set as postional,\n"
161  "\t with 'number' defining the order in the positional parameter list.\n"
162  "\tif the next line starts with 'range' the values following are added\n"
163  "\tto the allowed value range of the option, many range lines may follow!\n"
164  "\tif only two are given they denote a true range in the closed interval\n"
165  "\tif the next line starts with 'default' a default value\n"
166  "\t (the rest of line) is set\n"
167  "\tThe keyword 'minusMinusSpecialTreatment' will put the parameters\n"
168  "\tfollowing '--' into the shell variable following that keyword\n"
169  "\tThe keyword 'noPath' clears the search path for config files\n"
170  "\tThe keyword 'path' adds the (escaped) rest of the line\n"
171  "\tto the search path for config files\n"
172  "\tThe keyword 'minUnusedParameters' sets the min number of params\n"
173  "\tThe keyword 'maxUnusedParameters' sets the max number of params\n";
174 
175  return (1);
176  }
177 
178  std::vector<options::base*> options;
179  std::set<const options::base*> exportedOptions;
180  std::string minusMinusSpecialTreatment = "";
181  std::vector<std::string> searchPath({"/etc/", "~/.", "~/.config/", "./."});
182 
183  unsigned int minUnusedParameters = 0;
184  unsigned int maxUnusedParameters = std::numeric_limits<unsigned int>::max();
185  {
186  options::parser throwAwayInstance("tAI1");
187  std::string keyWord;
188  bool exportNextOption = false;
189  typeModifierType nextOptionAsWhat = kSimple;
190  int nextOptionPositional = 0;
191  while (std::cin.good()) {
192  std::cin >> keyWord;
193  if (std::cin.eof()) {
194  break;
195  }
196  if (keyWord == "string") {
197  options.push_back(fOptionFromStream<std::string>(std::cin, "", nextOptionAsWhat));
198  } else if (keyWord == "int") {
199  options.push_back(fOptionFromStream<int>(std::cin, 0, nextOptionAsWhat));
200  } else if (keyWord == "uint") {
201  options.push_back(fOptionFromStream<unsigned int>(std::cin, 0, nextOptionAsWhat));
202  } else if (keyWord == "size") {
203  options.push_back(fOptionFromStream<options::postFixedNumber<size_t>>(std::cin, 0, nextOptionAsWhat));
204  } else if (keyWord == "bool") {
205  options.push_back(fOptionFromStream<bool>(std::cin, false, nextOptionAsWhat));
206  } else if (keyWord == "seconds") {
207  options.push_back(fOptionFromStream<std::chrono::duration<long long>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
208  } else if (keyWord == "milliseconds") {
209  options.push_back(fOptionFromStream<std::chrono::duration<long long, std::milli>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
210  } else if (keyWord == "microseconds") {
211  options.push_back(fOptionFromStream<std::chrono::duration<long long, std::micro>>(std::cin, std::chrono::seconds(1), nextOptionAsWhat));
212  } else if (keyWord == "date") {
213  options.push_back(fOptionFromStream<std::chrono::system_clock::time_point>(std::cin, std::chrono::system_clock::now(), nextOptionAsWhat));
214  } else if (keyWord == "idate") {
215  options.push_back(fOptionFromStream<std::chrono::system_clock::time_point>(std::cin, std::chrono::system_clock::now(), nextOptionAsWhat));
217  if (opt) {
218  opt->fSetValuePrinter([](std::ostream & aStream, const std::chrono::system_clock::time_point & aValue)->void {aStream << std::chrono::duration_cast<std::chrono::duration<long>>(aValue.time_since_epoch()).count();});
219  }
220  } else if (keyWord == "range") {
221  options.back()->fAddToRangeFromStream(std::cin);
222  } else if (keyWord == "default") {
223  options.back()->fAddDefaultFromStream(std::cin);
224  } else if (keyWord == "export") {
225  exportNextOption = true;
226  continue;
227  } else if (keyWord == "array") {
228  nextOptionAsWhat = kAsArray;
229  continue;
230  } else if (keyWord == "map") {
231  nextOptionAsWhat = kAsMap;
232  continue;
233  } else if (keyWord == "list") {
234  nextOptionAsWhat = kAsList;
235  continue;
236  } else if (keyWord == "positional") {
237  std::cin >> nextOptionPositional;
238  continue;
239  } else if (keyWord == "minusMinusSpecialTreatment") {
240  std::cin >> minusMinusSpecialTreatment;
241  continue;
242  } else if (keyWord == "minUnusedParameters") {
243  std::cin >> minUnusedParameters;
244  continue;
245  } else if (keyWord == "maxUnusedParameters") {
246  std::cin >> maxUnusedParameters;
247  continue;
248  } else if (keyWord == "noPath") {
249  searchPath.clear();
250  continue;
251  } else if (keyWord == "path") {
252  std::string buffer;
253  using options::escapedIO::operator>>;
254  std::cin >> buffer;
255  searchPath.push_back(buffer);
256  } else if (keyWord == "trailer:") {
257  break;
258  } else {
259  std::cerr << "illegal option type '" << keyWord << "', giving up" << std::endl;
260  return 1;
261  }
262  if (nextOptionPositional != 0) {
263  options::internal::positional_base(nextOptionPositional, options.back());
264  }
265  if (exportNextOption) {
266  exportedOptions.insert(options.back());
267  }
268  exportNextOption = false;
269  nextOptionAsWhat = kSimple;
270  nextOptionPositional = 0;
271  }
272  }
273  std::string trailer;
274  while (std::cin.good()) {
275  std::string line;
276  if (std::cin.eof()) {
277  break;
278  }
279  std::getline(std::cin, line);
280  trailer += line;
281  trailer += "\n";
282  }
283 
284  options::parser parser(description, trailer, searchPath);
285  parser.fSetMessageStream(&std::cerr);
286  parser.fSetHelpReturnValue(1);
287  parser.fSetExecutableName(argv[1]);
288  if (! minusMinusSpecialTreatment.empty()) {
290  }
291 
292  auto unusedOptions = parser.fParse(argc - 1, argv + 1);
293 
294  if (unusedOptions.size() < minUnusedParameters ||
295  unusedOptions.size() > maxUnusedParameters) {
296  std::cerr << "illegal number of non-option parameters " << unusedOptions.size() << ", must be between " << minUnusedParameters << " and " << maxUnusedParameters << std::endl;
297  parser.fHelp();
298  return (1);
299  }
300 
301 
302  for (auto option : options) {
303  if (exportedOptions.find(option) != exportedOptions.end()) {
304  std::cout << "export ";
305  }
306  std::cout << option->fGetLongName() << "=";
307  option->fWriteValue(std::cout);
308  std::cout << "\n";
309  }
310  std::cout << "shift $#\n";
311  if (unusedOptions.empty() == false) {
312  std::cout << "set --";
313  for (auto & unusedOption : unusedOptions) {
314  std::cout << " " << unusedOption;
315  }
316  std::cout << "\n";
317  }
318  if (parser.fGetStuffAfterMinusMinus().empty() == false) {
319  std::cout << minusMinusSpecialTreatment << "=\"";
320  for (auto it = parser.fGetStuffAfterMinusMinus().begin(); it != parser.fGetStuffAfterMinusMinus().end(); ++it) {
321  if (it != parser.fGetStuffAfterMinusMinus().begin()) {
322  std::cout << " ";
323  }
324  std::cout << *it;
325  }
326  std::cout << "\"\n";
327  }
328 
329  return 0;
330 }
options::parser::fHelp
void fHelp()
print help, normally automatically called by the –help option or in case of problems.
Definition: Options.cpp:724
kAsMap
@ kAsMap
Definition: shellScriptOptionParser.cpp:79
options::parser
class that contains the parser, i.e. does that option handling
Definition: Options.h:363
options::single
generic option class with any type that can be used with std::istream and std::ostream
Definition: Options.h:533
options::base::fGetLongName
const std::string & fGetLongName() const
returns long name of option, usually only for internal use.
Definition: Options.h:273
options::parser::fParse
const std::vector< std::string > & fParse(int argc, const char *argv[])
parse the options on the command line
Definition: Options.cpp:168
typeModifierType
typeModifierType
Definition: shellScriptOptionParser.cpp:76
options::container< T >::container
container(char aShortName, const std::string &aLongName, const std::string &aExplanation, std::initializer_list< typename std::vector< T > ::value_type > aDefault={})
Definition: Options.h:791
arrayOption
Definition: shellScriptOptionParser.cpp:24
options::base
base class for options
Definition: Options.h:193
kAsList
@ kAsList
Definition: shellScriptOptionParser.cpp:80
options
Definition: Options.h:33
main
int main(int argc, const char *argv[])
Definition: shellScriptOptionParser.cpp:110
options::map
template for map-based options.
Definition: Options.h:671
options::parser::fGetStuffAfterMinusMinus
const std::vector< std::string > & fGetStuffAfterMinusMinus()
get trailong part of command line as a vector od strings, requires that fSetMinusMinusStartsExtraList...
Definition: Options.h:433
mapOption::fWriteValue
void fWriteValue(std::ostream &aStream) const override
write textual representation of value to a std::ostream
Definition: shellScriptOptionParser.cpp:45
options::valuePrinter::fSetValuePrinter
virtual void fSetValuePrinter(valuePrinterType aValuePrinter)
Definition: Options.h:182
throttle::start
static auto start
Definition: throttle.h:10
options::container
template for container-based options.
Definition: Options.h:789
OptionsChrono.h
options::valuePrinter< std::chrono::system_clock::time_point >
options::internal::positional_base
Definition: Options.h:345
options::parser::fSetHelpReturnValue
void fSetHelpReturnValue(int aValue)
Definition: Options.cpp:124
kSimple
@ kSimple
Definition: shellScriptOptionParser.cpp:77
listOption::listOption
listOption(Types ... args)
Definition: shellScriptOptionParser.cpp:58
options::map< T >::map
map(char aShortName, const std::string &aLongName, const std::string &aExplanation, std::initializer_list< typename std::map< std::string, T > ::value_type > aDefault={})
Definition: Options.h:673
options::parser::fSetMinusMinusStartsExtraList
void fSetMinusMinusStartsExtraList()
switch on use of – to separate a trailer on the command line that is not to be parsed
Definition: Options.cpp:372
mapOption
Definition: shellScriptOptionParser.cpp:40
kAsArray
@ kAsArray
Definition: shellScriptOptionParser.cpp:78
arrayOption::fWriteValue
void fWriteValue(std::ostream &aStream) const override
write textual representation of value to a std::ostream
Definition: shellScriptOptionParser.cpp:29
arrayOption::arrayOption
arrayOption(Types ... args)
Definition: shellScriptOptionParser.cpp:26
mapOption::mapOption
mapOption(Types ... args)
Definition: shellScriptOptionParser.cpp:42
listOption
Definition: shellScriptOptionParser.cpp:56
options::parser::fSetMessageStream
void fSetMessageStream(std::ostream *aStream)
Definition: Options.cpp:115
options::postFixedNumber
Definition: Options.h:104
fOptionFromStream
options::base * fOptionFromStream(std::istream &aStream, T defaultValue, typeModifierType aAsWhat)
Definition: shellScriptOptionParser.cpp:83
options::parser::fSetExecutableName
void fSetExecutableName(const char *aName)
Definition: Options.cpp:131
listOption::fWriteValue
void fWriteValue(std::ostream &aStream) const override
write textual representation of value to a std::ostream
Definition: shellScriptOptionParser.cpp:61