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 &current = 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 &current = 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 &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;
+}
--- /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 &section)
+	{
+		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 &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);
+
+	friend std::ostream & operator<<(std::ostream & stream, const Parser &parser)
+	{
+		for (auto s : parser.m_sections)
+			stream << s;;
+
+		return stream;
+	}
+};
+
+#endif // !_PARSER_H_