source: 3DVCSoftware/branches/0.1-poznan-univ/source/App/TAppCommon/program_options_lite.cpp

Last change on this file was 2, checked in by hhi, 13 years ago

inital import

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