/* * Copyright (c) 2002-2004 Vladimir Prus. * Copyright (c) 2010 Monty Taylor * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE_1_0.txt or copy at * http://www.boost.org/LICENSE_1_0.txt) */ #ifndef DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H #define DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H #include #include #include #include #include #include #include #include #include #include #include namespace drizzled { namespace program_options { typedef std::pair option_result_pair; std::string parse_suffix(const std::string& arg_val); option_result_pair parse_size_suffixes(std::string s); option_result_pair parse_size_arg(std::string s); std::string parse_suffix(const std::string& arg_val) { try { size_t size_suffix_pos= arg_val.find_last_of("kmgKMG"); if (size_suffix_pos == arg_val.size()-1) { char suffix= arg_val[size_suffix_pos]; std::string size_val(arg_val.substr(0, size_suffix_pos)); uint64_t base_size= boost::lexical_cast(size_val); uint64_t new_size= 0; switch (suffix) { case 'K': case 'k': new_size= base_size * 1024; break; case 'M': case 'm': new_size= base_size * 1024 * 1024; break; case 'G': case 'g': new_size= base_size * 1024 * 1024 * 1024; break; } return boost::lexical_cast(new_size); } } catch (...) { } return arg_val; } option_result_pair parse_size_suffixes(std::string s) { size_t equal_pos= s.find("="); if (equal_pos != std::string::npos) { std::string arg_key(s.substr(0, equal_pos)); std::string arg_val(parse_suffix(s.substr(equal_pos+1))); if (arg_val != s.substr(equal_pos+1)) { return std::make_pair(arg_key, arg_val); } } return std::make_pair(std::string(""), std::string("")); } option_result_pair parse_size_arg(std::string s) { if (s.find("--") == 0) { return parse_size_suffixes(s.substr(2)); } return make_pair(std::string(""), std::string("")); } class invalid_syntax : public boost::program_options::error { public: enum kind_t { long_not_allowed = 30, long_adjacent_not_allowed, short_adjacent_not_allowed, empty_adjacent_parameter, missing_parameter, extra_parameter, unrecognized_line }; invalid_syntax(const std::string& in_tokens, kind_t in_kind); // gcc says that throw specification on dtor is loosened // without this line ~invalid_syntax() throw() {} kind_t kind() const { return m_kind; } const std::string& tokens() const { return m_tokens; } protected: /** Used to convert kind_t to a related error text */ static std::string error_message(kind_t kind) { // Initially, store the message in 'const char*' variable, to avoid // conversion to string in all cases. const char* msg; switch(kind) { case long_not_allowed: msg = "long options are not allowed"; break; case long_adjacent_not_allowed: msg = "parameters adjacent to long options not allowed"; break; case short_adjacent_not_allowed: msg = "parameters adjust to short options are not allowed"; break; case empty_adjacent_parameter: msg = "adjacent parameter is empty"; break; case missing_parameter: msg = "required parameter is missing"; break; case extra_parameter: msg = "extra parameter"; break; case unrecognized_line: msg = "unrecognized line"; break; default: msg = "unknown error"; } return msg; } private: // TODO: copy ctor might throw std::string m_tokens; kind_t m_kind; }; invalid_syntax::invalid_syntax(const std::string& in_tokens, invalid_syntax::kind_t in_kind) : boost::program_options::error(error_message(in_kind).append(" in '").append(in_tokens).append("'")), m_tokens(in_tokens), m_kind(in_kind) { } namespace detail { /** Standalone parser for config files in ini-line format. The parser is a model of single-pass lvalue iterator, and default constructor creates past-the-end-iterator. The typical usage is: config_file_iterator i(is, ... set of options ...), e; for(; i !=e; ++i) { *i; } Syntax conventions: - config file can not contain positional options - '#' is comment character: it is ignored together with the rest of the line. - variable assignments are in the form name '=' value. spaces around '=' are trimmed. - Section names are given in brackets. The actual option name is constructed by combining current section name and specified option name, with dot between. If section_name already contains dot at the end, new dot is not inserted. For example: @verbatim [gui.accessibility] visual_bell=yes @endverbatim will result in option "gui.accessibility.visual_bell" with value "yes" been returned. */ class common_config_file_iterator : public boost::eof_iterator { public: common_config_file_iterator() { found_eof(); } common_config_file_iterator(const std::set& in_allowed_options, bool allow_unregistered) : allowed_options(in_allowed_options), m_allow_unregistered(allow_unregistered) { for(std::set::const_iterator i = allowed_options.begin(); i != allowed_options.end(); ++i) { add_option(i->c_str()); } } virtual ~common_config_file_iterator() {} public: // Method required by eof_iterator void get() { std::string s; std::string::size_type n; bool found = false; while(this->getline(s)) { // strip '#' comments and whitespace if ((n = s.find('#')) != std::string::npos) s = s.substr(0, n); boost::trim(s); if (!s.empty()) { // Handle section name if (*s.begin() == '[' && *s.rbegin() == ']') { m_prefix = s.substr(1, s.size()-2); if (*m_prefix.rbegin() != '.') m_prefix += '.'; } else { std::string name; std::string option_value("true"); if ((n = s.find('=')) != std::string::npos) { name = m_prefix + boost::trim_copy(s.substr(0, n)); option_value = boost::trim_copy(parse_suffix(s.substr(n+1))); } else { name = m_prefix + boost::trim_copy(s); } bool registered = allowed_option(name); if (!registered && !m_allow_unregistered) boost::throw_exception(boost::program_options::unknown_option(name)); found = true; this->value().string_key = name; this->value().value.clear(); this->value().value.push_back(option_value); this->value().unregistered = !registered; this->value().original_tokens.clear(); this->value().original_tokens.push_back(name); this->value().original_tokens.push_back(option_value); break; } } } if (!found) found_eof(); } protected: // Stubs for derived classes // Obtains next line from the config file // Note: really, this design is a bit ugly // The most clean thing would be to pass 'line_iterator' to // constructor of this class, but to avoid templating this class // we'd need polymorphic iterator, which does not exist yet. virtual bool getline(std::string&) { return false; } private: /** Adds another allowed option. If the 'name' ends with '*', then all options with the same prefix are allowed. For example, if 'name' is 'foo*', then 'foo1' and 'foo_bar' are allowed. */ void add_option(const char* name) { std::string s(name); assert(!s.empty()); if (*s.rbegin() == '*') { s.resize(s.size()-1); bool bad_prefixes(false); // If 's' is a prefix of one of allowed suffix, then // lower_bound will return that element. // If some element is prefix of 's', then lower_bound will // return the next element. std::set::iterator i = allowed_prefixes.lower_bound(s); if (i != allowed_prefixes.end()) { if (i->find(s) == 0) bad_prefixes = true; } if (i != allowed_prefixes.begin()) { --i; if (s.find(*i) == 0) bad_prefixes = true; } if (bad_prefixes) boost::throw_exception(boost::program_options::error("bad prefixes")); allowed_prefixes.insert(s); } } // Returns true if 's' is a registered option name. bool allowed_option(const std::string& s) const { std::set::const_iterator i = allowed_options.find(s); if (i != allowed_options.end()) return true; // If s is "pa" where "p" is allowed prefix then // lower_bound should find the element after "p". // This depends on 'allowed_prefixes' invariant. i = allowed_prefixes.lower_bound(s); if (i != allowed_prefixes.begin() && s.find(*--i) == 0) return true; return false; } // That's probably too much data for iterator, since // it will be copied, but let's not bother for now. std::set allowed_options; // Invariant: no element is prefix of other element. std::set allowed_prefixes; std::string m_prefix; bool m_allow_unregistered; }; template class basic_config_file_iterator : public common_config_file_iterator { public: basic_config_file_iterator() { found_eof(); } /** Creates a config file parser for the specified stream. */ basic_config_file_iterator(std::basic_istream& is, const std::set& allowed_options, bool allow_unregistered = false); private: // base overrides bool getline(std::string&); private: // internal data boost::shared_ptr > is; }; typedef basic_config_file_iterator config_file_iterator; typedef basic_config_file_iterator wconfig_file_iterator; struct null_deleter { void operator()(void const *) const {} }; template basic_config_file_iterator:: basic_config_file_iterator(std::basic_istream& in_is, const std::set& in_allowed_options, bool in_allow_unregistered) : common_config_file_iterator(in_allowed_options, in_allow_unregistered) { this->is.reset(&in_is, null_deleter()); get(); } // Specializing this function for wchar_t causes problems on // borland and vc7, as well as on metrowerks. On the first two // I don't know a workaround, so make use of 'to_internal' to // avoid specialization. template bool basic_config_file_iterator::getline(std::string& s) { if (std::getline(*is, s)) { return true; } else { return false; } } } /* namespace detail */ /** Parse a config file. Read from given stream. */ template boost::program_options::basic_parsed_options parse_config_file(std::basic_istream& is, const boost::program_options::options_description& desc, bool allow_unregistered = false) { std::set allowed_options; const std::vector >& options = desc.options(); for (unsigned i = 0; i < options.size(); ++i) { const boost::program_options::option_description& d= *options[i]; if (d.long_name().empty()) boost::throw_exception(boost::program_options::error("long name required for config file")); allowed_options.insert(d.long_name()); } // Parser return char strings boost::program_options::parsed_options result(&desc); std::copy(detail::basic_config_file_iterator(is, allowed_options, allow_unregistered), detail::basic_config_file_iterator(), std::back_inserter(result.options)); // Convert char strings into desired type. return boost::program_options::basic_parsed_options(result); } /** Parse a config file. Read from file with the given name. The character type is passed to the file stream. */ template boost::program_options::basic_parsed_options parse_config_file(const char* filename, const boost::program_options::options_description& desc, bool allow_unregistered = false) { // Parser return char strings std::basic_ifstream< charT > strm(filename); if (!strm) { boost::throw_exception("Couldn't open file"); } return parse_config_file(strm, desc, allow_unregistered); } } /* namespace program_options */ } /* namespace drizzled */ #endif /* DRIZZLED_PROGRAM_OPTIONS_CONFIG_FILE_H */