source: 3DVCSoftware/branches/HTM-15.1-dev0/source/Lib/TAppCommon/program_options_lite.cpp @ 1327

Last change on this file since 1327 was 1313, checked in by tech, 9 years ago

Merged 14.1-update-dev1@1312.

File size: 17.7 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-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#include  "../TLibCommon/CommonDef.h"
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#if NH_MV
164        if  ( (*it)->opt->opt_duplicate ) continue; 
165#endif
166        ostringstream line(ios_base::out);
167        doHelpOpt(line, **it, pad_short);
168        max_width = max(max_width, (unsigned) line.tellp());
169      }
170
171      unsigned opt_width = min(max_width+2, 28u + pad_short) + 2;
172      unsigned desc_width = columns - opt_width;
173
174      /* second pass: write out formatted option and help text.
175       *  - align start of help text to start at opt_width
176       *  - if the option text is longer than opt_width, place the help
177       *    text at opt_width on the next line.
178       */
179      for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
180      {
181#if NH_MV
182        if  ( (*it)->opt->opt_duplicate ) continue; 
183#endif
184        ostringstream line(ios_base::out);
185        line << "  ";
186        doHelpOpt(line, **it, pad_short);
187
188        const string& opt_desc = (*it)->opt->opt_desc;
189        if (opt_desc.empty())
190        {
191          /* no help text: output option, skip further processing */
192          cout << line.str() << endl;
193          continue;
194        }
195        size_t currlength = size_t(line.tellp());
196        if (currlength > opt_width)
197        {
198          /* if option text is too long (and would collide with the
199           * help text, split onto next line */
200          line << endl;
201          currlength = 0;
202        }
203        /* split up the help text, taking into account new lines,
204         *   (add opt_width of padding to each new line) */
205        for (size_t newline_pos = 0, cur_pos = 0; cur_pos != string::npos; currlength = 0)
206        {
207          /* print any required padding space for vertical alignment */
208          line << &(spaces[40 - opt_width + currlength]);
209          newline_pos = opt_desc.find_first_of('\n', newline_pos);
210          if (newline_pos != string::npos)
211          {
212            /* newline found, print substring (newline needn't be stripped) */
213            newline_pos++;
214            line << opt_desc.substr(cur_pos, newline_pos - cur_pos);
215            cur_pos = newline_pos;
216            continue;
217          }
218          if (cur_pos + desc_width > opt_desc.size())
219          {
220            /* no need to wrap text, remainder is less than avaliable width */
221            line << opt_desc.substr(cur_pos);
222            break;
223          }
224          /* find a suitable point to split text (avoid spliting in middle of word) */
225          size_t split_pos = opt_desc.find_last_of(' ', cur_pos + desc_width);
226          if (split_pos != string::npos)
227          {
228            /* eat up multiple space characters */
229            split_pos = opt_desc.find_last_not_of(' ', split_pos) + 1;
230          }
231
232          /* bad split if no suitable space to split at.  fall back to width */
233          bool bad_split = split_pos == string::npos || split_pos <= cur_pos;
234          if (bad_split)
235          {
236            split_pos = cur_pos + desc_width;
237          }
238          line << opt_desc.substr(cur_pos, split_pos - cur_pos);
239
240          /* eat up any space for the start of the next line */
241          if (!bad_split)
242          {
243            split_pos = opt_desc.find_first_not_of(' ', split_pos);
244          }
245          cur_pos = newline_pos = split_pos;
246
247          if (cur_pos >= opt_desc.size())
248          {
249            break;
250          }
251          line << endl;
252        }
253
254        cout << line.str() << endl;
255      }
256    }
257
258    struct OptionWriter
259    {
260      OptionWriter(Options& rOpts, ErrorReporter& err)
261      : opts(rOpts), error_reporter(err)
262      {}
263      virtual ~OptionWriter() {}
264
265      virtual const string where() = 0;
266
267      bool storePair(bool allow_long, bool allow_short, const string& name, const string& value);
268      bool storePair(const string& name, const string& value)
269      {
270        return storePair(true, true, name, value);
271      }
272
273      Options& opts;
274      ErrorReporter& error_reporter;
275    };
276
277    bool OptionWriter::storePair(bool allow_long, bool allow_short, const string& name, const string& value)
278    {
279      bool found = false;
280      Options::NamesMap::iterator opt_it;
281      if (allow_long)
282      {
283        opt_it = opts.opt_long_map.find(name);
284        if (opt_it != opts.opt_long_map.end())
285        {
286          found = true;
287        }
288      }
289
290      /* check for the short list */
291      if (allow_short && !(found && allow_long))
292      {
293        opt_it = opts.opt_short_map.find(name);
294        if (opt_it != opts.opt_short_map.end())
295        {
296          found = true;
297        }
298      }
299
300      if (!found)
301      {
302        error_reporter.error(where())
303          << "Unknown option `" << name << "' (value:`" << value << "')\n";
304        return false;
305      }
306
307      setOptions((*opt_it).second, value, error_reporter);
308      return true;
309    }
310
311    struct ArgvParser : public OptionWriter
312    {
313      ArgvParser(Options& rOpts, ErrorReporter& rError_reporter)
314      : OptionWriter(rOpts, rError_reporter)
315      {}
316
317      const string where() { return "command line"; }
318
319      unsigned parseGNU(unsigned argc, const char* argv[]);
320      unsigned parseSHORT(unsigned argc, const char* argv[]);
321    };
322
323    /**
324     * returns number of extra arguments consumed
325     */
326    unsigned ArgvParser::parseGNU(unsigned argc, const char* argv[])
327    {
328      /* gnu style long options can take the forms:
329       *  --option=arg
330       *  --option arg
331       */
332      string arg(argv[0]);
333      size_t arg_opt_start = arg.find_first_not_of('-');
334      size_t arg_opt_sep = arg.find_first_of('=');
335      string option = arg.substr(arg_opt_start, arg_opt_sep - arg_opt_start);
336
337      unsigned extra_argc_consumed = 0;
338      if (arg_opt_sep == string::npos)
339      {
340        /* no argument found => argument in argv[1] (maybe) */
341        /* xxx, need to handle case where option isn't required */
342#if 0
343        /* commented out, to return to true GNU style processing
344        * where longopts have to include an =, otherwise they are
345        * booleans */
346        if (argc == 1)
347        {
348          return 0; /* run out of argv for argument */
349        }
350        extra_argc_consumed = 1;
351#endif
352        if(!storePair(true, false, option, "1"))
353        {
354          return 0;
355        }
356      }
357      else
358      {
359        /* argument occurs after option_sep */
360        string val = arg.substr(arg_opt_sep + 1);
361        storePair(true, false, option, val);
362      }
363
364      return extra_argc_consumed;
365    }
366
367    unsigned ArgvParser::parseSHORT(unsigned argc, const char* argv[])
368    {
369      /* short options can take the forms:
370       *  --option arg
371       *  -option arg
372       */
373      string arg(argv[0]);
374      size_t arg_opt_start = arg.find_first_not_of('-');
375      string option = arg.substr(arg_opt_start);
376      /* lookup option */
377
378      /* argument in argv[1] */
379      /* xxx, need to handle case where option isn't required */
380      if (argc == 1)
381      {
382        error_reporter.error(where())
383          << "Not processing option `" << option << "' without argument\n";
384        return 0; /* run out of argv for argument */
385      }
386      storePair(false, true, option, string(argv[1]));
387
388      return 1;
389    }
390
391    list<const char*>
392    scanArgv(Options& opts, unsigned argc, const char* argv[], ErrorReporter& error_reporter)
393    {
394      ArgvParser avp(opts, error_reporter);
395
396      /* a list for anything that didn't get handled as an option */
397      list<const char*> non_option_arguments;
398
399      for(unsigned i = 1; i < argc; i++)
400      {
401        if (argv[i][0] != '-')
402        {
403          non_option_arguments.push_back(argv[i]);
404          continue;
405        }
406
407        if (argv[i][1] == 0)
408        {
409          /* a lone single dash is an argument (usually signifying stdin) */
410          non_option_arguments.push_back(argv[i]);
411          continue;
412        }
413
414        if (argv[i][1] != '-')
415        {
416          /* handle short (single dash) options */
417          i += avp.parseSHORT(argc - i, &argv[i]);
418          continue;
419        }
420
421        if (argv[i][2] == 0)
422        {
423          /* a lone double dash ends option processing */
424          while (++i < argc)
425          {
426            non_option_arguments.push_back(argv[i]);
427          }
428          break;
429        }
430
431        /* handle long (double dash) options */
432        i += avp.parseGNU(argc - i, &argv[i]);
433      }
434
435      return non_option_arguments;
436    }
437
438    struct CfgStreamParser : public OptionWriter
439    {
440      CfgStreamParser(const string& rName, Options& rOpts, ErrorReporter& rError_reporter)
441      : OptionWriter(rOpts, rError_reporter)
442      , name(rName)
443      , linenum(0)
444      {}
445
446      const string name;
447      int linenum;
448      const string where()
449      {
450        ostringstream os;
451        os << name << ":" << linenum;
452        return os.str();
453      }
454
455      void scanLine(string& line);
456      void scanStream(istream& in);
457    };
458
459    void CfgStreamParser::scanLine(string& line)
460    {
461      /* strip any leading whitespace */
462      size_t start = line.find_first_not_of(" \t\n\r");
463      if (start == string::npos)
464      {
465        /* blank line */
466        return;
467      }
468      if (line[start] == '#')
469      {
470        /* comment line */
471        return;
472      }
473      /* look for first whitespace or ':' after the option end */
474      size_t option_end = line.find_first_of(": \t\n\r",start);
475      string option = line.substr(start, option_end - start);
476
477      /* look for ':', eat up any whitespace first */
478      start = line.find_first_not_of(" \t\n\r", option_end);
479      if (start == string::npos)
480      {
481        /* error: badly formatted line */
482        error_reporter.warn(where()) << "line formatting error\n";
483        return;
484      }
485      if (line[start] != ':')
486      {
487        /* error: badly formatted line */
488        error_reporter.warn(where()) << "line formatting error\n";
489        return;
490      }
491
492      /* look for start of value string -- eat up any leading whitespace */
493      start = line.find_first_not_of(" \t\n\r", ++start);
494      if (start == string::npos)
495      {
496        /* error: badly formatted line */
497#if !NH_MV
498        error_reporter.warn(where()) << "line formatting error\n";
499#else
500        // HTM also allows empty parameters.
501#endif
502        return;
503      }
504
505      /* extract the value part, which may contain embedded spaces
506       * by searching for a word at a time, until we hit a comment or end of line */
507      size_t value_end = start;
508      do
509      {
510        if (line[value_end] == '#')
511        {
512          /* rest of line is a comment */
513          value_end--;
514          break;
515        }
516        value_end = line.find_first_of(" \t\n\r", value_end);
517        /* consume any white space, incase there is another word.
518         * any trailing whitespace will be removed shortly */
519        value_end = line.find_first_not_of(" \t\n\r", value_end);
520      } while (value_end != string::npos);
521      /* strip any trailing space from value*/
522      value_end = line.find_last_not_of(" \t\n\r", value_end);
523
524      string value;
525      if (value_end >= start)
526      {
527        value = line.substr(start, value_end +1 - start);
528      }
529      else
530      {
531        /* error: no value */
532#if !NH_MV
533        error_reporter.warn(where()) << "no value found\n";
534#else
535        // This is ok for HTM.
536#endif
537        return;
538      }
539
540      /* store the value in option */
541      storePair(true, false, option, value);
542    }
543
544    void CfgStreamParser::scanStream(istream& in)
545    {
546      do
547      {
548        linenum++;
549        string line;
550        getline(in, line);
551        scanLine(line);
552      } while(!!in);
553    }
554
555    /* for all options in opts, set their storage to their specified
556     * default value */
557    void setDefaults(Options& opts)
558    {
559      for(Options::NamesPtrList::iterator it = opts.opt_list.begin(); it != opts.opt_list.end(); it++)
560      {
561        (*it)->opt->setDefault();
562      }
563    }
564
565    void parseConfigFile(Options& opts, const string& filename, ErrorReporter& error_reporter)
566    {
567      ifstream cfgstream(filename.c_str(), ifstream::in);
568      if (!cfgstream)
569      {
570        error_reporter.error(filename) << "Failed to open config file\n";
571        return;
572      }
573      CfgStreamParser csp(filename, opts, error_reporter);
574      csp.scanStream(cfgstream);
575    }
576
577  }
578}
579
580//! \}
Note: See TracBrowser for help on using the repository browser.