HEVC Test Model (HM)  HM-16.18
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
program_options_lite.cpp
Go to the documentation of this file.
1 /* The copyright in this software is being made available under the BSD
2  * License, included below. This software may be subject to other third party
3  * and contributor rights, including patent rights, and no such rights are
4  * granted under this license.
5  *
6  * Copyright (c) 2010-2017, ITU/ISO/IEC
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions are met:
11  *
12  * * Redistributions of source code must retain the above copyright notice,
13  * this list of conditions and the following disclaimer.
14  * * Redistributions in binary form must reproduce the above copyright notice,
15  * this list of conditions and the following disclaimer in the documentation
16  * and/or other materials provided with the distribution.
17  * * Neither the name of the ITU/ISO/IEC nor the names of its contributors may
18  * be used to endorse or promote products derived from this software without
19  * specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
25  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
31  * THE POSSIBILITY OF SUCH DAMAGE.
32  */
33 #include <stdlib.h>
34 #include <iostream>
35 #include <fstream>
36 #include <sstream>
37 #include <string>
38 #include <list>
39 #include <map>
40 #include <algorithm>
41 #include "program_options_lite.h"
42 
43 using namespace std;
44 
47 
48 namespace df
49 {
50  namespace program_options_lite
51  {
53 
54  ostream& ErrorReporter::error(const string& where)
55  {
56  is_errored = 1;
57  cerr << where << " error: ";
58  return cerr;
59  }
60 
61  ostream& ErrorReporter::warn(const string& where)
62  {
63  cerr << where << " warning: ";
64  return cerr;
65  }
66 
67  Options::~Options()
68  {
69  for(Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); it++)
70  {
71  delete *it;
72  }
73  }
74 
75  void Options::addOption(OptionBase *opt)
76  {
77  Names* names = new Names();
78  names->opt = opt;
79  string& opt_string = opt->opt_string;
80 
81  size_t opt_start = 0;
82  for (size_t opt_end = 0; opt_end != string::npos;)
83  {
84  opt_end = opt_string.find_first_of(',', opt_start);
85  bool force_short = 0;
86  if (opt_string[opt_start] == '-')
87  {
88  opt_start++;
89  force_short = 1;
90  }
91  string opt_name = opt_string.substr(opt_start, opt_end - opt_start);
92  if (force_short || opt_name.size() == 1)
93  {
94  names->opt_short.push_back(opt_name);
95  opt_short_map[opt_name].push_back(names);
96  }
97  else
98  {
99  names->opt_long.push_back(opt_name);
100  opt_long_map[opt_name].push_back(names);
101  }
102  opt_start += opt_end + 1;
103  }
104  opt_list.push_back(names);
105  }
106 
107  /* Helper method to initiate adding options to Options */
108  OptionSpecific Options::addOptions()
109  {
110  return OptionSpecific(*this);
111  }
112 
113  static void setOptions(Options::NamesPtrList& opt_list, const string& value, ErrorReporter& error_reporter)
114  {
115  /* multiple options may be registered for the same name:
116  * allow each to parse value */
117  for (Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); ++it)
118  {
119  (*it)->opt->parse(value, error_reporter);
120  }
121  }
122 
123  static const char spaces[41] = " ";
124 
125  /* format help text for a single option:
126  * using the formatting: "-x, --long",
127  * if a short/long option isn't specified, it is not printed
128  */
129  static void doHelpOpt(ostream& out, const Options::Names& entry, unsigned pad_short = 0)
130  {
131  pad_short = min(pad_short, 8u);
132 
133  if (!entry.opt_short.empty())
134  {
135  unsigned pad = max((int)pad_short - (int)entry.opt_short.front().size(), 0);
136  out << "-" << entry.opt_short.front();
137  if (!entry.opt_long.empty())
138  {
139  out << ", ";
140  }
141  out << &(spaces[40 - pad]);
142  }
143  else
144  {
145  out << " ";
146  out << &(spaces[40 - pad_short]);
147  }
148 
149  if (!entry.opt_long.empty())
150  {
151  out << "--" << entry.opt_long.front();
152  }
153  }
154 
155  /* format the help text */
156  void doHelp(ostream& out, Options& opts, unsigned columns)
157  {
158  const unsigned pad_short = 3;
159  /* first pass: work out the longest option name */
160  unsigned max_width = 0;
161  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
162  {
163  ostringstream line(ios_base::out);
164  doHelpOpt(line, **it, pad_short);
165  max_width = max(max_width, (unsigned) line.tellp());
166  }
167 
168  unsigned opt_width = min(max_width+2, 28u + pad_short) + 2;
169  unsigned desc_width = columns - opt_width;
170 
171  /* second pass: write out formatted option and help text.
172  * - align start of help text to start at opt_width
173  * - if the option text is longer than opt_width, place the help
174  * text at opt_width on the next line.
175  */
176  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
177  {
178  ostringstream line(ios_base::out);
179  line << " ";
180  doHelpOpt(line, **it, pad_short);
181 
182  const string& opt_desc = (*it)->opt->opt_desc;
183  if (opt_desc.empty())
184  {
185  /* no help text: output option, skip further processing */
186  cout << line.str() << endl;
187  continue;
188  }
189  size_t currlength = size_t(line.tellp());
190  if (currlength > opt_width)
191  {
192  /* if option text is too long (and would collide with the
193  * help text, split onto next line */
194  line << endl;
195  currlength = 0;
196  }
197  /* split up the help text, taking into account new lines,
198  * (add opt_width of padding to each new line) */
199  for (size_t newline_pos = 0, cur_pos = 0; cur_pos != string::npos; currlength = 0)
200  {
201  /* print any required padding space for vertical alignment */
202  line << &(spaces[40 - opt_width + currlength]);
203  newline_pos = opt_desc.find_first_of('\n', newline_pos);
204  if (newline_pos != string::npos)
205  {
206  /* newline found, print substring (newline needn't be stripped) */
207  newline_pos++;
208  line << opt_desc.substr(cur_pos, newline_pos - cur_pos);
209  cur_pos = newline_pos;
210  continue;
211  }
212  if (cur_pos + desc_width > opt_desc.size())
213  {
214  /* no need to wrap text, remainder is less than avaliable width */
215  line << opt_desc.substr(cur_pos);
216  break;
217  }
218  /* find a suitable point to split text (avoid spliting in middle of word) */
219  size_t split_pos = opt_desc.find_last_of(' ', cur_pos + desc_width);
220  if (split_pos != string::npos)
221  {
222  /* eat up multiple space characters */
223  split_pos = opt_desc.find_last_not_of(' ', split_pos) + 1;
224  }
225 
226  /* bad split if no suitable space to split at. fall back to width */
227  bool bad_split = split_pos == string::npos || split_pos <= cur_pos;
228  if (bad_split)
229  {
230  split_pos = cur_pos + desc_width;
231  }
232  line << opt_desc.substr(cur_pos, split_pos - cur_pos);
233 
234  /* eat up any space for the start of the next line */
235  if (!bad_split)
236  {
237  split_pos = opt_desc.find_first_not_of(' ', split_pos);
238  }
239  cur_pos = newline_pos = split_pos;
240 
241  if (cur_pos >= opt_desc.size())
242  {
243  break;
244  }
245  line << endl;
246  }
247 
248  cout << line.str() << endl;
249  }
250  }
251 
253  {
255  : opts(rOpts), error_reporter(err)
256  {}
257  virtual ~OptionWriter() {}
258 
259  virtual const string where() = 0;
260 
261  bool storePair(bool allow_long, bool allow_short, const string& name, const string& value);
262  bool storePair(const string& name, const string& value)
263  {
264  return storePair(true, true, name, value);
265  }
266 
269  };
270 
271  bool OptionWriter::storePair(bool allow_long, bool allow_short, const string& name, const string& value)
272  {
273  bool found = false;
274  Options::NamesMap::iterator opt_it;
275  if (allow_long)
276  {
277  opt_it = opts.opt_long_map.find(name);
278  if (opt_it != opts.opt_long_map.end())
279  {
280  found = true;
281  }
282  }
283 
284  /* check for the short list */
285  if (allow_short && !(found && allow_long))
286  {
287  opt_it = opts.opt_short_map.find(name);
288  if (opt_it != opts.opt_short_map.end())
289  {
290  found = true;
291  }
292  }
293 
294  if (!found)
295  {
296  error_reporter.error(where())
297  << "Unknown option `" << name << "' (value:`" << value << "')\n";
298  return false;
299  }
300 
301  setOptions((*opt_it).second, value, error_reporter);
302  return true;
303  }
304 
305  struct ArgvParser : public OptionWriter
306  {
307  ArgvParser(Options& rOpts, ErrorReporter& rError_reporter)
308  : OptionWriter(rOpts, rError_reporter)
309  {}
310 
311  const string where() { return "command line"; }
312 
313  unsigned parseGNU(unsigned argc, const char* argv[]);
314  unsigned parseSHORT(unsigned argc, const char* argv[]);
315  };
316 
320  unsigned ArgvParser::parseGNU(unsigned argc, const char* argv[])
321  {
322  /* gnu style long options can take the forms:
323  * --option=arg
324  * --option arg
325  */
326  string arg(argv[0]);
327  size_t arg_opt_start = arg.find_first_not_of('-');
328  size_t arg_opt_sep = arg.find_first_of('=');
329  string option = arg.substr(arg_opt_start, arg_opt_sep - arg_opt_start);
330 
331  unsigned extra_argc_consumed = 0;
332  if (arg_opt_sep == string::npos)
333  {
334  /* no argument found => argument in argv[1] (maybe) */
335  /* xxx, need to handle case where option isn't required */
336 #if 0
337  /* commented out, to return to true GNU style processing
338  * where longopts have to include an =, otherwise they are
339  * booleans */
340  if (argc == 1)
341  {
342  return 0; /* run out of argv for argument */
343  }
344  extra_argc_consumed = 1;
345 #endif
346  if(!storePair(true, false, option, "1"))
347  {
348  return 0;
349  }
350  }
351  else
352  {
353  /* argument occurs after option_sep */
354  string val = arg.substr(arg_opt_sep + 1);
355  storePair(true, false, option, val);
356  }
357 
358  return extra_argc_consumed;
359  }
360 
361  unsigned ArgvParser::parseSHORT(unsigned argc, const char* argv[])
362  {
363  /* short options can take the forms:
364  * --option arg
365  * -option arg
366  */
367  string arg(argv[0]);
368  size_t arg_opt_start = arg.find_first_not_of('-');
369  string option = arg.substr(arg_opt_start);
370  /* lookup option */
371 
372  /* argument in argv[1] */
373  /* xxx, need to handle case where option isn't required */
374  if (argc == 1)
375  {
376  error_reporter.error(where())
377  << "Not processing option `" << option << "' without argument\n";
378  return 0; /* run out of argv for argument */
379  }
380  storePair(false, true, option, string(argv[1]));
381 
382  return 1;
383  }
384 
385  list<const char*>
386  scanArgv(Options& opts, unsigned argc, const char* argv[], ErrorReporter& error_reporter)
387  {
388  ArgvParser avp(opts, error_reporter);
389 
390  /* a list for anything that didn't get handled as an option */
391  list<const char*> non_option_arguments;
392 
393  for(unsigned i = 1; i < argc; i++)
394  {
395  if (argv[i][0] != '-')
396  {
397  non_option_arguments.push_back(argv[i]);
398  continue;
399  }
400 
401  if (argv[i][1] == 0)
402  {
403  /* a lone single dash is an argument (usually signifying stdin) */
404  non_option_arguments.push_back(argv[i]);
405  continue;
406  }
407 
408  if (argv[i][1] != '-')
409  {
410  /* handle short (single dash) options */
411  i += avp.parseSHORT(argc - i, &argv[i]);
412  continue;
413  }
414 
415  if (argv[i][2] == 0)
416  {
417  /* a lone double dash ends option processing */
418  while (++i < argc)
419  {
420  non_option_arguments.push_back(argv[i]);
421  }
422  break;
423  }
424 
425  /* handle long (double dash) options */
426  i += avp.parseGNU(argc - i, &argv[i]);
427  }
428 
429  return non_option_arguments;
430  }
431 
433  {
434  CfgStreamParser(const string& rName, Options& rOpts, ErrorReporter& rError_reporter)
435  : OptionWriter(rOpts, rError_reporter)
436  , name(rName)
437  , linenum(0)
438  {}
439 
440  const string name;
441  int linenum;
442  const string where()
443  {
444  ostringstream os;
445  os << name << ":" << linenum;
446  return os.str();
447  }
448 
449  void scanLine(string& line);
450  void scanStream(istream& in);
451  };
452 
453  void CfgStreamParser::scanLine(string& line)
454  {
455  /* strip any leading whitespace */
456  size_t start = line.find_first_not_of(" \t\n\r");
457  if (start == string::npos)
458  {
459  /* blank line */
460  return;
461  }
462  if (line[start] == '#')
463  {
464  /* comment line */
465  return;
466  }
467  /* look for first whitespace or ':' after the option end */
468  size_t option_end = line.find_first_of(": \t\n\r",start);
469  string option = line.substr(start, option_end - start);
470 
471  /* look for ':', eat up any whitespace first */
472  start = line.find_first_not_of(" \t\n\r", option_end);
473  if (start == string::npos)
474  {
475  /* error: badly formatted line */
476  error_reporter.warn(where()) << "line formatting error\n";
477  return;
478  }
479  if (line[start] != ':')
480  {
481  /* error: badly formatted line */
482  error_reporter.warn(where()) << "line formatting error\n";
483  return;
484  }
485 
486  /* look for start of value string -- eat up any leading whitespace */
487  start = line.find_first_not_of(" \t\n\r", ++start);
488  if (start == string::npos)
489  {
490  /* error: badly formatted line */
491  error_reporter.warn(where()) << "line formatting error\n";
492  return;
493  }
494 
495  /* extract the value part, which may contain embedded spaces
496  * by searching for a word at a time, until we hit a comment or end of line */
497  size_t value_end = start;
498  do
499  {
500  if (line[value_end] == '#')
501  {
502  /* rest of line is a comment */
503  value_end--;
504  break;
505  }
506  value_end = line.find_first_of(" \t\n\r", value_end);
507  /* consume any white space, incase there is another word.
508  * any trailing whitespace will be removed shortly */
509  value_end = line.find_first_not_of(" \t\n\r", value_end);
510  } while (value_end != string::npos);
511  /* strip any trailing space from value*/
512  value_end = line.find_last_not_of(" \t\n\r", value_end);
513 
514  string value;
515  if (value_end >= start)
516  {
517  value = line.substr(start, value_end +1 - start);
518  }
519  else
520  {
521  /* error: no value */
522  error_reporter.warn(where()) << "no value found\n";
523  return;
524  }
525 
526  /* store the value in option */
527  storePair(true, false, option, value);
528  }
529 
530  void CfgStreamParser::scanStream(istream& in)
531  {
532  do
533  {
534  linenum++;
535  string line;
536  getline(in, line);
537  scanLine(line);
538  } while(!!in);
539  }
540 
541  /* for all options in opts, set their storage to their specified
542  * default value */
543  void setDefaults(Options& opts)
544  {
545  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
546  {
547  (*it)->opt->setDefault();
548  }
549  }
550 
551  void parseConfigFile(Options& opts, const string& filename, ErrorReporter& error_reporter)
552  {
553  ifstream cfgstream(filename.c_str(), ifstream::in);
554  if (!cfgstream)
555  {
556  error_reporter.error(filename) << "Failed to open config file\n";
557  return;
558  }
559  CfgStreamParser csp(filename, opts, error_reporter);
560  csp.scanStream(cfgstream);
561  }
562 
563  }
564 }
565 
void doHelp(ostream &out, Options &opts, unsigned columns)
unsigned parseGNU(unsigned argc, const char *argv[])
static const char spaces[41]
static void setOptions(Options::NamesPtrList &opt_list, const string &value, ErrorReporter &error_reporter)
list< const char * > scanArgv(Options &opts, unsigned argc, const char *argv[], ErrorReporter &error_reporter)
unsigned parseSHORT(unsigned argc, const char *argv[])
CfgStreamParser(const string &rName, Options &rOpts, ErrorReporter &rError_reporter)
bool storePair(const string &name, const string &value)
OptionWriter(Options &rOpts, ErrorReporter &err)
void parseConfigFile(Options &opts, const string &filename, ErrorReporter &error_reporter)
static void doHelpOpt(ostream &out, const Options::Names &entry, unsigned pad_short=0)
ArgvParser(Options &rOpts, ErrorReporter &rError_reporter)
virtual std::ostream & error(const std::string &where)