source: 3DVCSoftware/trunk/source/Lib/TAppCommon/program_options_lite.cpp @ 1356

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

Merged 15.1-dev0-NICT@1355.

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