Mercurial > code
changeset 179:3648e9e6935b
Add parser
author | David Demelier <markand@malikania.fr> |
---|---|
date | Sun, 22 Sep 2013 19:57:59 +0200 |
parents | 2bfe43b85d7f |
children | 2bcdee0fe8d4 |
files | C++/Parser.cpp C++/Parser.h |
diffstat | 2 files changed, 702 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Parser.cpp Sun Sep 22 19:57:59 2013 +0200 @@ -0,0 +1,396 @@ +/* + * Parser.h -- config file parser + * + * Copyright (c) 2011, 2012, 2013 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <iostream> +#include <fstream> + +#include "Parser.h" + +/* -------------------------------------------------------- + * Option public members + * -------------------------------------------------------- */ + +bool operator==(const Option &o1, const Option &o2) +{ + return o1.m_key == o2.m_key && + o1.m_value == o2.m_value; +} + +/* -------------------------------------------------------- + * Section public members + * -------------------------------------------------------- */ + +Section::Section() + :m_allowed(true) +{ +} + +Section::~Section() +{ +} + +Section::Section(const Section &s) +{ + m_name = s.m_name; + m_options = s.m_options; + m_allowed = s.m_allowed; +} + +const std::string Section::findOption(const std::string &name) const +{ + std::string ret; + + for (const Option &o : m_options) + if (o.m_key == name) { + ret = o.m_value; + break; + } + + return ret; +} + +template <> bool Section::getOption(const std::string &name) const +{ + bool result = false; + + if (hasOption(name)) { + std::string value = findOption(name); + + if (value == "yes" || value == "true"|| value == "1") + result = true; + else if (value == "no" || value == "false" || value == "0") + result = false; + } + + return result; +} + +template <> int Section::getOption(const std::string &name) const +{ + int result = -1; + + if (hasOption(name)) { + result = atoi(findOption(name).c_str()); + } + + return result; +} + +template <> std::string Section::getOption(const std::string &name) const +{ + std::string result; + + if (hasOption(name)) + result = findOption(name); + + return result; +} + +const std::string & Section::getName() const +{ + return m_name; +} + +const std::vector<Option> & Section::getOptions() const +{ + return m_options; +} + +bool Section::hasOption(const std::string &name) const +{ + for (const Option &o : m_options) + if (o.m_key == name) + return true; + + return false; +} + +bool operator==(const Section &s1, const Section &s2) +{ + if (s1.m_name != s2.m_name) + return false; + + return s1.m_options == s2.m_options; +} + +/* -------------------------------------------------------- + * Parser private members + * -------------------------------------------------------- */ + +void Parser::addSection(const std::string &name) +{ + Section section; + + section.m_name = name; + section.m_allowed = true; + + m_sections.push_back(section); +} + +void Parser::addOption(const std::string &key, const std::string &value) +{ + Option option; + Section ¤t = m_sections.back(); + + option.m_key = key; + option.m_value = value; + + current.m_options.push_back(option); +} + +void Parser::readSection(int lineno, const std::string &line) +{ + size_t end; + + if ((end = line.find_first_of(']')) != std::string::npos) { + if (end > 1) { + std::string name = line.substr(1, end - 1); + + /* + * Check if we can add a section, if redefinition is + * disabled, we must disable the previous section so the + * further read options should not be enabled until + * a correct section is found again. + */ + if (hasSection(name) && (m_tuning & DisableRedefinition)) { + if (!(m_tuning & DisableVerbosity)) + log(lineno, name, "redefinition not allowed"); + m_sections.back().m_allowed = false; + } else { + addSection(name); + } + } else if (!(m_tuning & DisableVerbosity)) { + /* + * Do not add options at this step because it will + * corrupt the previous one. + */ + m_sections.back().m_allowed = false; + log(lineno, "", "empty section name"); + } + } +} + +void Parser::readOption(int lineno, const std::string &line) +{ + size_t epos; + std::string key, value; + Section ¤t = m_sections.back(); + + // Error on last section? + if (!current.m_allowed) { + /* + * If it is the root section, this has been probably set by + * DisableRootSection flag, otherwise an error has occured + * so no need to log. + */ + if (current.m_name == "" && !(m_tuning == DisableVerbosity)) + log(lineno, "", "option not allowed in that scope"); + + return; + } + + if ((epos = line.find_first_of('=')) == std::string::npos) { + if (!(m_tuning & DisableVerbosity)) + log(lineno, current.m_name, "missing `=' keyword"); + return; + } + + if (epos > 0) { + size_t i, begin, last; + char c; + + key = line.substr(0, epos - 1); + value = line.substr(epos + 1); + + // clean option key + for (i = 0; !isspace(key[i]) && i < key.length(); ++i) + continue; + key = key.substr(0, i); + + // clean option value + for (begin = 0; isspace(value[begin]) && begin < value.length(); ++begin) + continue; + value = value.substr(begin); + + c = value[0]; + begin = 0; + if (c == '\'' || c == '"') { + for (last = begin = 1; value[last] != c && last < value.length(); ++last) + continue; + if (value[last] != c && !(m_tuning & DisableVerbosity)) + if (!(m_tuning & DisableVerbosity)) + log(lineno, current.m_name, "undisclosed std::string"); + } else { + for (last = begin; !isspace(value[last]) && last < value.length(); ++last) + continue; + } + + if (last - begin > 0) + value = value.substr(begin, last - begin); + else + value.clear(); + + // Add the option if the key is not empty + if (key.length() > 0) + addOption(key, value); + } +} + +void Parser::readLine(int lineno, const std::string &line) +{ + size_t i; + std::string buffer; + + // Skip default spaces + for (i = 0; isspace(line[i]) && i < line.length(); ++i) + continue; + + buffer = line.substr(i); + if (buffer.length() > 0) { + if (buffer[0] != m_commentChar) { + if (buffer[0] == '[') + readSection(lineno, buffer); + else + readOption(lineno, buffer); + } + } +} + +/* -------------------------------------------------------- + * Parser public methods + * -------------------------------------------------------- */ + +const char Parser::DEFAULT_COMMENT_CHAR = '#'; + +Parser::Parser(const std::string &path, int tuning, char commentToken) + :m_path(path), m_tuning(tuning), m_commentChar(commentToken) +{ + Section root; + + // Add a default root section + root.m_name = ""; + root.m_allowed = (tuning & DisableRootSection) ? false : true; + + m_sections.push_back(root); +} + +Parser::Parser() +{ +} + +Parser::~Parser() +{ +} + +bool Parser::open() +{ + std::ifstream file; + std::string line; + int lineno = 1; + + file.open(m_path.c_str()); + if (!file.is_open()) { + m_error = "could not open file " + m_path; // XXX: add a real error + return false; + } + + // Avoid use of C getline + while (std::getline(file, line)) { + readLine(lineno++, line); + } + + file.close(); + + return true; +} + +const std::string & Parser::getError() const +{ + return m_error; +} + +const std::vector<Section> & Parser::getSections() const +{ + return m_sections; +} + +std::vector<Section> Parser::findSections(const std::string &name) const +{ + std::vector<Section> list; + + for (const Section &s : m_sections) { + if (s.m_name == name) { + Section copy = s; + list.push_back(copy); + } + } + + return list; +} + +bool Parser::hasSection(const std::string &name) const +{ + for (const Section &s : m_sections) + if (s.m_name == name) + return true; + + return false; +} + +Section Parser::getSection(const std::string &name) const +{ + Section ret; + + for (const Section &s : m_sections) + if (s.m_name == name) + ret = s; + + return ret; +} + +Section Parser::requireSection(const std::string &name) const +{ + if (!hasSection(name)) + throw NotFoundException(name); + + return getSection(name); +} + +void Parser::log(int number, const std::string &, const std::string &message) +{ + std::cout << "line " << number << ": " << message << std::endl; +} + +void Parser::dump() +{ + for (auto s : m_sections) { + dumpSection(s); + for (auto o : s.m_options) + dumpOption(o); + } +} + +void Parser::dumpSection(const Section §ion) +{ + std::cout << "Section " << section.m_name << std::endl; +} + +void Parser::dumpOption(const Option &option) +{ + std::cout << " Option " << option.m_key << " = " << option.m_value << std::endl; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/C++/Parser.h Sun Sep 22 19:57:59 2013 +0200 @@ -0,0 +1,306 @@ +/* + * Parser.h -- config file parser + * + * Copyright (c) 2011, 2012, 2013 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef _PARSER_H_ +#define _PARSER_H_ + +#include <cstdlib> +#include <exception> +#include <iostream> +#include <string> +#include <vector> + +/** + * Thrown when a section or an option is not found. + */ +class NotFoundException : public std::exception +{ +private: + std::string m_key; + +public: + NotFoundException(const std::string &key) + :m_key(key) + { + } + + const std::string & which() const + { + return m_key; + } + + virtual const char *what() const throw() + { + return "Property not found"; + } +}; + +/** + * An option referenced by a key and a value. + */ +struct Option +{ + std::string m_key; /*! option name */ + std::string m_value; /*! option value */ +}; + +bool operator==(const Option &o1, const Option &o2); + +/** + * A list of section found in the file. If root + * options are allowed (default behavior), the root + * section is "". + */ +struct Section +{ + std::string m_name; /*! name of section */ + std::vector<Option> m_options; /*! list of options inside */ + bool m_allowed; /*! is authorized to push */ + + Section(); + ~Section(); + + /** + * Copy constructor + */ + Section(const Section &s); + + /** + * Get the section name + * + * @return the section name + */ + const std::string & getName() const; + + /** + * Search an option value. + * + * @param name the option name + * @return the value or "" if not found + */ + const std::string findOption(const std::string &name) const; + + /** + * Get all options from that section. + * + * @return the list of options + */ + const std::vector<Option> & getOptions() const; + + /** + * Tells if that section has the specified option name. + * + * @param name the option name + * @return true if has + */ + bool hasOption(const std::string &name) const; + + /** + * Template all functions for retrieving options value. + * + * @param name the option name + * @return the value if found + */ + template <typename T> + T getOption(const std::string &name) const; + + /** + * Requires an option, this works like getOption except + * that if an option is not found, an exception is + * thrown. + * + * @param name the name + * @throw NotFoundException if not found + * @return the value + */ + template <typename T> + T requireOption(const std::string &name) const + { + if (!hasOption(name)) + throw NotFoundException(name); + + return getOption<T>(name); + } + + friend std::ostream & operator<<(std::ostream & stream, const Section §ion) + { + stream << "[" << section.getName() << "]" << std::endl; + + for (auto p : section.getOptions()) + stream << p.m_key << "=" << p.m_value << std::endl; + + return stream; + } +}; + +bool operator==(const Section &s1, const Section &s2); + +class Parser +{ +public: + /** + * Options available for the parser. + */ + enum Tuning + { + DisableRootSection = 1, /*! disable options on root */ + DisableRedefinition = 2, /*! disable multiple redefinition */ + DisableVerbosity = 4 /*! be verbose by method */ + }; + +private: + std::vector<Section> m_sections; /*! list of sections found */ + std::string m_error; /*! if an error occured */ + std::string m_path; /*! path file */ + int m_tuning; /*! options for parsing */ + char m_commentChar; /*! the comment token default (#) */ + + void addSection(const std::string &name); + void addOption(const std::string &key, const std::string &value); + + void readSection(int lineno, const std::string &line); + void readOption(int lineno, const std::string &line); + + void readLine(int lineno, const std::string &line); + +public: + static const char DEFAULT_COMMENT_CHAR; + + /** + * Create a parser at the specified file path. Optional + * options may be added. + * + * @param path the file path + * @param tuning optional tuning flags + * @param commentToken an optional comment delimiter + * @see Tuning + */ + Parser(const std::string &path, int tuning = 0, char commentToken = Parser::DEFAULT_COMMENT_CHAR); + + /** + * Default constructor. + */ + Parser(); + + /** + * Default destructor. + */ + virtual ~Parser(); + + /** + * Open the config file. + * + * @return true on success + */ + bool open(); + + /** + * Get the error message if any + * + * @return the error message + */ + const std::string & getError() const; + + /** + * Get all sections found + * + * @return all sections + */ + const std::vector<Section> & getSections() const; + + /** + * Get a list of sections for config which multiple + * definitions are allowed. This does a full copy of sections + * and options. + * + * @param name the sections name + * @return a list of section with the options + */ + std::vector<Section> findSections(const std::string &name) const; + + /** + * Tell if a section is existing. + * + * @return true if exists + */ + bool hasSection(const std::string &name) const; + + /** + * Get a specified section. + * + * @param name the section name + * @return a section + */ + Section getSection(const std::string &name) const; + + /** + * Same as getSection except that throws an exception if + * the section is not found. + * + * @param name the section name + * @throw NotFoundException if not found + * @return the section + */ + Section requireSection(const std::string &name) const; + + /** + * Logging function, used only if DisableVerbosity is not set. The + * default behavior is to print to stdout something like: + * line 10: syntax error + * line 8: missing = + * + * @param number the line number + * @param section the current section worked on + * @param message the message + */ + virtual void log(int number, const std::string §ion, const std::string &message); + + /** + * Dump all sections and options. + */ + void dump(); + + /** + * Dump function used in the dump() method. This default method + * only print the section name like: + * Section foo + * + * @param section the current section + * @see dump + */ + virtual void dumpSection(const Section §ion); + + /** + * Dump the option. The default method only print the option name + * and value. + * + * @param option the current option + * @see dump + */ + virtual void dumpOption(const Option &option); + + friend std::ostream & operator<<(std::ostream & stream, const Parser &parser) + { + for (auto s : parser.m_sections) + stream << s;; + + return stream; + } +}; + +#endif // !_PARSER_H_