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

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

Merged 15.1-dev1@1381.

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