source: SHVCSoftware/trunk/source/Lib/TAppCommon/program_options_lite.cpp @ 854

Last change on this file since 854 was 595, checked in by seregin, 11 years ago

merge with SHM-5.0-dev branch

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