source: 3DVCSoftware/branches/0.3-poznan-univ/source/App/TAppCommon/program_options_lite.cpp @ 27

Last change on this file since 27 was 5, checked in by hhi, 13 years ago

Clean version with cfg-files

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