2
* Copyright (c) 2002-2004 Vladimir Prus.
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)
9
#ifndef DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H
10
#define DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H
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>
17
#include <boost/algorithm/string.hpp>
19
#include <boost/noncopyable.hpp>
28
namespace program_options
31
class invalid_syntax :
32
public boost::program_options::error
36
long_not_allowed = 30,
37
long_adjacent_not_allowed,
38
short_adjacent_not_allowed,
39
empty_adjacent_parameter,
45
invalid_syntax(const std::string& in_tokens, kind_t in_kind);
48
// gcc says that throw specification on dtor is loosened
50
~invalid_syntax() throw() {}
58
const std::string& tokens() const
65
/** Used to convert kind_t to a related error text */
66
static std::string error_message(kind_t kind)
68
// Initially, store the message in 'const char*' variable, to avoid
69
// conversion to string in all cases.
73
case long_not_allowed:
74
msg = "long options are not allowed";
76
case long_adjacent_not_allowed:
77
msg = "parameters adjacent to long options not allowed";
79
case short_adjacent_not_allowed:
80
msg = "parameters adjust to short options are not allowed";
82
case empty_adjacent_parameter:
83
msg = "adjacent parameter is empty";
85
case missing_parameter:
86
msg = "required parameter is missing";
89
msg = "extra parameter";
91
case unrecognized_line:
92
msg = "unrecognized line";
95
msg = "unknown error";
101
// TODO: copy ctor might throw
102
std::string m_tokens;
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)
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;
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
132
spaces around '=' are trimmed.
133
- Section names are given in brackets.
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:
142
will result in option "gui.accessibility.visual_bell" with value
146
class common_config_file_iterator :
147
public boost::eof_iterator<common_config_file_iterator,
148
boost::program_options::option>
151
common_config_file_iterator()
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)
161
for(std::set<std::string>::const_iterator i = allowed_options.begin();
162
i != allowed_options.end();
165
add_option(i->c_str());
169
virtual ~common_config_file_iterator() {}
171
public: // Method required by eof_iterator
176
std::string::size_type n;
179
while(this->getline(s)) {
181
// strip '#' comments and whitespace
182
if ((n = s.find('#')) != std::string::npos)
187
// Handle section name
188
if (*s.begin() == '[' && *s.rbegin() == ']') {
189
m_prefix = s.substr(1, s.size()-2);
190
if (*m_prefix.rbegin() != '.')
193
else if ((n = s.find('=')) != std::string::npos) {
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));
198
bool registered = allowed_option(name);
199
if (!registered && !m_allow_unregistered)
200
boost::throw_exception(boost::program_options::unknown_option(name));
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);
213
boost::throw_exception(invalid_syntax(s, invalid_syntax::unrecognized_line));
221
protected: // Stubs for derived classes
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; }
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)
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()) {
251
if (i != allowed_prefixes.begin()) {
257
boost::throw_exception(boost::program_options::error("bad prefixes"));
258
allowed_prefixes.insert(s);
263
// Returns true if 's' is a registered option name.
264
bool allowed_option(const std::string& s) const
266
std::set<std::string>::const_iterator i = allowed_options.find(s);
267
if (i != allowed_options.end())
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)
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;
288
template<class charT>
289
class basic_config_file_iterator :
290
public common_config_file_iterator
294
basic_config_file_iterator()
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);
304
private: // base overrides
306
bool getline(std::string&);
308
private: // internal data
309
boost::shared_ptr<std::basic_istream<charT> > is;
312
typedef basic_config_file_iterator<char> config_file_iterator;
313
typedef basic_config_file_iterator<wchar_t> wconfig_file_iterator;
317
void operator()(void const *) const {}
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)
328
this->is.reset(&in_is, null_deleter());
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>
339
basic_config_file_iterator<charT>::getline(std::string& s)
341
if (std::getline(*is, s)) {
348
} /* namespace detail */
350
/** Parse a config file.
352
Read from given stream.
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)
360
std::set<std::string> allowed_options;
362
const std::vector<boost::shared_ptr<boost::program_options::option_description> >& options = desc.options();
363
for (unsigned i = 0; i < options.size(); ++i)
365
const boost::program_options::option_description& d= *options[i];
367
if (d.long_name().empty())
368
boost::throw_exception(
369
boost::program_options::error("long name required for config file"));
371
allowed_options.insert(d.long_name());
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);
384
/** Parse a config file.
386
Read from file with the given name. The character type is
387
passed to the file stream.
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)
395
// Parser return char strings
396
std::basic_ifstream< charT > strm(filename);
399
boost::throw_exception("Couldn't open file");
401
return parse_config_file(strm, desc, allow_unregistered);
404
} /* namespace program_options */
405
} /* namespace drizzled */
407
#endif /* DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H */