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

Last change on this file since 100 was 56, checked in by hschwarz, 12 years ago

updated trunk (move to HM6.1)

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