changeset 330:14d9c7a4f358

Merge ini to default
author David Demelier <markand@malikania.fr>
date Fri, 06 Mar 2015 22:01:40 +0100
parents c5dd79aaa216 (current diff) 43b4163470c2 (diff)
children 9e223d1de96f
files
diffstat 15 files changed, 1309 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Ini.cpp	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,481 @@
+/*
+ * Ini.cpp -- .ini file parsing
+ *
+ * Copyright (c) 2013, 2014 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 <cctype>
+#include <cerrno>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <iterator>
+#include <memory>
+#include <ostream>
+#include <sstream>
+#include <vector>
+
+#if defined(_WIN32)
+#  include <Shlwapi.h>	// for PathIsRelative
+#endif
+
+#include "Ini.h"
+
+namespace {
+
+/* --------------------------------------------------------
+ * Tokens
+ * -------------------------------------------------------- */
+
+enum class TokenType {
+	Comment = '#',
+	SectionBegin = '[',
+	SectionEnd = ']',
+	Escape = '\\',
+	QuoteSimple = '\'',
+	QuoteDouble = '"',
+	NewLine = '\n',
+	Assign = '=',
+	Include = '@',
+	Word,
+	Space
+};
+
+std::ostream &operator<<(std::ostream &out, const TokenType &type)
+{
+	switch (type) {
+	case TokenType::Comment:
+		out << "Comment";
+		break;
+	case TokenType::SectionBegin:
+		out << "SectionBegin";
+		break;
+	case TokenType::SectionEnd:
+		out << "SectionEnd";
+		break;
+	case TokenType::Escape:
+		out << "Escape";
+		break;
+	case TokenType::QuoteSimple:
+		out << "QuoteSimple";
+		break;
+	case TokenType::QuoteDouble:
+		out << "QuoteDouble";
+		break;
+	case TokenType::NewLine:
+		out << "NewLine";
+		break;
+	case TokenType::Assign:
+		out << "Assign";
+		break;
+	case TokenType::Include:
+		out << "Include";
+		break;
+	case TokenType::Word:
+		out << "Word";
+		break;
+	case TokenType::Space:
+		out << "Space";
+		break;
+	default:
+		break;
+	}
+
+	return out;
+}
+
+class Token {
+private:
+	TokenType m_type;
+	int m_line;
+	int m_position;
+	std::string m_value;
+
+public:
+	inline Token(TokenType type, int line, int position, std::string value = "")
+		: m_type(type)
+		, m_line(line)
+		, m_position(position)
+		, m_value(std::move(value))
+	{
+	}
+
+	inline TokenType type() const noexcept
+	{
+		return m_type;
+	}
+
+	inline int line() const noexcept
+	{
+		return m_line;
+	}
+
+	inline int position() const noexcept
+	{
+		return m_position;
+	}
+
+	inline std::string value() const
+	{
+		switch (m_type) {
+		case TokenType::Comment:
+			return "#";
+		case TokenType::SectionBegin:
+			return "[";
+		case TokenType::SectionEnd:
+			return "]";
+		case TokenType::QuoteSimple:
+			return "'";
+		case TokenType::QuoteDouble:
+			return "\"";
+		case TokenType::NewLine:
+			return "\n";
+		case TokenType::Assign:
+			return "=";
+		case TokenType::Include:
+			return "@";
+		case TokenType::Space:
+			return m_value;
+		case TokenType::Word:
+			return m_value;
+		default:
+			break;
+		}
+
+		return "";
+	}
+
+	inline std::string toString() const
+	{
+		switch (m_type) {
+		case TokenType::Comment:
+			return "'#'";
+		case TokenType::SectionBegin:
+			return "'['";
+		case TokenType::SectionEnd:
+			return "']'";
+		case TokenType::QuoteSimple:
+			return "'";
+		case TokenType::QuoteDouble:
+			return "\"";
+		case TokenType::NewLine:
+			return "<newline>";
+		case TokenType::Assign:
+			return "=";
+		case TokenType::Include:
+			return "@";
+		case TokenType::Space:
+			return "<blank>";
+		case TokenType::Word:
+			return "`" + m_value + "'";
+		default:
+			break;
+		}
+
+		return "";
+	}
+};
+
+std::ostream &operator<<(std::ostream &out, const Token &token)
+{
+	out << token.type();
+
+	if (token.type() == TokenType::Space) {
+		out << ": size = " << token.value().size();
+	} else if (token.type() == TokenType::Word) {
+		out << ": value = [" << token.value() << "]";
+	}
+
+	return out;
+}
+
+using TokenStack = std::vector<Token>;
+
+/* --------------------------------------------------------
+ * IniBuilder
+ * -------------------------------------------------------- */
+
+class IniBuilder {
+private:
+	std::string m_path;
+	std::string m_base;
+	Ini &m_ini;
+
+private:
+	inline bool isReserved(char c) const noexcept
+	{
+		return c == '\n' || c == '#' || c == '"' || c == '\'' || c == '=' || c == '[' || c == ']' || c == '@';
+	}
+
+	std::string base(std::string path)
+	{
+		auto pos = path.find_last_of("/\\");
+
+		if (pos != std::string::npos) {
+			path.erase(pos);
+		} else {
+			path = ".";
+		}
+
+		return path;
+	}
+
+#if defined(_WIN32)
+	bool isAbsolute(const std::string &path)
+	{
+		return !PathIsRelative(path.c_str());
+	}
+#else
+	bool isAbsolute(const std::string &path)
+	{
+		return path.size() > 0 && path[0] == '/';
+	}
+#endif
+
+	std::vector<Token> analyze(std::istream &stream) const
+	{
+		std::istreambuf_iterator<char> it(stream);
+		std::istreambuf_iterator<char> end;
+		std::vector<Token> tokens;
+
+		int lineno{1};
+		int position{0};
+
+		while (it != end) {
+			std::string value;
+
+			if (isReserved(*it)) {
+				while (it != end && isReserved(*it)) {
+					// Single character tokens
+					switch (*it) {
+					case '\n':
+						++lineno;
+						position = 0;
+					case '#':
+					case '[':
+					case ']':
+					case '\'':
+					case '"':
+					case '=':
+					case '@':
+						tokens.push_back({ static_cast<TokenType>(*it), lineno, position });
+						++it;
+						++position;
+					default:
+						break;
+					}
+				}
+			} else if (std::isspace(*it)) {
+				while (it != end && std::isspace(*it) && *it != '\n') {
+					value.push_back(*it++);
+				}
+
+				tokens.push_back({ TokenType::Space, lineno, position, std::move(value) });
+			} else {
+				while (it != end && !std::isspace(*it) && !isReserved(*it)) {
+					value.push_back(*it++);
+				}
+
+				tokens.push_back({ TokenType::Word, lineno, position, std::move(value) });
+			}
+		}
+
+		return tokens;
+	}
+
+	void readComment(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		while (it != end && it->type() != TokenType::NewLine) {
+			++ it;
+		}
+
+		// remove new line
+		++ it;
+	}
+
+	void readSpace(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		while (it != end && it->type() == TokenType::Space) {
+			++ it;
+		}
+	}
+
+	void readNewLine(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		while (it != end && it->type() == TokenType::NewLine) {
+			++ it;
+		}
+	}
+
+	IniSection readSection(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		if (++it == end || it->type() != TokenType::Word) {
+			throw IniError(it->line(), it->position(), "word expected after [, got " + it->toString());
+		}
+
+		IniSection section(it->value());
+
+		if (++it == end || it->type() != TokenType::SectionEnd) {
+			throw IniError(it->line(), it->position(), "] expected, got " + it->toString());
+		}
+
+		// Remove ]
+		++ it;
+
+		if (it == end) {
+			return section;
+		}
+
+		while (it != end && it->type() != TokenType::SectionBegin) {
+			if (it->type() == TokenType::Space) {
+				readSpace(it, end);
+			} else if (it->type() == TokenType::NewLine) {
+				readNewLine(it, end);
+			} else if (it->type() == TokenType::Comment) {
+				readComment(it, end);
+			} else if (it->type() == TokenType::Word) {
+				section.push_back(readOption(it, end));
+			} else {
+				throw IniError(it->line(), it->position(), "unexpected token " + it->toString());
+			}
+		}
+
+		return section;
+	}
+
+	IniOption readOption(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		std::string key = it->value();
+
+		if (++it == end) {
+			throw IniError(it->line(), it->position(), "expected '=' after option declaration, got <EOF>");
+		}
+
+		readSpace(it, end);
+
+		if (it == end || it->type() != TokenType::Assign) {
+			throw IniError(it->line(), it->position(), "expected '=' after option declaration, got " + it++->toString());
+		}
+
+		readSpace(++it, end);
+
+		std::ostringstream oss;
+
+		if (it->type() == TokenType::QuoteSimple || it->type() == TokenType::QuoteDouble) {
+			TokenStack::iterator save = it++;
+
+			while (it != end && it->type() != save->type()) {
+				oss << it++->value();
+			}
+
+			if (it == end)
+				throw IniError(save->line(), save->position(), "undisclosed quote: " + save->toString() + " expected");
+
+			++ it;
+		} else if (it->type() == TokenType::Word) {
+			oss << it++->value();
+		} else if (it->type() != TokenType::NewLine && it->type() != TokenType::Comment) {
+			// No value requested, must be NewLine or comment
+			throw IniError(it->line(), it->position(), "expected option value after '=', got " + it->toString());
+		}
+
+
+		return IniOption(std::move(key), oss.str());
+	}
+
+	void readInclude(TokenStack::iterator &it, TokenStack::iterator end)
+	{
+		if (++it == end || (it->type() != TokenType::Word || it->value() != "include")) {
+			throw IniError(it->line(), it->position(), "expected `include' after '@' token, got " + it->toString());
+		}
+
+		readSpace(++it, end);
+
+		// Quotes mandatory
+		TokenStack::iterator save = it;
+
+		if (it == end || (it->type() != TokenType::QuoteSimple && it->type() != TokenType::QuoteDouble)) {
+			throw IniError(it->line(), it->position(), "expected filename after @include statement");
+		}
+
+		// Filename
+		if (++it == end || it->type() != TokenType::Word) {
+			throw IniError(it->line(), it->position(), "expected filename after @include statement");
+		}
+
+		std::string value = it->value();
+		std::string fullpath;
+
+		if (isAbsolute(value)) {
+			fullpath = value;
+		} else {
+			fullpath = m_base + "/" + it->value();
+		}
+
+		// Must be closed with the same quote
+		if (++it == end || it->type() != save->type()) {
+			throw IniError(save->line(), save->position(), "undiclosed quote: " + save->toString() + " expected");
+		}
+
+		// Remove quote
+		++ it;
+
+		IniBuilder(m_ini, fullpath);
+	}
+
+public:
+	IniBuilder(Ini &ini, std::string path)
+		: m_path(path)
+		, m_base(base(std::move(path)))
+		, m_ini(ini)
+	{
+		std::ifstream file(m_path);
+
+		if (!file.is_open())
+			throw std::runtime_error(std::strerror(errno));
+
+		std::vector<Token> ts = analyze(file);
+
+		auto it = ts.begin();
+		auto end = ts.end();
+
+		while (it != end) {
+			if (it->type() == TokenType::Space) {
+				readSpace(it, end);
+			} else if (it->type() == TokenType::NewLine) {
+				readNewLine(it, end);
+			} else if (it->type() == TokenType::Comment) {
+				readComment(it, end);
+			} else if (it->type() == TokenType::Include) {
+				readInclude(it, end);
+			} else if (it->type() == TokenType::SectionBegin) {
+				m_ini.push_back(readSection(it, end));
+			} else {
+				throw IniError(it->line(), it->position(), "unexpected " + it->toString() + " on root document");
+			}
+		}
+	}
+};
+
+} // !namespace
+
+/* --------------------------------------------------------
+ * Ini
+ * -------------------------------------------------------- */
+
+Ini::Ini(const std::string &path)
+{
+	IniBuilder(*this, path);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Ini.h	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,494 @@
+/*
+ * Ini.h -- .ini file parsing
+ *
+ * Copyright (c) 2013, 2014 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 _INI_H_
+#define _INI_H_
+
+/**
+ * @file Ini.h
+ * @brief Configuration file parser
+ */
+
+#include <algorithm>
+#include <deque>
+#include <stdexcept>
+#include <string>
+
+/**
+ * @class IniError
+ * @brief Error in a file
+ */
+class IniError : public std::exception {
+private:
+	int m_line;
+	int m_position;
+	std::string m_error;
+
+public:
+	/**
+	 * Construct an error.
+	 *
+	 * @param line the line
+	 * @param position the position
+	 * @param error the error
+	 */
+	inline IniError(int line, int position, std::string error)
+		: m_line(line)
+		, m_position(position)
+		, m_error(std::move(error))
+	{
+	}
+
+	/**
+	 * Return the line number.
+	 *
+	 * @return the line
+	 */
+	inline int line() const noexcept
+	{
+		return m_line;
+	}
+
+	/**
+	 * Return the position in the current line.
+	 *
+	 * @return the position
+	 */
+	inline int position() const noexcept
+	{
+		return m_position;
+	}
+
+	/**
+	 * Get the error string.
+	 *
+	 * @return the string
+	 */
+	inline const char *what() const noexcept
+	{
+		return m_error.c_str();
+	}
+};
+
+/**
+ * @class IniOption
+ * @brief Option definition
+ */
+class IniOption {
+private:
+	std::string m_key;
+	std::string m_value;
+
+public:
+	/**
+	 * Construct an option.
+	 *
+	 * @param key the key
+	 * @param value the value
+	 */
+	inline IniOption(std::string key, std::string value)
+		: m_key(std::move(key))
+		, m_value(std::move(value))
+	{
+	}
+
+	/**
+	 * Get the option key.
+	 *
+	 * @return the key
+	 */
+	inline const std::string &key() const noexcept
+	{
+		return m_key;
+	}
+
+	/**
+	 * Get the option value.
+	 *
+	 * @return the value
+	 */
+	inline const std::string &value() const noexcept
+	{
+		return m_value;
+	}
+};
+
+/**
+ * @class IniSection
+ * @brief Section that contains one or more options
+ */
+class IniSection {
+private:
+	std::string m_key;
+	std::deque<IniOption> m_options;
+
+	template <typename T>
+	T find(const std::string &key) const
+	{
+		auto it = std::find_if(m_options.begin(), m_options.end(), [&] (const IniOption &o) {
+			return o.key() == key;
+		});
+
+		if (it == m_options.end())
+			throw std::out_of_range("option " + key + " not found");
+
+		return const_cast<T>(*it);
+	}
+
+public:
+	/**
+	 * Default constructor has no sections and no values.
+	 */
+	IniSection() = default;
+
+	/**
+	 * Construct a section with a set of options.
+	 *
+	 * @param key the section name
+	 * @param options the list of options
+	 */
+	inline IniSection(std::string key, std::deque<IniOption> options = {}) noexcept
+		: m_key(std::move(key))
+		, m_options(std::move(options))
+	{
+	}
+
+	/**
+	 * Get the section key.
+	 *
+	 * @return the key
+	 */
+	inline const std::string &key() const noexcept
+	{
+		return m_key;
+	}
+
+	/**
+	 * Get an iterator to the beginning.
+	 *
+	 * @return the iterator
+	 */
+	inline auto begin() noexcept
+	{
+		return m_options.begin();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto begin() const noexcept
+	{
+		return m_options.begin();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto cbegin() const noexcept
+	{
+		return m_options.cbegin();
+	}
+
+	/**
+	 * Get an iterator to the end.
+	 *
+	 * @return the iterator
+	 */
+	inline auto end() noexcept
+	{
+		return m_options.end();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto end() const noexcept
+	{
+		return m_options.end();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto cend() const noexcept
+	{
+		return m_options.cend();
+	}
+
+	/**
+	 * Append an option.
+	 *
+	 * @param option the option to add
+	 */
+	inline void push_back(IniOption option)
+	{
+		m_options.push_back(std::move(option));
+	}
+
+	/**
+	 * Push an option to the beginning.
+	 *
+	 * @param option the option to add
+	 */
+	inline void push_front(IniOption option)
+	{
+		m_options.push_front(std::move(option));
+	}
+
+	/**
+	 * Get the number of options in that section.
+	 *
+	 * @return the size
+	 */
+	inline unsigned size() const noexcept
+	{
+		return m_options.size();
+	}
+
+	/**
+	 * Access an option at the specified index.
+	 *
+	 * @param index the index
+	 * @return the option
+	 * @warning No bounds checking is performed
+	 */
+	inline IniOption &operator[](int index) noexcept
+	{
+		return m_options[index];
+	}
+
+	/**
+	 * Access an option at the specified index.
+	 *
+	 * @param index the index
+	 * @return the option
+	 * @warning No bounds checking is performed
+	 */
+	inline const IniOption &operator[](int index) const noexcept
+	{
+		return m_options[index];
+	}
+
+	/**
+	 * Access an option at the specified key.
+	 *
+	 * @param key the key
+	 * @return the option
+	 * @warning No bounds checking is performed
+	 */
+	inline IniOption &operator[](const std::string &key)
+	{
+		return find<IniOption &>(key);
+	}
+
+	/**
+	 * Access an option at the specified key.
+	 *
+	 * @param key the key
+	 * @return the option
+	 * @warning No bounds checking is performed
+	 */
+	inline const IniOption &operator[](const std::string &key) const
+	{
+		return find<const IniOption &>(key);
+	}
+};
+
+/**
+ * @class Ini
+ * @brief Ini config file loader
+ */
+class Ini {
+private:
+	std::deque<IniSection> m_sections;
+
+	template <typename T>
+	T find(const std::string &key) const
+	{
+		auto it = std::find_if(m_sections.begin(), m_sections.end(), [&] (const IniSection &s) {
+			return s.key() == key;
+		});
+
+		if (it == m_sections.end())
+			throw std::out_of_range("section " + key + " not found");
+
+		return const_cast<T>(*it);
+	}
+
+public:
+	/**
+	 * Default constructor with an empty configuration.
+	 */
+	Ini() = default;
+
+	/**
+	 * Open the path as the configuration file.
+	 *
+	 * @param path the path
+	 * @throw IniError on any error
+	 */
+	Ini(const std::string &path);
+
+	/**
+	 * Get an iterator to the beginning.
+	 *
+	 * @return the iterator
+	 */
+	inline auto begin() noexcept
+	{
+		return m_sections.begin();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto begin() const noexcept
+	{
+		return m_sections.begin();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto cbegin() const noexcept
+	{
+		return m_sections.cbegin();
+	}
+
+	/**
+	 * Get an iterator to the end.
+	 *
+	 * @return the iterator
+	 */
+	inline auto end() noexcept
+	{
+		return m_sections.end();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto end() const noexcept
+	{
+		return m_sections.end();
+	}
+
+	/**
+	 * Overloaded function.
+	 *
+	 * @return the iterator
+	 */
+	inline auto cend() const noexcept
+	{
+		return m_sections.cend();
+	}
+
+	/**
+	 * Get the number of sections in the configuration.
+	 *
+	 * @return the size
+	 */
+	inline unsigned size() const noexcept
+	{
+		return m_sections.size();
+	}
+
+	/**
+	 * Append a section to the end.
+	 *
+	 * @param section the section to add
+	 */
+	inline void push_back(IniSection section)
+	{
+		m_sections.push_back(std::move(section));
+	}
+
+	/**
+	 * Add a section to the beginning.
+	 *
+	 * @param section the section to add
+	 */
+	inline void push_front(IniSection section)
+	{
+		m_sections.push_front(std::move(section));
+	}
+
+	/**
+	 * Access a section at the specified index.
+	 *
+	 * @param index the index
+	 * @return the section
+	 * @warning No bounds checking is performed
+	 */
+	inline IniSection &operator[](int index) noexcept
+	{
+		return m_sections[index];
+	}
+
+	/**
+	 * Access a section at the specified index.
+	 *
+	 * @param index the index
+	 * @return the section
+	 * @warning No bounds checking is performed
+	 */
+	inline const IniSection &operator[](int index) const noexcept
+	{
+		return m_sections[index];
+	}
+
+	/**
+	 * Access a section at the specified key.
+	 *
+	 * @param key the key
+	 * @return the section
+	 * @warning No bounds checking is performed
+	 */
+	inline IniSection &operator[](const std::string &key)
+	{
+		return find<IniSection &>(key);
+	}
+
+	/**
+	 * Access a section at the specified key.
+	 *
+	 * @param key the key
+	 * @return the section
+	 * @warning No bounds checking is performed
+	 */
+	inline const IniSection &operator[](const std::string &key) const
+	{
+		return find<IniSection &>(key);
+	}
+};
+
+#endif // !_INI_H_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/CMakeLists.txt	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,42 @@
+#
+# CMakeLists.txt -- tests for Ini
+#
+# Copyright (c) 2013, 2014 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.
+#
+
+set(
+	SOURCES
+	${code_SOURCE_DIR}/C++/Ini.cpp
+	${code_SOURCE_DIR}/C++/Ini.h
+	main.cpp
+	configs/simple.conf
+	configs/multi.conf
+	configs/novalue.conf
+	configs/compact.conf
+	configs/includes.conf
+	configs/error-badcomment.conf
+	configs/error-lineassigment.conf
+	configs/error-nosection.conf
+)
+
+define_test(ini "${SOURCES}")
+
+add_custom_command(
+	TARGET ini
+	POST_BUILD
+	COMMENT "Copying examples files"
+	COMMAND
+		${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/configs $<TARGET_FILE_DIR:ini>
+)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/compact.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,1 @@
+[general]verbose=true foreground=false[server]host=google.fr
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/error-badcomment.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,2 @@
+[general]
+verbose #hello = xyz
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/error-badsection.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,2 @@
+[[general]
+verbose = false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/error-lineassigment.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,4 @@
+[general]
+host
+=
+google.fr
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/error-nosection.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,1 @@
+option = value
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/includes.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,5 @@
+# With some includes
+@include "simple.conf"	# comments also work here
+
+[standard]
+verbose = false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/multi.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,7 @@
+[entity]
+name	= "Player"
+version	= 1.0
+
+[entity]
+name	= "Subwinner"
+version	= 2.0
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/novalue.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,6 @@
+[plugins]
+histedit=
+highlight= #empty
+general = 
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/simple.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,4 @@
+[general]
+option1=1
+option2 =2
+option3 = 3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/configs/tokens.conf	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,3 @@
+[tokens]
+bracket = "I have [brackets]"
+at = "I have foo@at"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/C++/Tests/Ini/main.cpp	Fri Mar 06 22:01:40 2015 +0100
@@ -0,0 +1,251 @@
+/*
+ * main.cpp -- main test file for Ini
+ *
+ * Copyright (c) 2013, 2014 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 <gtest/gtest.h>
+
+#include <Ini.h>
+
+class BasicTest : public testing::Test {
+protected:
+	Ini m_ini;
+
+public:
+	BasicTest()
+		: m_ini("simple.conf")
+	{
+	}
+
+};
+
+TEST_F(BasicTest, simple)
+{
+	ASSERT_EQ(1, static_cast<int>(m_ini.size()));
+}
+
+TEST_F(BasicTest, iniOperators)
+{
+	try {
+		ASSERT_EQ(3, static_cast<int>(m_ini[0].size()));
+		ASSERT_EQ("general", m_ini[0].key());
+		ASSERT_EQ("general", m_ini["general"].key());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+TEST_F(BasicTest, iniSectionOperators)
+{
+	try {
+		// option1=1 (indexes)
+		ASSERT_EQ("option1", m_ini[0][0].key());
+		ASSERT_EQ("1", m_ini[0][0].value());
+
+		// option1=1 (keys)
+		ASSERT_EQ("option1", m_ini["general"]["option1"].key());
+		ASSERT_EQ("1", m_ini["general"]["option1"].value());
+
+		// option2 =2 (indexes)
+		ASSERT_EQ("option2", m_ini[0][1].key());
+		ASSERT_EQ("2", m_ini[0][1].value());
+
+		// option2 =2 (keys)
+		ASSERT_EQ("option2", m_ini["general"]["option2"].key());
+		ASSERT_EQ("2", m_ini["general"]["option2"].value());
+
+		// option3 = 3 (indexes)
+		ASSERT_EQ("option3", m_ini[0][2].key());
+		ASSERT_EQ("3", m_ini[0][2].value());
+
+		// option3 = 3 (keys)
+		ASSERT_EQ("option3", m_ini["general"]["option3"].key());
+		ASSERT_EQ("3", m_ini["general"]["option3"].value());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+/* --------------------------------------------------------
+ * Reserved tokens in words
+ * -------------------------------------------------------- */
+
+TEST(Tokens, iniReserved)
+{
+	try {
+		Ini ini("tokens.conf");
+
+		ASSERT_EQ("I have [brackets]", ini["tokens"]["bracket"].value());
+		ASSERT_EQ("I have foo@at", ini["tokens"]["at"].value());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+/* --------------------------------------------------------
+ * Multiple definition
+ * -------------------------------------------------------- */
+
+class MultiTest : public testing::Test {
+protected:
+	Ini m_ini;
+
+public:
+	MultiTest()
+		: m_ini("multi.conf")
+	{
+	}
+};
+
+TEST_F(MultiTest, defined)
+{
+	ASSERT_EQ(2, static_cast<int>(m_ini.size()));
+	ASSERT_EQ("name", m_ini[0]["name"].key());
+	ASSERT_EQ("Player", m_ini[0]["name"].value());
+	ASSERT_EQ("version", m_ini[0]["version"].key());
+	ASSERT_EQ("1.0", m_ini[0]["version"].value());
+	ASSERT_EQ("name", m_ini[1]["name"].key());
+	ASSERT_EQ("Subwinner", m_ini[1]["name"].value());
+	ASSERT_EQ("version", m_ini[1]["version"].key());
+	ASSERT_EQ("2.0", m_ini[1]["version"].value());
+}
+
+/* --------------------------------------------------------
+ * Option with no values
+ * -------------------------------------------------------- */
+
+class NoValueTest : public testing::Test {
+protected:
+	Ini m_ini;
+
+public:
+	NoValueTest()
+		: m_ini("novalue.conf")
+	{
+	}
+};
+
+TEST_F(NoValueTest, isDefined)
+{
+	ASSERT_EQ("plugins", m_ini[0].key());
+	ASSERT_EQ("", m_ini["plugins"]["histedit"].value());
+	ASSERT_EQ("", m_ini["plugins"]["highlight"].value());
+	ASSERT_EQ("", m_ini["plugins"]["general"].value());
+}
+
+/* --------------------------------------------------------
+ * Include tests
+ * -------------------------------------------------------- */
+
+class IncludeTest : public testing::Test {
+protected:
+	Ini m_ini;
+
+public:
+	IncludeTest()
+		: m_ini("includes.conf")
+	{
+	}
+};
+
+TEST_F(IncludeTest, all)
+{
+	ASSERT_EQ(2, static_cast<int>(m_ini.size()));
+
+	// from include
+	ASSERT_EQ("1", m_ini[0][0].value());
+	ASSERT_EQ("2", m_ini[0][1].value());
+	ASSERT_EQ("3", m_ini[0][2].value());
+
+	// from standard
+	ASSERT_EQ("false", m_ini[1][0].value());
+}
+
+/* --------------------------------------------------------
+ * Compact
+ * -------------------------------------------------------- */
+
+TEST(Compact, test)
+{
+	try {
+		Ini ini("compact.conf");
+
+		ASSERT_EQ(2, static_cast<int>(ini.size()));
+		ASSERT_EQ("true", ini["general"]["verbose"].value());
+		ASSERT_EQ("false", ini["general"]["foreground"].value());
+		ASSERT_EQ("google.fr", ini["server"]["host"].value());
+	} catch (const std::exception &ex) {
+		FAIL() << ex.what();
+	}
+}
+
+/* --------------------------------------------------------
+ * Errors
+ * -------------------------------------------------------- */
+
+TEST(Errors, nosection)
+{
+	// An option outside a section is not allowed
+	try {
+		Ini ini("error-nosection.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const std::exception &ex) {
+	}
+}
+
+TEST(Errors, lineassigment)
+{
+	// The = assignment must be on the same line as the option key
+	try {
+		Ini ini("error-lineassigment.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const std::exception &ex) {
+	}
+}
+
+TEST(Errors, badcomment)
+{
+	// Comment can't between option-key and = assigment
+	try {
+		Ini ini("error-badcomment.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const std::exception &ex) {
+	}
+}
+
+TEST(Errors, badsection)
+{
+	// Bad section naming
+	try {
+		Ini ini("error-badsection.conf");
+
+		FAIL() << "Failure expected, got success";
+	} catch (const std::exception &ex) {
+		std::cout << ex.what() << std::endl;
+	}
+}
+
+int main(int argc, char **argv)
+{
+	testing::InitGoogleTest(&argc, argv);
+
+	return RUN_ALL_TESTS();
+}
--- a/CMakeLists.txt	Tue Mar 03 18:57:50 2015 +0100
+++ b/CMakeLists.txt	Fri Mar 06 22:01:40 2015 +0100
@@ -52,10 +52,11 @@
 option(WITH_DYNLIB "Enable DynLib tests" On)
 option(WITH_FLAGS "Enable Flags tests" On)
 option(WITH_HASH "Enable hash functions tests" On)
+option(WITH_INI "Enable .ini parser" On)
 option(WITH_JSON "Enable Jansson wrapper tests" On)
 option(WITH_OPTIONPARSER "Enable option parser tests" On)
 option(WITH_PACK "Enable pack functions" On)
-option(WITH_PARSER "Enable parser tests" On)
+option(WITH_PARSER "Enable parser tests (deprecated)" On)
 option(WITH_SOCKETS "Enable sockets tests" On)
 option(WITH_TREENODE "Enable treenode tests" On)
 option(WITH_UTF8 "Enable Utf8 functions tests" On)
@@ -89,6 +90,10 @@
 	add_subdirectory(C++/Tests/Json)
 endif ()
 
+if (WITH_INI)
+	add_subdirectory(C++/Tests/Ini)
+endif ()
+
 if (WITH_OPTIONPARSER)
 	add_subdirectory(C++/Tests/OptionParser)
 endif ()