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