Drizzled Public API Documentation

config_file.h
00001 /*
00002  * Copyright (C) 2002-2004 Vladimir Prus.
00003  * Copyright (C) 2010 Monty Taylor
00004  *
00005  * Distributed under the Boost Software License, Version 1.0.
00006  * (See accompanying file LICENSE_1_0.txt or copy at
00007  * http://www.boost.org/LICENSE_1_0.txt)
00008  */
00009 
00010 #pragma once
00011 
00012 #include <boost/program_options.hpp>
00013 #include <boost/program_options/eof_iterator.hpp>
00014 #include <boost/static_assert.hpp>
00015 #include <boost/type_traits/is_same.hpp>
00016 #include <boost/shared_ptr.hpp>
00017 #include <boost/algorithm/string.hpp>
00018 
00019 #include <boost/noncopyable.hpp>
00020 
00021 #include <iosfwd>
00022 #include <vector>
00023 #include <utility>
00024 #include <set>
00025 
00026 namespace drizzled
00027 {
00028 namespace program_options
00029 {
00030 
00031 typedef std::pair<std::string, std::string> option_result_pair;
00032 std::string parse_suffix(const std::string& arg_val);
00033 option_result_pair parse_size_suffixes(std::string s);
00034 option_result_pair parse_size_arg(std::string s);
00035 
00036 std::string parse_suffix(const std::string& arg_val)
00037 {
00038   try
00039   {
00040     size_t size_suffix_pos= arg_val.find_last_of("kmgKMG");
00041     if (size_suffix_pos == arg_val.size()-1)
00042     {
00043       char suffix= arg_val[size_suffix_pos];
00044       std::string size_val(arg_val.substr(0, size_suffix_pos));
00045 
00046       uint64_t base_size= boost::lexical_cast<uint64_t>(size_val);
00047       uint64_t new_size= 0;
00048 
00049       switch (suffix)
00050       {
00051       case 'K':
00052       case 'k':
00053         new_size= base_size * 1024;
00054         break;
00055       case 'M':
00056       case 'm':
00057         new_size= base_size * 1024 * 1024;
00058         break;
00059       case 'G':
00060       case 'g':
00061         new_size= base_size * 1024 * 1024 * 1024;
00062         break;
00063       }
00064       return boost::lexical_cast<std::string>(new_size);
00065     }
00066   }
00067   catch (std::exception&)
00068   { }
00069 
00070   return arg_val;
00071 }
00072 
00073 option_result_pair parse_size_suffixes(std::string s)
00074 {
00075   size_t equal_pos= s.find("=");
00076   if (equal_pos != std::string::npos)
00077   {
00078     std::string arg_key(s.substr(0, equal_pos));
00079     std::string arg_val(parse_suffix(s.substr(equal_pos+1)));
00080 
00081     if (arg_val != s.substr(equal_pos+1))
00082     {
00083       return std::make_pair(arg_key, arg_val);
00084     }
00085   }
00086 
00087   return std::make_pair(std::string(""), std::string(""));
00088 }
00089 
00090 option_result_pair parse_size_arg(std::string s)
00091 {
00092   if (s.find("--") == 0)
00093   {
00094     return parse_size_suffixes(s.substr(2));
00095   }
00096   return make_pair(std::string(""), std::string(""));
00097 }
00098 
00099 class invalid_syntax :
00100   public boost::program_options::error
00101 {
00102 public:
00103   enum kind_t
00104   {
00105     long_not_allowed = 30,
00106     long_adjacent_not_allowed,
00107     short_adjacent_not_allowed,
00108     empty_adjacent_parameter,
00109     missing_parameter,
00110     extra_parameter,
00111     unrecognized_line
00112   };
00113 
00114   invalid_syntax(const std::string& in_tokens, kind_t in_kind);
00115 
00116 
00117   // gcc says that throw specification on dtor is loosened
00118   // without this line
00119   ~invalid_syntax() throw() {}
00120 
00121   kind_t kind() const
00122   {
00123     return m_kind;
00124   }
00125 
00126 
00127   const std::string& tokens() const
00128   {
00129     return m_tokens;
00130   }
00131 
00132 
00133 protected:
00135   static std::string error_message(kind_t kind)
00136   {
00137     // Initially, store the message in 'const char*' variable, to avoid
00138     // conversion to string in all cases.
00139     const char* msg;
00140     switch(kind)
00141     {
00142     case long_not_allowed:
00143       msg = "long options are not allowed";
00144       break;
00145     case long_adjacent_not_allowed:
00146       msg = "parameters adjacent to long options not allowed";
00147       break;
00148     case short_adjacent_not_allowed:
00149       msg = "parameters adjust to short options are not allowed";
00150       break;
00151     case empty_adjacent_parameter:
00152       msg = "adjacent parameter is empty";
00153       break;
00154     case missing_parameter:
00155       msg = "required parameter is missing";
00156       break;
00157     case extra_parameter:
00158       msg = "extra parameter";
00159       break;
00160     case unrecognized_line:
00161       msg = "unrecognized line";
00162       break;
00163     default:
00164       msg = "unknown error";
00165     }
00166     return msg;
00167   }
00168 
00169 private:
00170   // TODO: copy ctor might throw
00171   std::string m_tokens;
00172 
00173   kind_t m_kind;
00174 };
00175 
00176 invalid_syntax::invalid_syntax(const std::string& in_tokens,
00177                                invalid_syntax::kind_t in_kind) :
00178   boost::program_options::error(error_message(in_kind).append(" in '").append(in_tokens).append("'")),
00179   m_tokens(in_tokens),
00180   m_kind(in_kind)
00181 { }
00182 
00183 namespace detail
00184 {
00185 
00215 class common_config_file_iterator :
00216   public boost::eof_iterator<common_config_file_iterator,
00217                              boost::program_options::option>
00218 {
00219 public:
00220   common_config_file_iterator()
00221   {
00222     found_eof();
00223   }
00224 
00225   common_config_file_iterator(const std::set<std::string>& in_allowed_options,
00226                               bool allow_unregistered) :
00227     allowed_options(in_allowed_options),
00228     m_allow_unregistered(allow_unregistered)
00229   {
00230     for(std::set<std::string>::const_iterator i = allowed_options.begin();
00231         i != allowed_options.end(); 
00232         ++i)
00233     {
00234       add_option(i->c_str());
00235     }
00236   }
00237 
00238   virtual ~common_config_file_iterator() {}
00239 
00240 public: // Method required by eof_iterator
00241 
00242   void get()
00243   {
00244     std::string s;
00245     std::string::size_type n;
00246     bool found = false;
00247 
00248     while(this->getline(s)) {
00249 
00250       // strip '#' comments and whitespace
00251       if ((n = s.find('#')) != std::string::npos)
00252         s = s.substr(0, n);
00253       boost::trim(s);
00254 
00255       if (!s.empty()) {
00256         // Handle section name
00257         if (*s.begin() == '[' && *s.rbegin() == ']')
00258         {
00259           m_prefix = s.substr(1, s.size()-2);
00260           if (*m_prefix.rbegin() != '.')
00261             m_prefix += '.';
00262         }
00263         else
00264         {
00265           
00266           std::string name;
00267           std::string option_value("true");
00268 
00269           if ((n = s.find('=')) != std::string::npos)
00270           {
00271 
00272             name = m_prefix + boost::trim_copy(s.substr(0, n));
00273             option_value = boost::trim_copy(parse_suffix(s.substr(n+1)));
00274 
00275           }
00276           else
00277           {
00278             name = m_prefix + boost::trim_copy(s);
00279           }
00280 
00281           bool registered = allowed_option(name);
00282           if (!registered && !m_allow_unregistered)
00283             boost::throw_exception(boost::program_options::unknown_option(name));
00284 
00285           found = true;
00286           this->value().string_key = name;
00287           this->value().value.clear();
00288           this->value().value.push_back(option_value);
00289           this->value().unregistered = !registered;
00290           this->value().original_tokens.clear();
00291           this->value().original_tokens.push_back(name);
00292           this->value().original_tokens.push_back(option_value);
00293           break;
00294 
00295         }
00296       }
00297     }
00298     if (!found)
00299       found_eof();
00300   }
00301 
00302 protected: // Stubs for derived classes
00303 
00304   // Obtains next line from the config file
00305   // Note: really, this design is a bit ugly
00306   // The most clean thing would be to pass 'line_iterator' to
00307   // constructor of this class, but to avoid templating this class
00308   // we'd need polymorphic iterator, which does not exist yet.
00309   virtual bool getline(std::string&) { return false; }
00310 
00311 private:
00316   void add_option(const char* name)
00317   {
00318     std::string s(name);
00319     assert(!s.empty());
00320     if (*s.rbegin() == '*')
00321     {
00322       s.resize(s.size()-1);
00323       bool bad_prefixes(false);
00324       // If 's' is a prefix of one of allowed suffix, then
00325       // lower_bound will return that element.
00326       // If some element is prefix of 's', then lower_bound will
00327       // return the next element.
00328       std::set<std::string>::iterator i = allowed_prefixes.lower_bound(s);
00329       if (i != allowed_prefixes.end())
00330       {
00331         if (i->find(s) == 0)
00332           bad_prefixes = true;                    
00333       }
00334       if (i != allowed_prefixes.begin())
00335       {
00336         --i;
00337         if (s.find(*i) == 0)
00338           bad_prefixes = true;
00339       }
00340       if (bad_prefixes)
00341         boost::throw_exception(boost::program_options::error("bad prefixes"));
00342       allowed_prefixes.insert(s);
00343     }
00344   }
00345 
00346 
00347   // Returns true if 's' is a registered option name.
00348   bool allowed_option(const std::string& s) const
00349   {
00350     std::set<std::string>::const_iterator i = allowed_options.find(s);
00351     if (i != allowed_options.end())
00352       return true;        
00353     // If s is "pa" where "p" is allowed prefix then
00354     // lower_bound should find the element after "p". 
00355     // This depends on 'allowed_prefixes' invariant.
00356     i = allowed_prefixes.lower_bound(s);
00357     if (i != allowed_prefixes.begin() && s.find(*--i) == 0)
00358       return true;
00359     return false;
00360   }
00361 
00362 
00363   // That's probably too much data for iterator, since
00364   // it will be copied, but let's not bother for now.
00365   std::set<std::string> allowed_options;
00366   // Invariant: no element is prefix of other element.
00367   std::set<std::string> allowed_prefixes;
00368   std::string m_prefix;
00369   bool m_allow_unregistered;
00370 };
00371 
00372 template<class charT>
00373 class basic_config_file_iterator :
00374   public common_config_file_iterator
00375 {
00376 public:
00377 
00378   basic_config_file_iterator()
00379   {
00380     found_eof();
00381   }
00382 
00384   basic_config_file_iterator(std::basic_istream<charT>& is, 
00385                              const std::set<std::string>& allowed_options,
00386                              bool allow_unregistered = false); 
00387 
00388 private: // base overrides
00389 
00390   bool getline(std::string&);
00391 
00392 private: // internal data
00393   boost::shared_ptr<std::basic_istream<charT> > is;
00394 };
00395 
00396 typedef basic_config_file_iterator<char> config_file_iterator;
00397 typedef basic_config_file_iterator<wchar_t> wconfig_file_iterator;
00398 
00399 struct null_deleter
00400 {
00401   void operator()(void const *) const {}
00402 };
00403 
00404 
00405 template<class charT>
00406 basic_config_file_iterator<charT>::
00407 basic_config_file_iterator(std::basic_istream<charT>& in_is, 
00408                            const std::set<std::string>& in_allowed_options,
00409                            bool in_allow_unregistered) :
00410   common_config_file_iterator(in_allowed_options, in_allow_unregistered)
00411 {
00412   this->is.reset(&in_is, null_deleter());                 
00413   get();
00414 }
00415 
00416 
00417 // Specializing this function for wchar_t causes problems on
00418 // borland and vc7, as well as on metrowerks. On the first two
00419 // I don't know a workaround, so make use of 'to_internal' to
00420 // avoid specialization.
00421 template<class charT>
00422 bool
00423 basic_config_file_iterator<charT>::getline(std::string& s)
00424 {
00425   if (std::getline(*is, s))
00426   {
00427     return true;
00428   }
00429   else
00430   {
00431     return false;
00432   }
00433 }
00434 
00435 } /* namespace detail */
00436 
00441 template<class charT>
00442 boost::program_options::basic_parsed_options<charT>
00443 parse_config_file(std::basic_istream<charT>& is,
00444                   const boost::program_options::options_description& desc,
00445                   bool allow_unregistered = false)
00446 {    
00447   std::set<std::string> allowed_options;
00448 
00449   const std::vector<boost::shared_ptr<boost::program_options::option_description> >& options = desc.options();
00450   for (unsigned i = 0; i < options.size(); ++i)
00451   {
00452     const boost::program_options::option_description& d= *options[i];
00453 
00454     if (d.long_name().empty())
00455       boost::throw_exception(boost::program_options::error("long name required for config file"));
00456 
00457     allowed_options.insert(d.long_name());
00458   }
00459 
00460   // Parser return char strings
00461   boost::program_options::parsed_options result(&desc);        
00462   std::copy(detail::basic_config_file_iterator<charT>(is,
00463                                                       allowed_options,
00464                                                       allow_unregistered), 
00465        detail::basic_config_file_iterator<charT>(), 
00466        std::back_inserter(result.options));
00467   // Convert char strings into desired type.
00468   return boost::program_options::basic_parsed_options<charT>(result);
00469 }
00470 
00476 template<class charT>
00477 boost::program_options::basic_parsed_options<charT>
00478 parse_config_file(const char* filename,
00479                   const boost::program_options::options_description& desc,
00480                   bool allow_unregistered = false)
00481 { 
00482   // Parser return char strings
00483   std::basic_ifstream< charT > strm(filename);
00484   if (!strm) 
00485   {
00486     boost::throw_exception("Couldn't open file");
00487   }
00488   return parse_config_file(strm, desc, allow_unregistered);
00489 }
00490 
00491 } /* namespace program_options */
00492 } /* namespace drizzled */
00493 
00494