~drizzle-trunk/drizzle/development

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