~drizzle-trunk/drizzle/development

« back to all changes in this revision

Viewing changes to drizzled/program_options/config_file.h

  • Committer: Stewart Smith
  • Date: 2011-01-14 05:20:34 UTC
  • mto: (2086.1.3 build)
  • mto: This revision was merged to the branch mainline in revision 2087.
  • Revision ID: stewart@flamingspork.com-20110114052034-b45vpdmk4a8l70c2
just a syntax fix

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * Copyright (C) 2002-2004 Vladimir Prus.
 
3
 * Copyright (C) 2010 Monty Taylor
 
4
 *
 
5
 * Distributed under the Boost Software License, Version 1.0.
 
6
 * (See accompanying file LICENSE_1_0.txt or copy at
 
7
 * http://www.boost.org/LICENSE_1_0.txt)
 
8
 */
 
9
 
 
10
#ifndef DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H
 
11
#define DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H
 
12
 
 
13
#include <boost/program_options.hpp>
 
14
#include <boost/program_options/eof_iterator.hpp>
 
15
#include <boost/static_assert.hpp>
 
16
#include <boost/type_traits/is_same.hpp>
 
17
#include <boost/shared_ptr.hpp>
 
18
#include <boost/algorithm/string.hpp>
 
19
 
 
20
#include <boost/noncopyable.hpp>
 
21
 
 
22
#include <iosfwd>
 
23
#include <vector>
 
24
#include <utility>
 
25
#include <set>
 
26
 
 
27
namespace drizzled
 
28
{
 
29
namespace program_options
 
30
{
 
31
 
 
32
typedef std::pair<std::string, std::string> option_result_pair;
 
33
std::string parse_suffix(const std::string& arg_val);
 
34
option_result_pair parse_size_suffixes(std::string s);
 
35
option_result_pair parse_size_arg(std::string s);
 
36
 
 
37
std::string parse_suffix(const std::string& arg_val)
 
38
{
 
39
  try
 
40
  {
 
41
    size_t size_suffix_pos= arg_val.find_last_of("kmgKMG");
 
42
    if (size_suffix_pos == arg_val.size()-1)
 
43
    {
 
44
      char suffix= arg_val[size_suffix_pos];
 
45
      std::string size_val(arg_val.substr(0, size_suffix_pos));
 
46
 
 
47
      uint64_t base_size= boost::lexical_cast<uint64_t>(size_val);
 
48
      uint64_t new_size= 0;
 
49
 
 
50
      switch (suffix)
 
51
      {
 
52
      case 'K':
 
53
      case 'k':
 
54
        new_size= base_size * 1024;
 
55
        break;
 
56
      case 'M':
 
57
      case 'm':
 
58
        new_size= base_size * 1024 * 1024;
 
59
        break;
 
60
      case 'G':
 
61
      case 'g':
 
62
        new_size= base_size * 1024 * 1024 * 1024;
 
63
        break;
 
64
      }
 
65
      return boost::lexical_cast<std::string>(new_size);
 
66
    }
 
67
  }
 
68
  catch (std::exception&)
 
69
  { }
 
70
 
 
71
  return arg_val;
 
72
}
 
73
 
 
74
option_result_pair parse_size_suffixes(std::string s)
 
75
{
 
76
  size_t equal_pos= s.find("=");
 
77
  if (equal_pos != std::string::npos)
 
78
  {
 
79
    std::string arg_key(s.substr(0, equal_pos));
 
80
    std::string arg_val(parse_suffix(s.substr(equal_pos+1)));
 
81
 
 
82
    if (arg_val != s.substr(equal_pos+1))
 
83
    {
 
84
      return std::make_pair(arg_key, arg_val);
 
85
    }
 
86
  }
 
87
 
 
88
  return std::make_pair(std::string(""), std::string(""));
 
89
}
 
90
 
 
91
option_result_pair parse_size_arg(std::string s)
 
92
{
 
93
  if (s.find("--") == 0)
 
94
  {
 
95
    return parse_size_suffixes(s.substr(2));
 
96
  }
 
97
  return make_pair(std::string(""), std::string(""));
 
98
}
 
99
 
 
100
class invalid_syntax :
 
101
  public boost::program_options::error
 
102
{
 
103
public:
 
104
  enum kind_t
 
105
  {
 
106
    long_not_allowed = 30,
 
107
    long_adjacent_not_allowed,
 
108
    short_adjacent_not_allowed,
 
109
    empty_adjacent_parameter,
 
110
    missing_parameter,
 
111
    extra_parameter,
 
112
    unrecognized_line
 
113
  };
 
114
 
 
115
  invalid_syntax(const std::string& in_tokens, kind_t in_kind);
 
116
 
 
117
 
 
118
  // gcc says that throw specification on dtor is loosened
 
119
  // without this line
 
120
  ~invalid_syntax() throw() {}
 
121
 
 
122
  kind_t kind() const
 
123
  {
 
124
    return m_kind;
 
125
  }
 
126
 
 
127
 
 
128
  const std::string& tokens() const
 
129
  {
 
130
    return m_tokens;
 
131
  }
 
132
 
 
133
 
 
134
protected:
 
135
  /** Used to convert kind_t to a related error text */
 
136
  static std::string error_message(kind_t kind)
 
137
  {
 
138
    // Initially, store the message in 'const char*' variable, to avoid
 
139
    // conversion to string in all cases.
 
140
    const char* msg;
 
141
    switch(kind)
 
142
    {
 
143
    case long_not_allowed:
 
144
      msg = "long options are not allowed";
 
145
      break;
 
146
    case long_adjacent_not_allowed:
 
147
      msg = "parameters adjacent to long options not allowed";
 
148
      break;
 
149
    case short_adjacent_not_allowed:
 
150
      msg = "parameters adjust to short options are not allowed";
 
151
      break;
 
152
    case empty_adjacent_parameter:
 
153
      msg = "adjacent parameter is empty";
 
154
      break;
 
155
    case missing_parameter:
 
156
      msg = "required parameter is missing";
 
157
      break;
 
158
    case extra_parameter:
 
159
      msg = "extra parameter";
 
160
      break;
 
161
    case unrecognized_line:
 
162
      msg = "unrecognized line";
 
163
      break;
 
164
    default:
 
165
      msg = "unknown error";
 
166
    }
 
167
    return msg;
 
168
  }
 
169
 
 
170
private:
 
171
  // TODO: copy ctor might throw
 
172
  std::string m_tokens;
 
173
 
 
174
  kind_t m_kind;
 
175
};
 
176
 
 
177
invalid_syntax::invalid_syntax(const std::string& in_tokens,
 
178
                               invalid_syntax::kind_t in_kind) :
 
179
  boost::program_options::error(error_message(in_kind).append(" in '").append(in_tokens).append("'")),
 
180
  m_tokens(in_tokens),
 
181
  m_kind(in_kind)
 
182
{ }
 
183
 
 
184
namespace detail
 
185
{
 
186
 
 
187
/** Standalone parser for config files in ini-line format.
 
188
  The parser is a model of single-pass lvalue iterator, and
 
189
  default constructor creates past-the-end-iterator. The typical usage is:
 
190
  config_file_iterator i(is, ... set of options ...), e;
 
191
  for(; i !=e; ++i) {
 
192
 *i;
 
193
 }
 
194
 
 
195
 Syntax conventions:
 
196
 
 
197
 - config file can not contain positional options
 
198
 - '#' is comment character: it is ignored together with
 
199
 the rest of the line.
 
200
 - variable assignments are in the form
 
201
 name '=' value.
 
202
 spaces around '=' are trimmed.
 
203
 - Section names are given in brackets. 
 
204
 
 
205
 The actual option name is constructed by combining current section
 
206
 name and specified option name, with dot between. If section_name 
 
207
 already contains dot at the end, new dot is not inserted. For example:
 
208
 @verbatim
 
209
 [gui.accessibility]
 
210
 visual_bell=yes
 
211
 @endverbatim
 
212
 will result in option "gui.accessibility.visual_bell" with value
 
213
 "yes" been returned.
 
214
 
 
215
 */    
 
216
class common_config_file_iterator :
 
217
  public boost::eof_iterator<common_config_file_iterator,
 
218
                             boost::program_options::option>
 
219
{
 
220
public:
 
221
  common_config_file_iterator()
 
222
  {
 
223
    found_eof();
 
224
  }
 
225
 
 
226
  common_config_file_iterator(const std::set<std::string>& in_allowed_options,
 
227
                              bool allow_unregistered) :
 
228
    allowed_options(in_allowed_options),
 
229
    m_allow_unregistered(allow_unregistered)
 
230
  {
 
231
    for(std::set<std::string>::const_iterator i = allowed_options.begin();
 
232
        i != allowed_options.end(); 
 
233
        ++i)
 
234
    {
 
235
      add_option(i->c_str());
 
236
    }
 
237
  }
 
238
 
 
239
  virtual ~common_config_file_iterator() {}
 
240
 
 
241
public: // Method required by eof_iterator
 
242
 
 
243
  void get()
 
244
  {
 
245
    std::string s;
 
246
    std::string::size_type n;
 
247
    bool found = false;
 
248
 
 
249
    while(this->getline(s)) {
 
250
 
 
251
      // strip '#' comments and whitespace
 
252
      if ((n = s.find('#')) != std::string::npos)
 
253
        s = s.substr(0, n);
 
254
      boost::trim(s);
 
255
 
 
256
      if (!s.empty()) {
 
257
        // Handle section name
 
258
        if (*s.begin() == '[' && *s.rbegin() == ']')
 
259
        {
 
260
          m_prefix = s.substr(1, s.size()-2);
 
261
          if (*m_prefix.rbegin() != '.')
 
262
            m_prefix += '.';
 
263
        }
 
264
        else
 
265
        {
 
266
          
 
267
          std::string name;
 
268
          std::string option_value("true");
 
269
 
 
270
          if ((n = s.find('=')) != std::string::npos)
 
271
          {
 
272
 
 
273
            name = m_prefix + boost::trim_copy(s.substr(0, n));
 
274
            option_value = boost::trim_copy(parse_suffix(s.substr(n+1)));
 
275
 
 
276
          }
 
277
          else
 
278
          {
 
279
            name = m_prefix + boost::trim_copy(s);
 
280
          }
 
281
 
 
282
          bool registered = allowed_option(name);
 
283
          if (!registered && !m_allow_unregistered)
 
284
            boost::throw_exception(boost::program_options::unknown_option(name));
 
285
 
 
286
          found = true;
 
287
          this->value().string_key = name;
 
288
          this->value().value.clear();
 
289
          this->value().value.push_back(option_value);
 
290
          this->value().unregistered = !registered;
 
291
          this->value().original_tokens.clear();
 
292
          this->value().original_tokens.push_back(name);
 
293
          this->value().original_tokens.push_back(option_value);
 
294
          break;
 
295
 
 
296
        }
 
297
      }
 
298
    }
 
299
    if (!found)
 
300
      found_eof();
 
301
  }
 
302
 
 
303
protected: // Stubs for derived classes
 
304
 
 
305
  // Obtains next line from the config file
 
306
  // Note: really, this design is a bit ugly
 
307
  // The most clean thing would be to pass 'line_iterator' to
 
308
  // constructor of this class, but to avoid templating this class
 
309
  // we'd need polymorphic iterator, which does not exist yet.
 
310
  virtual bool getline(std::string&) { return false; }
 
311
 
 
312
private:
 
313
  /** Adds another allowed option. If the 'name' ends with
 
314
    '*', then all options with the same prefix are
 
315
    allowed. For example, if 'name' is 'foo*', then 'foo1' and
 
316
    'foo_bar' are allowed. */
 
317
  void add_option(const char* name)
 
318
  {
 
319
    std::string s(name);
 
320
    assert(!s.empty());
 
321
    if (*s.rbegin() == '*')
 
322
    {
 
323
      s.resize(s.size()-1);
 
324
      bool bad_prefixes(false);
 
325
      // If 's' is a prefix of one of allowed suffix, then
 
326
      // lower_bound will return that element.
 
327
      // If some element is prefix of 's', then lower_bound will
 
328
      // return the next element.
 
329
      std::set<std::string>::iterator i = allowed_prefixes.lower_bound(s);
 
330
      if (i != allowed_prefixes.end())
 
331
      {
 
332
        if (i->find(s) == 0)
 
333
          bad_prefixes = true;                    
 
334
      }
 
335
      if (i != allowed_prefixes.begin())
 
336
      {
 
337
        --i;
 
338
        if (s.find(*i) == 0)
 
339
          bad_prefixes = true;
 
340
      }
 
341
      if (bad_prefixes)
 
342
        boost::throw_exception(boost::program_options::error("bad prefixes"));
 
343
      allowed_prefixes.insert(s);
 
344
    }
 
345
  }
 
346
 
 
347
 
 
348
  // Returns true if 's' is a registered option name.
 
349
  bool allowed_option(const std::string& s) const
 
350
  {
 
351
    std::set<std::string>::const_iterator i = allowed_options.find(s);
 
352
    if (i != allowed_options.end())
 
353
      return true;        
 
354
    // If s is "pa" where "p" is allowed prefix then
 
355
    // lower_bound should find the element after "p". 
 
356
    // This depends on 'allowed_prefixes' invariant.
 
357
    i = allowed_prefixes.lower_bound(s);
 
358
    if (i != allowed_prefixes.begin() && s.find(*--i) == 0)
 
359
      return true;
 
360
    return false;
 
361
  }
 
362
 
 
363
 
 
364
  // That's probably too much data for iterator, since
 
365
  // it will be copied, but let's not bother for now.
 
366
  std::set<std::string> allowed_options;
 
367
  // Invariant: no element is prefix of other element.
 
368
  std::set<std::string> allowed_prefixes;
 
369
  std::string m_prefix;
 
370
  bool m_allow_unregistered;
 
371
};
 
372
 
 
373
template<class charT>
 
374
class basic_config_file_iterator :
 
375
  public common_config_file_iterator
 
376
{
 
377
public:
 
378
 
 
379
  basic_config_file_iterator()
 
380
  {
 
381
    found_eof();
 
382
  }
 
383
 
 
384
  /** Creates a config file parser for the specified stream. */
 
385
  basic_config_file_iterator(std::basic_istream<charT>& is, 
 
386
                             const std::set<std::string>& allowed_options,
 
387
                             bool allow_unregistered = false); 
 
388
 
 
389
private: // base overrides
 
390
 
 
391
  bool getline(std::string&);
 
392
 
 
393
private: // internal data
 
394
  boost::shared_ptr<std::basic_istream<charT> > is;
 
395
};
 
396
 
 
397
typedef basic_config_file_iterator<char> config_file_iterator;
 
398
typedef basic_config_file_iterator<wchar_t> wconfig_file_iterator;
 
399
 
 
400
struct null_deleter
 
401
{
 
402
  void operator()(void const *) const {}
 
403
};
 
404
 
 
405
 
 
406
template<class charT>
 
407
basic_config_file_iterator<charT>::
 
408
basic_config_file_iterator(std::basic_istream<charT>& in_is, 
 
409
                           const std::set<std::string>& in_allowed_options,
 
410
                           bool in_allow_unregistered) :
 
411
  common_config_file_iterator(in_allowed_options, in_allow_unregistered)
 
412
{
 
413
  this->is.reset(&in_is, null_deleter());                 
 
414
  get();
 
415
}
 
416
 
 
417
 
 
418
// Specializing this function for wchar_t causes problems on
 
419
// borland and vc7, as well as on metrowerks. On the first two
 
420
// I don't know a workaround, so make use of 'to_internal' to
 
421
// avoid specialization.
 
422
template<class charT>
 
423
bool
 
424
basic_config_file_iterator<charT>::getline(std::string& s)
 
425
{
 
426
  if (std::getline(*is, s))
 
427
  {
 
428
    return true;
 
429
  }
 
430
  else
 
431
  {
 
432
    return false;
 
433
  }
 
434
}
 
435
 
 
436
} /* namespace detail */
 
437
 
 
438
/** Parse a config file. 
 
439
 
 
440
  Read from given stream.
 
441
*/
 
442
template<class charT>
 
443
boost::program_options::basic_parsed_options<charT>
 
444
parse_config_file(std::basic_istream<charT>& is,
 
445
                  const boost::program_options::options_description& desc,
 
446
                  bool allow_unregistered = false)
 
447
{    
 
448
  std::set<std::string> allowed_options;
 
449
 
 
450
  const std::vector<boost::shared_ptr<boost::program_options::option_description> >& options = desc.options();
 
451
  for (unsigned i = 0; i < options.size(); ++i)
 
452
  {
 
453
    const boost::program_options::option_description& d= *options[i];
 
454
 
 
455
    if (d.long_name().empty())
 
456
      boost::throw_exception(boost::program_options::error("long name required for config file"));
 
457
 
 
458
    allowed_options.insert(d.long_name());
 
459
  }
 
460
 
 
461
  // Parser return char strings
 
462
  boost::program_options::parsed_options result(&desc);        
 
463
  std::copy(detail::basic_config_file_iterator<charT>(is,
 
464
                                                      allowed_options,
 
465
                                                      allow_unregistered), 
 
466
       detail::basic_config_file_iterator<charT>(), 
 
467
       std::back_inserter(result.options));
 
468
  // Convert char strings into desired type.
 
469
  return boost::program_options::basic_parsed_options<charT>(result);
 
470
}
 
471
 
 
472
/** Parse a config file. 
 
473
 
 
474
  Read from file with the given name. The character type is
 
475
  passed to the file stream. 
 
476
*/
 
477
template<class charT>
 
478
boost::program_options::basic_parsed_options<charT>
 
479
parse_config_file(const char* filename,
 
480
                  const boost::program_options::options_description& desc,
 
481
                  bool allow_unregistered = false)
 
482
 
483
  // Parser return char strings
 
484
  std::basic_ifstream< charT > strm(filename);
 
485
  if (!strm) 
 
486
  {
 
487
    boost::throw_exception("Couldn't open file");
 
488
  }
 
489
  return parse_config_file(strm, desc, allow_unregistered);
 
490
}
 
491
 
 
492
} /* namespace program_options */
 
493
} /* namespace drizzled */
 
494
 
 
495
#endif /* DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H */
 
496