~drizzle-trunk/drizzle/development

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