HEVC Test Model (HM)  HM-16.3
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-2015, 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  {
52 
53  Options::~Options()
54  {
55  for(Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); it++)
56  {
57  delete *it;
58  }
59  }
60 
61  void Options::addOption(OptionBase *opt)
62  {
63  Names* names = new Names();
64  names->opt = opt;
65  string& opt_string = opt->opt_string;
66 
67  size_t opt_start = 0;
68  for (size_t opt_end = 0; opt_end != string::npos;)
69  {
70  opt_end = opt_string.find_first_of(',', opt_start);
71  bool force_short = 0;
72  if (opt_string[opt_start] == '-')
73  {
74  opt_start++;
75  force_short = 1;
76  }
77  string opt_name = opt_string.substr(opt_start, opt_end - opt_start);
78  if (force_short || opt_name.size() == 1)
79  {
80  names->opt_short.push_back(opt_name);
81  opt_short_map[opt_name].push_back(names);
82  }
83  else
84  {
85  names->opt_long.push_back(opt_name);
86  opt_long_map[opt_name].push_back(names);
87  }
88  opt_start += opt_end + 1;
89  }
90  opt_list.push_back(names);
91  }
92 
93  /* Helper method to initiate adding options to Options */
94  OptionSpecific Options::addOptions()
95  {
96  return OptionSpecific(*this);
97  }
98 
99  static void setOptions(Options::NamesPtrList& opt_list, const string& value)
100  {
101  /* multiple options may be registered for the same name:
102  * allow each to parse value */
103  for (Options::NamesPtrList::iterator it = opt_list.begin(); it != opt_list.end(); ++it)
104  {
105  (*it)->opt->parse(value);
106  }
107  }
108 
109  static const char spaces[41] = " ";
110 
111  /* format help text for a single option:
112  * using the formatting: "-x, --long",
113  * if a short/long option isn't specified, it is not printed
114  */
115  static void doHelpOpt(ostream& out, const Options::Names& entry, unsigned pad_short = 0)
116  {
117  pad_short = min(pad_short, 8u);
118 
119  if (!entry.opt_short.empty())
120  {
121  unsigned pad = max((int)pad_short - (int)entry.opt_short.front().size(), 0);
122  out << "-" << entry.opt_short.front();
123  if (!entry.opt_long.empty())
124  {
125  out << ", ";
126  }
127  out << &(spaces[40 - pad]);
128  }
129  else
130  {
131  out << " ";
132  out << &(spaces[40 - pad_short]);
133  }
134 
135  if (!entry.opt_long.empty())
136  {
137  out << "--" << entry.opt_long.front();
138  }
139  }
140 
141  /* format the help text */
142  void doHelp(ostream& out, Options& opts, unsigned columns)
143  {
144  const unsigned pad_short = 3;
145  /* first pass: work out the longest option name */
146  unsigned max_width = 0;
147  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
148  {
149  ostringstream line(ios_base::out);
150  doHelpOpt(line, **it, pad_short);
151  max_width = max(max_width, (unsigned) line.tellp());
152  }
153 
154  unsigned opt_width = min(max_width+2, 28u + pad_short) + 2;
155  unsigned desc_width = columns - opt_width;
156 
157  /* second pass: write out formatted option and help text.
158  * - align start of help text to start at opt_width
159  * - if the option text is longer than opt_width, place the help
160  * text at opt_width on the next line.
161  */
162  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
163  {
164  ostringstream line(ios_base::out);
165  line << " ";
166  doHelpOpt(line, **it, pad_short);
167 
168  const string& opt_desc = (*it)->opt->opt_desc;
169  if (opt_desc.empty())
170  {
171  /* no help text: output option, skip further processing */
172  cout << line.str() << endl;
173  continue;
174  }
175  size_t currlength = size_t(line.tellp());
176  if (currlength > opt_width)
177  {
178  /* if option text is too long (and would collide with the
179  * help text, split onto next line */
180  line << endl;
181  currlength = 0;
182  }
183  /* split up the help text, taking into account new lines,
184  * (add opt_width of padding to each new line) */
185  for (size_t newline_pos = 0, cur_pos = 0; cur_pos != string::npos; currlength = 0)
186  {
187  /* print any required padding space for vertical alignment */
188  line << &(spaces[40 - opt_width + currlength]);
189  newline_pos = opt_desc.find_first_of('\n', newline_pos);
190  if (newline_pos != string::npos)
191  {
192  /* newline found, print substring (newline needn't be stripped) */
193  newline_pos++;
194  line << opt_desc.substr(cur_pos, newline_pos - cur_pos);
195  cur_pos = newline_pos;
196  continue;
197  }
198  if (cur_pos + desc_width > opt_desc.size())
199  {
200  /* no need to wrap text, remainder is less than avaliable width */
201  line << opt_desc.substr(cur_pos);
202  break;
203  }
204  /* find a suitable point to split text (avoid spliting in middle of word) */
205  size_t split_pos = opt_desc.find_last_of(' ', cur_pos + desc_width);
206  if (split_pos != string::npos)
207  {
208  /* eat up multiple space characters */
209  split_pos = opt_desc.find_last_not_of(' ', split_pos) + 1;
210  }
211 
212  /* bad split if no suitable space to split at. fall back to width */
213  bool bad_split = split_pos == string::npos || split_pos <= cur_pos;
214  if (bad_split)
215  {
216  split_pos = cur_pos + desc_width;
217  }
218  line << opt_desc.substr(cur_pos, split_pos - cur_pos);
219 
220  /* eat up any space for the start of the next line */
221  if (!bad_split)
222  {
223  split_pos = opt_desc.find_first_not_of(' ', split_pos);
224  }
225  cur_pos = newline_pos = split_pos;
226 
227  if (cur_pos >= opt_desc.size())
228  {
229  break;
230  }
231  line << endl;
232  }
233 
234  cout << line.str() << endl;
235  }
236  }
237 
238  bool storePair(Options& opts, bool allow_long, bool allow_short, const string& name, const string& value)
239  {
240  bool found = false;
241  Options::NamesMap::iterator opt_it;
242  if (allow_long)
243  {
244  opt_it = opts.opt_long_map.find(name);
245  if (opt_it != opts.opt_long_map.end())
246  {
247  found = true;
248  }
249  }
250 
251  /* check for the short list */
252  if (allow_short && !(found && allow_long))
253  {
254  opt_it = opts.opt_short_map.find(name);
255  if (opt_it != opts.opt_short_map.end())
256  {
257  found = true;
258  }
259  }
260 
261  if (!found)
262  {
263  /* not found */
264  cerr << "Unknown option: `" << name << "' (value:`" << value << "')" << endl;
265  return false;
266  }
267 
268  setOptions((*opt_it).second, value);
269  return true;
270  }
271 
272  bool storePair(Options& opts, const string& name, const string& value)
273  {
274  return storePair(opts, true, true, name, value);
275  }
276 
280  unsigned parseGNU(Options& opts, unsigned argc, const char* argv[])
281  {
282  /* gnu style long options can take the forms:
283  * --option=arg
284  * --option arg
285  */
286  string arg(argv[0]);
287  size_t arg_opt_start = arg.find_first_not_of('-');
288  size_t arg_opt_sep = arg.find_first_of('=');
289  string option = arg.substr(arg_opt_start, arg_opt_sep - arg_opt_start);
290 
291  unsigned extra_argc_consumed = 0;
292  if (arg_opt_sep == string::npos)
293  {
294  /* no argument found => argument in argv[1] (maybe) */
295  /* xxx, need to handle case where option isn't required */
296 #if 0
297  /* commented out, to return to true GNU style processing
298  * where longopts have to include an =, otherwise they are
299  * booleans */
300  if (argc == 1)
301  {
302  return 0; /* run out of argv for argument */
303  }
304  extra_argc_consumed = 1;
305 #endif
306  if(!storePair(opts, true, false, option, "1"))
307  {
308  return 0;
309  }
310  }
311  else
312  {
313  /* argument occurs after option_sep */
314  string val = arg.substr(arg_opt_sep + 1);
315  storePair(opts, true, false, option, val);
316  }
317 
318  return extra_argc_consumed;
319  }
320 
321  unsigned parseSHORT(Options& opts, unsigned argc, const char* argv[])
322  {
323  /* short options can take the forms:
324  * --option arg
325  * -option arg
326  */
327  string arg(argv[0]);
328  size_t arg_opt_start = arg.find_first_not_of('-');
329  string option = arg.substr(arg_opt_start);
330  /* lookup option */
331 
332  /* argument in argv[1] */
333  /* xxx, need to handle case where option isn't required */
334  if (argc == 1)
335  {
336  cerr << "Not processing option without argument `" << option << "'" << endl;
337  return 0; /* run out of argv for argument */
338  }
339  storePair(opts, false, true, option, string(argv[1]));
340 
341  return 1;
342  }
343 
344  list<const char*>
345  scanArgv(Options& opts, unsigned argc, const char* argv[])
346  {
347  /* a list for anything that didn't get handled as an option */
348  list<const char*> non_option_arguments;
349 
350  for(unsigned i = 1; i < argc; i++)
351  {
352  if (argv[i][0] != '-')
353  {
354  non_option_arguments.push_back(argv[i]);
355  continue;
356  }
357 
358  if (argv[i][1] == 0)
359  {
360  /* a lone single dash is an argument (usually signifying stdin) */
361  non_option_arguments.push_back(argv[i]);
362  continue;
363  }
364 
365  if (argv[i][1] != '-')
366  {
367  /* handle short (single dash) options */
368 #if 0
369  i += parsePOSIX(opts, argc - i, &argv[i]);
370 #else
371  i += parseSHORT(opts, argc - i, &argv[i]);
372 #endif
373  continue;
374  }
375 
376  if (argv[i][2] == 0)
377  {
378  /* a lone double dash ends option processing */
379  while (++i < argc)
380  {
381  non_option_arguments.push_back(argv[i]);
382  }
383  break;
384  }
385 
386  /* handle long (double dash) options */
387  i += parseGNU(opts, argc - i, &argv[i]);
388  }
389 
390  return non_option_arguments;
391  }
392 
393  void scanLine(Options& opts, string& line)
394  {
395  /* strip any leading whitespace */
396  size_t start = line.find_first_not_of(" \t\n\r");
397  if (start == string::npos)
398  {
399  /* blank line */
400  return;
401  }
402  if (line[start] == '#')
403  {
404  /* comment line */
405  return;
406  }
407  /* look for first whitespace or ':' after the option end */
408  size_t option_end = line.find_first_of(": \t\n\r",start);
409  string option = line.substr(start, option_end - start);
410 
411  /* look for ':', eat up any whitespace first */
412  start = line.find_first_not_of(" \t\n\r", option_end);
413  if (start == string::npos)
414  {
415  /* error: badly formatted line */
416  return;
417  }
418  if (line[start] != ':')
419  {
420  /* error: badly formatted line */
421  return;
422  }
423 
424  /* look for start of value string -- eat up any leading whitespace */
425  start = line.find_first_not_of(" \t\n\r", ++start);
426  if (start == string::npos)
427  {
428  /* error: badly formatted line */
429  return;
430  }
431 
432  /* extract the value part, which may contain embedded spaces
433  * by searching for a word at a time, until we hit a comment or end of line */
434  size_t value_end = start;
435  do
436  {
437  if (line[value_end] == '#')
438  {
439  /* rest of line is a comment */
440  value_end--;
441  break;
442  }
443  value_end = line.find_first_of(" \t\n\r", value_end);
444  /* consume any white space, incase there is another word.
445  * any trailing whitespace will be removed shortly */
446  value_end = line.find_first_not_of(" \t\n\r", value_end);
447  } while (value_end != string::npos);
448  /* strip any trailing space from value*/
449  value_end = line.find_last_not_of(" \t\n\r", value_end);
450 
451  string value;
452  if (value_end >= start)
453  {
454  value = line.substr(start, value_end +1 - start);
455  }
456  else
457  {
458  /* error: no value */
459  return;
460  }
461 
462  /* store the value in option */
463  storePair(opts, true, false, option, value);
464  }
465 
466  void scanFile(Options& opts, istream& in)
467  {
468  do
469  {
470  string line;
471  getline(in, line);
472  scanLine(opts, line);
473  } while(!!in);
474  }
475 
476  /* for all options in opts, set their storage to their specified
477  * default value */
478  void setDefaults(Options& opts)
479  {
480  for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
481  {
482  (*it)->opt->setDefault();
483  }
484  }
485 
486  void parseConfigFile(Options& opts, const string& filename)
487  {
488  ifstream cfgstream(filename.c_str(), ifstream::in);
489  if (!cfgstream)
490  {
491  cerr << "Failed to open config file: `" << filename << "'" << endl;
492  exit(EXIT_FAILURE);
493  }
494  scanFile(opts, cfgstream);
495  }
496 
497  }
498 }
499 
void parseConfigFile(Options &opts, const string &filename)
void doHelp(ostream &out, Options &opts, unsigned columns)
static const char spaces[41]
void scanLine(Options &opts, string &line)
STL namespace.
bool storePair(Options &opts, const string &name, const string &value)
unsigned parseGNU(Options &opts, unsigned argc, const char *argv[])
unsigned parseSHORT(Options &opts, unsigned argc, const char *argv[])
static void setOptions(Options::NamesPtrList &opt_list, const string &value)
list< const char * > scanArgv(Options &opts, unsigned argc, const char *argv[])
static void doHelpOpt(ostream &out, const Options::Names &entry, unsigned pad_short=0)
void scanFile(Options &opts, istream &in)