source: 3DVCSoftware/branches/HTM-DEV-0.3-dev0/source/Lib/TAppCommon/program_options_lite.cpp @ 500

Last change on this file since 500 was 500, checked in by tech, 11 years ago

Further fixes.

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