123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444 |
- /*!
- * \file config_file.cpp
- * The file interface to the config class.
- */
- // Config Class
- // Author: Charles Gruenwald III
- #include "config_file.hpp"
- #include "config_exceptions.hpp"
- #include "itostr.h"
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include <errno.h>
- #include <boost/algorithm/string.hpp>
- #include <boost/version.hpp>
- #include <boost/lexical_cast.hpp>
- namespace config
- {
- ConfigFile::ConfigFile(bool case_sensitive)
- :
- Config(case_sensitive)
- {
- }
- ConfigFile::ConfigFile(const Section & root, bool case_sensitive)
- :
- Config(root, case_sensitive)
- {
- }
- //load a given filename into a string, taken from boost regexp replace example
- void ConfigFile::loadFileToString(String& s, const String& filename)
- {
- std::ifstream file_in(filename.c_str());
- s.erase();
- s.reserve(file_in.rdbuf()->in_avail());
- char c;
- while(file_in.get(c))
- {
- if(s.capacity() == s.size())
- s.reserve(s.capacity() * 3);
- s.append(1, c);
- }
- file_in.close();
- }
- void ConfigFile::loadConfig()
- {
- String filename = m_path;
- //Make sure the file exists
- if(access(filename.c_str(), R_OK) != 0)
- {
- throw FileNotFound(filename);
- }
- //Read the data in from the file
- String data;
- loadFileToString(data, filename);
- loadConfigFromString(data);
- }
- void ConfigFile::loadConfigFromString(const String & cfg)
- {
- parse(cfg, m_root);
- }
- //Build the in-memory representation by line-by-line matching boost::regex expressions
- void ConfigFile::parse(const String &source, Section & current)
- {
- //rules
- config_parser parser;
- parse_info_t info = ast_parse<config_parser::factory_t>(source.c_str(), source.c_str() + source.size(),parser,space_p | comment_p("#"));
- if(info.full)
- {
- // Uncomment the following lines for more verbose output
- // showParseTree(info.trees.begin());
- evalTree(m_root, info.trees.begin());
- }
- else
- {
- String error_str(info.stop);
- throw parserError(error_str);
- }
- }
- void ConfigFile::showParseTree(tree_iter_t const& node, int depth)
- {
- String tabs = "";
- for(int i=0;i<depth;i++)
- tabs = tabs.append(" ");
- RuleID rule = node->value.value().e;
- // get the string value from the iter
- String value(node->value.begin(), node->value.end() - node->value.begin());
- if(rule == sectionID)
- value = "s: ";
- if(rule == configID)
- value = "c: " + value;
- else if(rule == keyID)
- value = "k: " + value;
- else if(rule == keyValueID)
- value = "kv: " + value;
- else if(rule == keyNameID)
- value = "kn: " + value;
- else if(rule == sectionNameID)
- value = "sn: " + value;
- else if(rule == keyValueArrayID)
- value = "kva: " + value;
- else if(rule == keyValueSpanID)
- value = "kvs: " + value;
- // if(rule == sectionID || rule == keyID || rule == keyValueID || rule == keyNameID || rule == sectionNameID)
- // if(rule > 0)
- std::cout << tabs << (int)(rule) << ".[" << value << "]" << std::endl;
- for(tree_iter_t chi = node->children.begin(); chi != node->children.end(); chi++)
- {
- showParseTree(chi, depth + 1);
- }
- }
- void ConfigFile::unEscapeText(const String & source, String & dest)
- {
- bool backslash = false;
- unsigned int start = 0;
- unsigned int end = source.size();
- //remove surrounding parens if necessary
- if(source.size() >= 2 && source.c_str()[0] == '"' && source.c_str()[source.size() - 1] == '"')
- {
- ++start;
- --end;
- }
- for (unsigned int i = start; i < end; i++) {
- char c = source.c_str()[i];
- if (backslash)
- {
- switch (c)
- {
- case '\\': dest += "\\"; break;
- case 'b': dest += "\b"; break;
- case 'r': dest += "\r"; break;
- case 'f': dest += "\f"; break;
- case 't': dest += "\t"; break;
- case 'n': dest += "\n"; break;
- case '"': dest += "\""; break;
- case '\'': dest += "'"; break;
- default: dest += c;
- }
- backslash = false;
- }
- else
- {
- if (c == '\\')
- backslash = true;
- else
- dest += c;
- }
- }
- }
- String ConfigFile::getNodeValue(tree_iter_t const& node)
- {
- if(node->value.begin() == node->value.end())
- return "";
- return String(node->value.begin(), node->value.end());
- }
- RuleID ConfigFile::getNodeID(tree_iter_t const& node)
- {
- return node->value.value().e;
- }
- void ConfigFile::escapeText(const String & source, String & dest)
- {
- for (unsigned int i = 0; i < source.size(); i++) {
- char c = source.c_str()[i];
- switch (c)
- {
- case '\\': dest += "\\\\"; break;
- case '\"': dest += "\\\""; break;
- default: dest += c;
- }
- }
- }
- // This function recursively decends the tree built by the parser and populates the config
- // file with the appropriate entries
- void ConfigFile::evalTree(Section & current, tree_iter_t const& node, int depth)
- {
- String tabs = "";
- for(int i=0;i<depth;i++)
- tabs = tabs.append(" ");
- const RuleID rule (getNodeID(node));
- const String value(getNodeValue(node));
- if(rule == sectionNameID)
- {
- // Since this is a flat file representation, whenever we get a section we only add it
- // to the root node, thus we break out if we are currently nested.
- if(!current.isRoot())
- return;
- //HACK: Strip off the '[' and ']', guaranteed to be matched.
- //This should be removed when we upgrade to Boost 1.35, as per config_file_grammar.hpp
- //note we should just be able to say section_name = value
- String section_name;
- section_name = value.substr(1, value.size() - 2);
- // std::cout << "Found section: [" << section_name << "]" << std::endl;
- // create the section
- Section &child = Config::getSection_unsafe(section_name);
- // add each of the children nodes to this section
- for(tree_iter_t chi_node = node->children.begin(); chi_node != node->children.end(); chi_node++)
- {
- evalTree(child, chi_node, depth + 1);
- }
- }
- else if(rule == keyID)
- {
- //assert(node->children.size() == 1 || node->children.size() == 2);
- String key_name = "";
- String key_value = "";
- // Allow for empty (non-existant) values
- if (node->children.size() != 1 && node->children.size() != 2)
- {
- throw parserError("Internal parser error: Expected one or two keyID children, not " + boost::lexical_cast<String>(node->children.size()));
- }
- if (getNodeID(node->children.begin()) != keyNameID)
- {
- throw parserError("Internal parser error: Expected the first entry to be a keyNameID");
- }
- unEscapeText(getNodeValue(node->children.begin()), key_name);
- if (node->children.size() == 1)
- {
- current.addKey(key_name, key_value);
- }
- else if (node->children.size() == 2)
- {
- UInt64 index = 0;
- tree_iter_t const& datanode = node->children.begin() + 1;
- switch(getNodeID(datanode))
- {
- case keyValueID:
- unEscapeText(getNodeValue(datanode), key_value);
- current.addKey(key_name, key_value);
- break;
- case keyValueArrayID:
- for(tree_iter_t chi = datanode->children.begin(); chi != datanode->children.end(); chi++)
- {
- switch(getNodeID(chi))
- {
- case keySeparatorID:
- ++index;
- break;
- case keyValueID:
- key_value = "";
- unEscapeText(getNodeValue(chi), key_value);
- if (key_value != "")
- current.addKey(key_name, key_value, index);
- break;
- default:
- throw parserError("Internal parser error: Expected separator or value");
- }
- }
- break;
- // FIXME: Span should properly create a span of numbers
- case keyValueSpanID:
- default:
- throw parserError("Internal parser error: Unexpected node type");
- }
- }
- else
- {
- throw parserError("Internal parser error: Unexpacted number of keyID children found: " + boost::lexical_cast<String>(node->children.size()));
- }
- }
- else
- {
- for(tree_iter_t chi = node->children.begin(); chi != node->children.end(); chi++)
- {
- evalTree(current, chi, depth + 1);
- }
- }
- }
- void ConfigFile::saveAs(const String &path)
- {
- const String tmp_path(path + ".tmp");
- std::ofstream output_file;
- output_file.exceptions ( std::ofstream::eofbit | std::ofstream::failbit | std::ofstream::badbit );
- //Atomic write with error handling
- try
- {
- output_file.open(tmp_path.c_str());
- SaveTreeAs(output_file, m_root);
- output_file.close();
- }
- catch(const std::ofstream::failure & e)
- {
- throw SaveError(e.what());
- }
- unlink(path.c_str());
- int res = rename(tmp_path.c_str(), path.c_str());
- if (res == -1)
- {
- int errsv = errno;
- String error_str("Moving " + tmp_path + " to " + path + ": error(" + itostr(errsv) + ") " + strerror(errsv) + ".");
- throw SaveError(error_str);
- }
- }
- //Dump the tree in a format that is readable by our parser
- void ConfigFile::SaveTreeAs(std::ofstream &out, const Section ¤t)
- {
- if(!current.isRoot())
- {
- out << "[" << current.getFullPath() << "]" << std::endl;
- }
- // Write out all of the default values
- KeyList const & keys = current.getKeys();
- for(KeyList::const_iterator i = keys.begin(); i != keys.end();i++)
- {
- const String & name = i->second->getName();
- //Quote the name if it has spaces in it
- if(boost::find_first(name, " ") || boost::find_first(name, "\""))
- {
- String escaped_name;
- escapeText(name, escaped_name);
- out << "\"" << escaped_name << "\"";
- }
- else
- out << name;
- //Quote the value if it is a string
- if(i->second->getFloatValid() || i->second->getIntValid())
- out << " = " << i->second->getString() << std::endl;
- else
- {
- String escaped_value;
- escapeText(i->second->getString(), escaped_value);
- out << " = \"" << escaped_value << "\"" << std::endl;
- }
- }
- //Write out the array data that overrides the defaults
- KeyArrayList const & arr = current.getArrayKeys();
- for(KeyArrayList::const_iterator i = arr.begin(); i != arr.end(); ++i)
- {
- for (uint64_t index = 0; index < i->second.size(); ++index)
- {
- // Find a non-default configuration value where we can get the name from
- if (i->second[index])
- {
- const String & name = i->second[index]->getName();
- //Quote the name if it has spaces in it
- if(boost::find_first(name, " ") || boost::find_first(name, "\""))
- {
- String escaped_name;
- escapeText(name, escaped_name);
- out << "\"" << escaped_name << "\"";
- }
- else
- out << name;
- break;
- }
- }
- out << "[] = ";
- for (uint64_t index = 0; index < i->second.size(); ++index)
- {
- if (i->second[index])
- {
- //Quote the value if it is a string
- if(i->second[index]->getFloatValid() || i->second[index]->getIntValid())
- out << i->second[index]->getString();
- else
- {
- String escaped_value;
- escapeText(i->second[index]->getString(), escaped_value);
- out << "\"" << escaped_value << "\"";
- }
- }
- // else: default value, print nothing between the ","
- out << ",";
- }
- out << std::endl;
- }
- out << std::endl;
- SectionList const & subsections = current.getSubsections();
- for(SectionList::const_iterator i = subsections.begin(); i != subsections.end(); i++)
- {
- Section const & subsection = *(i->second);
- SaveTreeAs(out, subsection);
- }
- return;
- }
- //namespaces
- }
|