changeset 203:1ffe6d4937b7

Update parser for more convenience and types security #224
author David Demelier <markand@malikania.fr>
date Thu, 23 Jan 2014 14:56:50 +0100
parents 99d0887395cc
children 7086e93bc4ea 706f861c4c6d
files C++/Parser.cpp C++/Parser.h
diffstat 2 files changed, 207 insertions(+), 265 deletions(-) [+]
line wrap: on
line diff
--- a/C++/Parser.cpp	Sat Jan 04 18:02:06 2014 +0100
+++ b/C++/Parser.cpp	Thu Jan 23 14:56:50 2014 +0100
@@ -16,22 +16,14 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <cstring>
+#include <cerrno>
 #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
  * -------------------------------------------------------- */
 
@@ -40,108 +32,50 @@
 {
 }
 
+Section::Section(const std::string &name)
+	: m_name(name)
+	, m_allowed(true)
+{
+
+}
+
 const std::string &Section::getName() const
 {
 	return m_name;
 }
 
-const std::string Section::findOption(const std::string &name) const
+bool Section::hasOption(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;
+	return m_options.count(name) >= 1;
 }
 
-template <>
-bool Section::getValue(const std::string &name) const
+Section::Map::iterator Section::begin()
 {
-	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;
+	return m_options.begin();
 }
 
-template <>
-int Section::getValue(const std::string &name) const
+Section::Map::const_iterator Section::cbegin() const
 {
-	int result = -1;
-
-	if (hasOption(name))
-		result = atoi(findOption(name).c_str());
-
-	return result;
+	return m_options.cbegin();
 }
 
-template <>
-std::string Section::getValue(const std::string &name) const
+Section::Map::iterator Section::end()
 {
-	std::string result;
-
-	if (hasOption(name))
-		result = findOption(name);
-
-	return result;
-}
-
-const std::vector<Option> &Section::getOptions() const
-{
-	return m_options;
+	return m_options.end();
 }
 
-bool Section::hasOption(const std::string &name) const
+Section::Map::const_iterator Section::cend() 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;
+	return m_options.end();
 }
 
 /* --------------------------------------------------------
  * 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 &current = m_sections.back();
-
-	option.m_key = key;
-	option.m_value = value;
-
-	current.m_options.push_back(option);
+	m_sections.back().m_options.insert(std::make_pair(key, value));
 }
 
 void Parser::readSection(int lineno, const std::string &line)
@@ -150,7 +84,7 @@
 
 	if ((end = line.find_first_of(']')) != std::string::npos) {
 		if (end > 1) {
-			std::string name = line.substr(1, end - 1);
+			auto name = line.substr(1, end - 1);
 
 			/*
 			 * Check if we can add a section, if redefinition is
@@ -162,8 +96,9 @@
 				if (!(m_tuning & DisableVerbosity))
 					log(lineno, name, "redefinition not allowed");
 				m_sections.back().m_allowed = false;
-			} else
-				addSection(name);
+			} else {
+				m_sections.push_back(Section(name));
+			}
 		} else if (!(m_tuning & DisableVerbosity)) {
 			/*
 			 * Do not add options at this step because it will
@@ -177,9 +112,9 @@
 
 void Parser::readOption(int lineno, const std::string &line)
 {
+	auto &current = m_sections.back();
 	size_t epos;
 	std::string key, value;
-	Section &current = m_sections.back();
 
 	// Error on last section?
 	if (!current.m_allowed) {
@@ -267,6 +202,22 @@
 
 const char Parser::DEFAULT_COMMENT_CHAR = '#';
 
+void Parser::open()
+{
+	std::ifstream file;
+	std::string line;
+	int lineno = 1;
+
+	file.open(m_path.c_str());
+	if (!file.is_open())
+		throw std::runtime_error(m_path + std::string(std::strerror(errno)));
+
+	while (std::getline(file, line))
+		readLine(lineno++, line);
+
+	file.close();
+}
+
 Parser::Parser()
 {
 }
@@ -276,60 +227,48 @@
 	, m_tuning(tuning)
 	, m_commentChar(commentToken)
 {
-	Section root;
+	Section s("");
 
-	// Add a default root section
-	root.m_name = "";
-	root.m_allowed = (tuning & DisableRootSection) ? false : true;
+	s.m_allowed = (tuning & DisableRootSection) ? false : true;
 
-	m_sections.push_back(root);
+	m_sections.push_back(s);
+	open();
 }
 
 Parser::~Parser()
 {
 }
 
-bool Parser::open()
+Parser::List::iterator Parser::begin()
 {
-	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;
+	return m_sections.begin();
 }
 
-const std::string &Parser::getError() const
+Parser::List::const_iterator Parser::cbegin() const
 {
-	return m_error;
+	return m_sections.cbegin();
 }
 
-const std::vector<Section> &Parser::getSections() const
+Parser::List::iterator Parser::end()
 {
-	return m_sections;
+	return m_sections.end();
+}
+
+Parser::List::const_iterator Parser::cend() const
+{
+	return m_sections.end();
 }
 
 void Parser::findSections(const std::string &name, FindFunc func) const
 {
-	for (const Section &s : m_sections)
+	for (const auto &s : m_sections)
 		if (s.m_name == name)
 			func(s);
 }
 
 bool Parser::hasSection(const std::string &name) const
 {
-	for (const Section &s : m_sections)
+	for (const auto &s : m_sections)
 		if (s.m_name == name)
 			return true;
 
@@ -338,34 +277,14 @@
 
 const Section &Parser::getSection(const std::string &name) const
 {
-	for (const Section &s : m_sections)
+	for (const auto &s : m_sections)
 		if (s.m_name == name)
 			return s;
 
-	throw NotFoundException(name);
+	throw std::out_of_range(name + " not found");
 }
 
 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 &section)
-{
-	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;
-}
--- a/C++/Parser.h	Sat Jan 04 18:02:06 2014 +0100
+++ b/C++/Parser.h	Thu Jan 23 14:56:50 2014 +0100
@@ -20,53 +20,14 @@
 #define _PARSER_H_
 
 #include <cstdlib>
-#include <exception>
 #include <functional>
-#include <iostream>
+#include <stdexcept>
 #include <string>
+#include <unordered_map>
+#include <utility>
 #include <vector>
 
 /**
- * @class NotFoundException
- * @brief Exception raised when a section or option is not found
- *
- * 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";
-	}
-};
-
-/**
- * @struct Option
- * @brief A key-value pair
- *
- * 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);
-
-/**
  * @class Section
  * @brief The option container
  *
@@ -75,13 +36,22 @@
  * section is "".
  */
 class Section {
-private:
-	const std::string findOption(const std::string &name) const;
-
 public:
-	std::string m_name;		/*! name of section */
-	std::vector<Option> m_options;	/*! list of options inside */
-	bool m_allowed;			/*! is authorized to push */
+	using Map = std::unordered_map<std::string, std::string>;
+
+	friend class Parser;
+
+private:
+	std::string	m_name;		/*! name of section */
+	Map		m_options;	/*! list of options inside */
+	bool		m_allowed;	/*! is authorized to push */
+
+	const std::string findOption(const std::string &name) const;
+public:
+	template <typename T>
+	struct Converter {
+		static const bool supported = false;
+	};
 
 	/**
 	 * Default constructor.
@@ -89,18 +59,11 @@
 	Section();
 
 	/**
-	 * Get the section name
+	 * Named constructor.
 	 *
-	 * @return the section name
+	 * @param name the section name
 	 */
-	const std::string &getName() const;
-
-	/**
-	 * Get all options from that section.
-	 *
-	 * @return the list of options
-	 */
-	const std::vector<Option> &getOptions() const;
+	Section(const std::string &name);
 
 	/**
 	 * Tells if that section has the specified option name.
@@ -111,13 +74,57 @@
 	bool hasOption(const std::string &name) const;
 
 	/**
+	 * Get the section name
+	 *
+	 * @return the section name
+	 */
+	const std::string &getName() const;
+
+	/**
+	 * Return an iterator to the beginning.
+	 *
+	 * @return the iterator.
+	 */
+	Map::iterator begin();
+
+	/**
+	 * Return a const iterator to the beginning.
+	 *
+	 * @return the iterator.
+	 */
+	Map::const_iterator cbegin() const;
+
+	/**
+	 * Return an iterator to the end.
+	 *
+	 * @return the iterator.
+	 */
+	Map::iterator end();
+	
+	/**
+	 * Return a const iterator to the end.
+	 *
+	 * @return the iterator.
+	 */
+	Map::const_iterator cend() const;
+
+	/**
 	 * Template all functions for retrieving options value.
 	 *
 	 * @param name the option name
 	 * @return the value if found
 	 */
 	template <typename T>
-	T getValue(const std::string &name) const;
+	T getValue(const std::string &name) const
+	{
+		try {
+			return requireValue<T>(name);
+		} catch (...) {
+			// Catch any conversion error.
+		}
+
+		return T();
+	}
 
 	/**
 	 * Requires an option, this works like getOption except
@@ -126,29 +133,74 @@
 	 *
 	 * @param name the name
 	 * @return the value
-	 * @throw NotFoundException if not found
+	 * @throw std::out_of_range if not found
+	 * @throw std::invalid_argument on conversion failures
 	 */
 	template <typename T>
 	T requireValue(const std::string &name) const
 	{
-		if (!hasOption(name))
-			throw NotFoundException(name);
+		static_assert(Converter<T>::supported, "invalid type requested");
 
-		return getValue<T>(name);
+		return Converter<T>::convert(m_options.at(name));
 	}
+};
+
+template <>
+struct Section::Converter<bool> {
+	static const bool supported = true;
 
-	friend std::ostream &operator<<(std::ostream & stream, const Section &section)
+	static bool convert(const std::string &value)
 	{
-		stream << "[" << section.getName() << "]" << std::endl;
+		bool result(false);
 
-		for (auto p : section.getOptions())
-			stream << p.m_key << "=" << p.m_value << std::endl;
+		if (value == "yes" || value == "true"|| value == "1")
+			result = true;
+		else if (value == "no" || value == "false" || value == "0")
+			result = false;
 
-		return stream;
+		return result;
 	}
 };
 
-bool operator==(const Section &s1, const Section &s2);
+template <>
+struct Section::Converter<int> {
+	static const bool supported = true;
+
+	static int convert(const std::string &value)
+	{
+		return std::stoi(value);
+	}
+};
+
+template <>
+struct Section::Converter<float> {
+	static const bool supported = true;
+
+	static float convert(const std::string &value)
+	{
+		return std::stof(value);
+	}
+};
+
+template <>
+struct Section::Converter<double> {
+	static const bool supported = true;
+
+	static double convert(const std::string &value)
+	{
+		return std::stod(value);
+	}
+};
+
+template <>
+struct Section::Converter<std::string> {
+	static const bool supported = true;
+
+	static std::string convert(const std::string &value)
+	{
+		return value;
+	}
+};
 
 /**
  * @class Parser
@@ -167,14 +219,14 @@
 		DisableVerbosity	= 4	/*! be verbose by method */
 	};
 
-	using FindFunc = std::function<void (const Section &)>;
+	using FindFunc	= std::function<void (const Section &)>;
+	using List	= std::vector<Section>;
 
 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 (#) */
+	List		m_sections;		/*! list of sections found */
+	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);
@@ -184,6 +236,8 @@
 
 	void readLine(int lineno, const std::string &line);
 
+	void open();
+
 public:
 	static const char DEFAULT_COMMENT_CHAR;
 
@@ -194,6 +248,7 @@
 	 * @param path the file path
 	 * @param tuning optional tuning flags
 	 * @param commentToken an optional comment delimiter
+	 * @throw std::runtime_error on errors
 	 * @see Tuning
 	 */
 	Parser(const std::string &path, int tuning = 0, char commentToken = Parser::DEFAULT_COMMENT_CHAR);
@@ -209,25 +264,32 @@
 	virtual ~Parser();
 
 	/**
-	 * Open the config file.
+	 * Return an iterator to the beginning.
 	 *
-	 * @return true on success
+	 * @return the iterator.
 	 */
-	bool open();
+	List::iterator begin();
 
 	/**
-	 * Get the error message if any
+	 * Return a const iterator to the beginning.
 	 *
-	 * @return the error message
+	 * @return the iterator.
 	 */
-	const std::string &getError() const;
+	List::const_iterator cbegin() const;
 
 	/**
-	 * Get all sections found
+	 * Return an iterator to the end.
 	 *
-	 * @return all sections
+	 * @return the iterator.
 	 */
-	const std::vector<Section> &getSections() const;
+	List::iterator end();
+	
+	/**
+	 * Return a const iterator to the end.
+	 *
+	 * @return the iterator.
+	 */
+	List::const_iterator cend() const;
 
 	/**
 	 * Find all sections matching the name.
@@ -250,7 +312,7 @@
 	 *
 	 * @param name the section name
 	 * @return a section
-	 * @throw NotFoundException if not found
+	 * @throw std::out_of_range if not found
 	 */
 	const Section &getSection(const std::string &name) const;
 
@@ -265,45 +327,6 @@
 	 * @param message the message
 	 */
 	virtual void log(int number, const std::string &section, 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 &section);
-
-	/**
-	 * 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);
-
-	/**
-	 * Write the configuration to the output stream.
-	 *
-	 * @param stream the output
-	 * @param parser the configuration
-	 * @return the stream
-	 */
-	friend std::ostream &operator<<(std::ostream &stream, const Parser &parser)
-	{
-		for (auto s : parser.m_sections)
-			stream << s;
-
-		return stream;
-	}
 };
 
 #endif // !_PARSER_H_