source: SHVCSoftware/branches/SHM-dev/source/Lib/TAppCommon/program_options_lite.cpp @ 1550

Last change on this file since 1550 was 1549, checked in by seregin, 9 years ago

port rev 4732, update copyright notice to include 2016

  • Property svn:eol-style set to native
File size: 16.9 KB
Line 
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-2016, 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
43using namespace std;
44
45//! \ingroup TAppCommon
46//! \{
47
48namespace df
49{
50  namespace program_options_lite
51  {
52    ErrorReporter default_error_reporter;
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
252    struct OptionWriter
253    {
254      OptionWriter(Options& rOpts, ErrorReporter& err)
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
267      Options& opts;
268      ErrorReporter& error_reporter;
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#if 1 //SVC_EXTENSION
297        if( error_reporter.verbose )
298#endif
299        error_reporter.error(where())
300          << "Unknown option `" << name << "' (value:`" << value << "')\n";
301        return false;
302      }
303
304      setOptions((*opt_it).second, value, error_reporter);
305      return true;
306    }
307
308    struct ArgvParser : public OptionWriter
309    {
310      ArgvParser(Options& rOpts, ErrorReporter& rError_reporter)
311      : OptionWriter(rOpts, rError_reporter)
312      {}
313
314      const string where() { return "command line"; }
315
316      unsigned parseGNU(unsigned argc, const char* argv[]);
317      unsigned parseSHORT(unsigned argc, const char* argv[]);
318    };
319
320    /**
321     * returns number of extra arguments consumed
322     */
323    unsigned ArgvParser::parseGNU(unsigned argc, const char* argv[])
324    {
325      /* gnu style long options can take the forms:
326       *  --option=arg
327       *  --option arg
328       */
329      string arg(argv[0]);
330      size_t arg_opt_start = arg.find_first_not_of('-');
331      size_t arg_opt_sep = arg.find_first_of('=');
332      string option = arg.substr(arg_opt_start, arg_opt_sep - arg_opt_start);
333
334      unsigned extra_argc_consumed = 0;
335      if (arg_opt_sep == string::npos)
336      {
337        /* no argument found => argument in argv[1] (maybe) */
338        /* xxx, need to handle case where option isn't required */
339#if 0
340        /* commented out, to return to true GNU style processing
341        * where longopts have to include an =, otherwise they are
342        * booleans */
343        if (argc == 1)
344        {
345          return 0; /* run out of argv for argument */
346        }
347        extra_argc_consumed = 1;
348#endif
349        if(!storePair(true, false, option, "1"))
350        {
351          return 0;
352        }
353      }
354      else
355      {
356        /* argument occurs after option_sep */
357        string val = arg.substr(arg_opt_sep + 1);
358        storePair(true, false, option, val);
359      }
360
361      return extra_argc_consumed;
362    }
363
364    unsigned ArgvParser::parseSHORT(unsigned argc, const char* argv[])
365    {
366      /* short options can take the forms:
367       *  --option arg
368       *  -option arg
369       */
370      string arg(argv[0]);
371      size_t arg_opt_start = arg.find_first_not_of('-');
372      string option = arg.substr(arg_opt_start);
373      /* lookup option */
374
375      /* argument in argv[1] */
376      /* xxx, need to handle case where option isn't required */
377      if (argc == 1)
378      {
379        error_reporter.error(where())
380          << "Not processing option `" << option << "' without argument\n";
381        return 0; /* run out of argv for argument */
382      }
383      storePair(false, true, option, string(argv[1]));
384
385      return 1;
386    }
387
388    list<const char*>
389    scanArgv(Options& opts, unsigned argc, const char* argv[], ErrorReporter& error_reporter)
390    {
391      ArgvParser avp(opts, error_reporter);
392
393      /* a list for anything that didn't get handled as an option */
394      list<const char*> non_option_arguments;
395
396      for(unsigned i = 1; i < argc; i++)
397      {
398        if (argv[i][0] != '-')
399        {
400          non_option_arguments.push_back(argv[i]);
401          continue;
402        }
403
404        if (argv[i][1] == 0)
405        {
406          /* a lone single dash is an argument (usually signifying stdin) */
407          non_option_arguments.push_back(argv[i]);
408          continue;
409        }
410
411        if (argv[i][1] != '-')
412        {
413          /* handle short (single dash) options */
414          i += avp.parseSHORT(argc - i, &argv[i]);
415          continue;
416        }
417
418        if (argv[i][2] == 0)
419        {
420          /* a lone double dash ends option processing */
421          while (++i < argc)
422          {
423            non_option_arguments.push_back(argv[i]);
424          }
425          break;
426        }
427
428        /* handle long (double dash) options */
429        i += avp.parseGNU(argc - i, &argv[i]);
430      }
431
432      return non_option_arguments;
433    }
434
435    struct CfgStreamParser : public OptionWriter
436    {
437      CfgStreamParser(const string& rName, Options& rOpts, ErrorReporter& rError_reporter)
438      : OptionWriter(rOpts, rError_reporter)
439      , name(rName)
440      , linenum(0)
441      {}
442
443      const string name;
444      int linenum;
445      const string where()
446      {
447        ostringstream os;
448        os << name << ":" << linenum;
449        return os.str();
450      }
451
452      void scanLine(string& line);
453      void scanStream(istream& in);
454    };
455
456    void CfgStreamParser::scanLine(string& line)
457    {
458      /* strip any leading whitespace */
459      size_t start = line.find_first_not_of(" \t\n\r");
460      if (start == string::npos)
461      {
462        /* blank line */
463        return;
464      }
465      if (line[start] == '#')
466      {
467        /* comment line */
468        return;
469      }
470      /* look for first whitespace or ':' after the option end */
471      size_t option_end = line.find_first_of(": \t\n\r",start);
472      string option = line.substr(start, option_end - start);
473
474      /* look for ':', eat up any whitespace first */
475      start = line.find_first_not_of(" \t\n\r", option_end);
476      if (start == string::npos)
477      {
478        /* error: badly formatted line */
479        error_reporter.warn(where()) << "line formatting error\n";
480        return;
481      }
482      if (line[start] != ':')
483      {
484        /* error: badly formatted line */
485        error_reporter.warn(where()) << "line formatting error\n";
486        return;
487      }
488
489      /* look for start of value string -- eat up any leading whitespace */
490      start = line.find_first_not_of(" \t\n\r", ++start);
491      if (start == string::npos)
492      {
493        /* error: badly formatted line */
494        error_reporter.warn(where()) << "line formatting error\n";
495        return;
496      }
497
498      /* extract the value part, which may contain embedded spaces
499       * by searching for a word at a time, until we hit a comment or end of line */
500      size_t value_end = start;
501      do
502      {
503        if (line[value_end] == '#')
504        {
505          /* rest of line is a comment */
506          value_end--;
507          break;
508        }
509        value_end = line.find_first_of(" \t\n\r", value_end);
510        /* consume any white space, incase there is another word.
511         * any trailing whitespace will be removed shortly */
512        value_end = line.find_first_not_of(" \t\n\r", value_end);
513      } while (value_end != string::npos);
514      /* strip any trailing space from value*/
515      value_end = line.find_last_not_of(" \t\n\r", value_end);
516
517      string value;
518      if (value_end >= start)
519      {
520        value = line.substr(start, value_end +1 - start);
521      }
522      else
523      {
524        /* error: no value */
525        error_reporter.warn(where()) << "no value found\n";
526        return;
527      }
528
529      /* store the value in option */
530      storePair(true, false, option, value);
531    }
532
533    void CfgStreamParser::scanStream(istream& in)
534    {
535      do
536      {
537        linenum++;
538        string line;
539        getline(in, line);
540        scanLine(line);
541      } while(!!in);
542    }
543
544    /* for all options in opts, set their storage to their specified
545     * default value */
546    void setDefaults(Options& opts)
547    {
548      for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
549      {
550        (*it)->opt->setDefault();
551      }
552    }
553
554    void parseConfigFile(Options& opts, const string& filename, ErrorReporter& error_reporter)
555    {
556      ifstream cfgstream(filename.c_str(), ifstream::in);
557      if (!cfgstream)
558      {
559        error_reporter.error(filename) << "Failed to open config file\n";
560        return;
561      }
562      CfgStreamParser csp(filename, opts, error_reporter);
563      csp.scanStream(cfgstream);
564    }
565
566  }
567}
568
569//! \}
Note: See TracBrowser for help on using the repository browser.