changeset 324:99e83685d4da

OptionParser: complete rewrite
author David Demelier <markand@malikania.fr>
date Fri, 06 Mar 2015 18:46:53 +0100
parents c5dd79aaa216
children 9e223d1de96f
files C++/OptionParser.cpp C++/OptionParser.h C++/Tests/OptionParser/main.cpp
diffstat 3 files changed, 454 insertions(+), 658 deletions(-) [+]
line wrap: on
line diff
--- a/C++/OptionParser.cpp	Tue Mar 03 18:57:50 2015 +0100
+++ b/C++/OptionParser.cpp	Fri Mar 06 18:46:53 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * OptionParser.cpp -- safe getopt(3) replacement
+ * OptionParser.cpp -- command line option parser
  *
  * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
  *
@@ -16,190 +16,188 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-#include <iostream>
-#include <iomanip>
+#include <algorithm>
 
 #include "OptionParser.h"
 
-/* --------------------------------------------------------
- * Option class
- * -------------------------------------------------------- */
+bool OptionParser::isShort(const std::string &arg) const
+{
+	return arg.size() >= 2 && arg[0] == '-' && arg[1] != '-';
+}
+
+bool OptionParser::isLong(const std::string &arg) const
+{
+	return arg.size() >= 3 && arg[0] == '-' && arg[1] == '-' && arg[2] != '-';
+}
 
-Option::Option(const std::string &name, const std::string &help, Type type, int flags)
-	: m_name(name)
-	, m_help(help)
-	, m_flags(flags)
-	, m_type(type)
+bool OptionParser::isOption(const std::string &arg) const
 {
+	return isShort(arg) || isLong(arg);
+}
+
+std::string OptionParser::key(const std::string &arg) const
+{
+	if (isShort(arg))
+		return arg.substr(1, 1);
+
+	return arg.substr(2);
 }
 
-const std::string &Option::name() const
+bool OptionParser::isShortCompacted(const std::string &arg) const
 {
-	return m_name;
+	return arg.size() >= 3;
 }
 
-const std::string &Option::token() const
+bool OptionParser::isDefined(const std::string &arg) const
 {
-	return m_token;
+	auto n = key(arg);
+	auto it = std::find_if(m_options.begin(), m_options.end(), [&] (const Option &o) -> bool {
+		return o.key() == n || o.full() == n;
+	});
+
+	return it != m_options.end();
+}
+
+const Option &OptionParser::get(const std::string &arg) const
+{
+	std::string n = key(arg);
+
+	return *std::find_if(m_options.begin(), m_options.end(), [&] (const Option &o) -> bool {
+		return o.key() == n || o.full() == n;
+	});
+}
+
+bool OptionParser::isToggle(const std::string &arg) const
+{
+	return (get(arg).flags() & Option::NoArg);
 }
 
-const std::string &Option::help() const
+void OptionParser::readShort(OptionPack &pack, Args::const_iterator &it, Args::const_iterator end) const
 {
-	return m_help;
-}
+	/*
+	 * There are many options when passing a short option:
+	 *
+	 * 1. -cmyconfig	Takes on argument but parsed as unique,
+	 * 2. -c myconfig	Takes on argument but parsed as two strings
+	 * 3. -abc		If a is not a toggle option, its argument is `bc'
+	 * 4. -abc		If a is a toggle option and b, c are toggle, they are added
+	 */
+
+	std::string v = it->substr(2);
+	std::string k = key(*it);
+	const Option &option = get(std::string("-") + k);
+
+	if (isToggle(*it)) {
+		// 3. and optionally 4.
+		pack.push_back(OptionValue(option, ""));
+		pack.m_argsParsed += 1;
 
-Option::Type Option::type() const
-{
-	return m_type;
+		if (isShortCompacted(*it)) {
+			for (char c : v) {
+				if (!isDefined("-" + std::string(1, c))) {
+					pack.m_error = "-" + std::string(1, c) + " is not a valid option";
+					break;
+				}
+
+				const Option &sub = get("-" + std::string(1, c));
+
+				pack.push_back(OptionValue(sub, ""));
+			}
+		}
+
+		++ it;
+	} else {
+		// 1.
+		if (isShortCompacted(*it++)) {
+			pack.push_back(OptionValue(option, v));
+			pack.m_argsParsed += 1;
+		} else {
+			// 2.
+			if (it == end) {
+				pack.m_error = option.key() + " requires an option";
+			} else {
+				pack.push_back(OptionValue(option, *it++));
+				pack.m_argsParsed += 2;
+			}
+		}
+	}
 }
 
-int Option::flags() const
+void OptionParser::readFull(OptionPack &pack, Args::const_iterator &it, Args::const_iterator end) const
 {
-	return m_flags;
+	/*
+	 * Long options can't be compacted, there are only two possibilities:
+	 *
+	 * 1. --fullscreen	No argument
+	 * 2. --config foo	One argument
+	 */
+	const Option &option = get(*it);
+
+	if (!isToggle(*it)) {
+		// 2.
+		if (++it == end) {
+			pack.m_error = "--" + option.full() + " requires an option";
+		} else {
+			pack.push_back(OptionValue(option, *it++));
+			pack.m_argsParsed += 2;
+		}
+	} else {
+		pack.push_back(OptionValue(option, ""));
+		pack.m_argsParsed ++;
+
+		++ it;
+	}
 }
 
-/* --------------------------------------------------------
- * OptionValue class
- * -------------------------------------------------------- */
-
-OptionValue::OptionValue(const std::string &name)
-	: m_name(name)
-	, m_enabled(true)
-{
-}
-
-OptionValue::OptionValue(const std::string &name, const std::string &value)
-	: m_name(name)
-	, m_value(value)
+OptionParser::OptionParser(std::initializer_list<Option> options)
+	: m_options(options.begin(), options.end())
 {
 }
 
-const std::string &OptionValue::name() const
-{
-	return m_name;
-}
-
-const std::string &OptionValue::value() const
-{
-	return m_value;
-}
-
-OptionValue::operator bool() const
-{
-	return m_enabled;
-}
-
-OptionValue::operator std::string() const
+OptionParser::OptionParser(std::vector<Option> options)
+	: m_options(std::move(options))
 {
-	return m_value;
-}
-
-/* --------------------------------------------------------
- * OptionResult class
- * -------------------------------------------------------- */
-
-auto OptionResult::begin() -> decltype(m_values.begin())
-{
-	return m_values.begin();
-}
-
-auto OptionResult::begin() const -> decltype(m_values.begin())
-{
-	return m_values.begin();
-}
-
-auto OptionResult::cbegin() const -> decltype(m_values.cbegin())
-{
-	return m_values.begin();
 }
 
-auto OptionResult::end() -> decltype(m_values.end())
-{
-	return m_values.end();
-}
-
-auto OptionResult::end() const -> decltype(m_values.end())
+OptionPack OptionParser::parse(Args::const_iterator it, Args::const_iterator end, int flags) const
 {
-	return m_values.end();
-}
-
-auto OptionResult::cend() const -> decltype(m_values.cend())
-{
-	return m_values.cend();
-}
-
-int OptionResult::count() const
-{
-	return static_cast<int>(m_values.size());
-}
+	OptionPack pack;
 
-int OptionResult::total() const
-{
-	return m_total;
-}
-
-/* --------------------------------------------------------
- * OptionParser class
- * -------------------------------------------------------- */
-
-OptionParser::Args OptionParser::unpack(const Args &args) const
-{
-	if (m_style != Getopt)
-		return args;
-
-	Args ret;
-
-	for (const auto &s : args) {
-		auto length = s.length();
-
-		if (length < 2 || s[0] != '-' || s[1] == '-') {
-			ret.push_back(s);
-			continue;
+	while (it != end) {
+		if (!isOption(*it)) {
+			if (flags & Unstrict) {
+				pack.m_argsParsed ++;
+				it ++;
+				continue;
+			} else {
+				pack.m_error = *it + " is not an option";
+				return pack;
+			}
 		}
 
-		for (int i = 1; i < (int)length; ++i) {
-			ret.push_back(std::string("-") + std::string(&s[i], 1));
+		if (!isDefined(*it)) {
+			pack.m_error = "Invalid option";
+			return pack;
+		}
+
+		if (isShort(*it)) {
+			readShort(pack, it, end);
+		} else {
+			readFull(pack, it, end);
+		}
+
+		// Read failure
+		if (pack.m_error.size() > 0) {
+			return pack;
 		}
 	}
 
-	return ret;
-}
-
-OptionParser::OptionParser(Style style)
-	: m_style(style)
-{
+	return pack;
 }
 
-void OptionParser::add(Option &&option)
+OptionPack OptionParser::parse(int argc, char **argv, int flags) const
 {
-	const auto &length = option.m_name.size();
-
-	if (length > m_maxlength)
-		m_maxlength = length;
-
-	/*
-	 * If style is Getopt, we should use a double dash as the long option
-	 * like --color and a single for short options like -v
-	 */
-	if (m_style == Getopt) {
-		if (length > 1)
-			option.m_token = "--" + option.m_name;
-		else
-			option.m_token = "-" + option.m_name;
-	} else
-		option.m_token = "/" + option.m_name;
-
-	m_options.emplace(std::make_pair(option.m_token, std::move(option)));
-}
-
-void OptionParser::add(const Option &option)
-{
-	add(Option(option));
-}
-
-OptionResult OptionParser::parse(int argc, const char * const *argv, int flags)
-{
-	Args args;
+	std::vector<std::string> args;
 
 	for (int i = 0; i < argc; ++i)
 		args.push_back(argv[i]);
@@ -207,87 +205,7 @@
 	return parse(args, flags);
 }
 
-OptionResult OptionParser::parse(const Args &argslist, int flags)
+OptionPack OptionParser::parse(const std::vector<std::string> &args, int flags) const
 {
-	OptionResult result;
-	int i;
-	auto args = unpack(argslist);
-	auto length = args.size();
-
-	for (i = 0; i < (int)length; ++i) {
-		/* Not a valid option at all? */
-		if (m_options.count(args[i]) == 0) {
-			/* When breaking is enabled we should not emit a warning */
-			if (flags & Strict) {
-				if (!(flags & Silent)) {
-					log(args[i], args[i] + " is not a valid option");
-					usage();
-				}
-
-				break;
-			} else if (flags & BreakNonOption)
-				break;
-			else
-				continue;
-		}
-
-		/*
-		 * At this step, we have an option name valid, check if it requires
-		 * an argument or not.
-		 */
-		const auto &option = m_options.at(args[i]);
-		const auto &name = option.name();
-		auto type = option.type();
-
-		/*
-		 * Already present and must appears only once?
-		 */
-		if ((option.flags() & Option::Single) && m_placed.count(name) > 0) {
-			if (!(flags & Silent)) {
-				log(name, "option " + name + " must appear only once");
-
-				if (flags & Strict) {
-					usage();
-					break;
-				}
-			} else if (flags & Strict)
-				break;
-		}
-
-		/*
-		 * Need an argument or it is a simple switch ?
-		 */
-		if (type == Option::Type::Switch)
-			result.m_values.push_back(OptionValue(name));
-		else {
-			if (i + 1 >= (int)length)
-				result.m_values.push_back(OptionValue(name, ""));
-			else {
-				result.m_values.push_back(OptionValue(name, args[i + 1]));
-				++ i;
-			}
-		}
-
-		m_placed.insert(name);
-	}
-
-	result.m_total = i;
-
-	return result;
-
+	return parse(args.begin(), args.end(), flags);
 }
-
-void OptionParser::usage()
-{
-	std::cout << "options available: " << std::endl;
-
-	for (const auto &option : m_options) {
-		std::cout << " " << std::setw(m_maxlength) << option.second.name();
-		std::cout << "\t" << option.second.help() << std::endl;
-	}
-}
-
-void OptionParser::log(const std::string &, const std::string &error)
-{
-	std::cout << error << std::endl;
-}
--- a/C++/OptionParser.h	Tue Mar 03 18:57:50 2015 +0100
+++ b/C++/OptionParser.h	Fri Mar 06 18:46:53 2015 +0100
@@ -1,5 +1,5 @@
 /*
- * OptionParser.h -- safe getopt(3) replacement
+ * OptionParser.h -- command line option parser
  *
  * Copyright (c) 2013, 2014 David Demelier <markand@malikania.fr>
  *
@@ -21,350 +21,255 @@
 
 /**
  * @file OptionParser.h
- * @brief Command line option replacement for getopt(3)
- *
- * This alternative to getopt is thread safe, reentrant and stateless. It does
- * not use any global and is customizable.
+ * @brief Command line option parser
  */
 
+#include <initializer_list>
 #include <string>
 #include <vector>
-#include <map>
-#include <unordered_set>
 
 /**
  * @class Option
- * @brief Option description
- *
- * This class describe an option with its optional flags.
+ * @brief Option definition
  */
-class Option final {
+class Option {
 public:
-	friend class OptionParser;
-
-	/**
-	 * @enum Type
-	 * @brief Describe the type of option
-	 */
-	enum Type {
-		Switch,					//!< just a boolean
-		Argument				//!< need an argument
-	};
-
-	/**
-	 * @enum Flags
-	 * @brief Optional flags
-	 */
 	enum Flags {
-		Multiple = 0,				//!< can appear multiple times
-		Single					//!< must appear exactly once
+		NoArg	= (1 << 0),
 	};
 
 private:
-	std::string	m_name;
-	std::string	m_token;
-	std::string	m_help;
-	int		m_flags;
-	Type		m_type;
+	std::string m_key;
+	std::string m_full;
+	int m_flags;
 
 public:
 	/**
-	 * Build an option.
+	 * Construct an option. By default, an option requires an argument
+	 * unless flags is set to NoArg.
+	 *
+	 * You <strong>must</strong> not prepend dashes to the option names.
 	 *
-	 * @param name the name
-	 * @param help the help message
-	 * @param type the option type
-	 * @param flags the flags
+	 * You don't need to set both short and long names, but you need at
+	 * least one.
+	 *
+	 * @param key the short name (e.g v)
+	 * @param full the long name (e.g verbose)
+	 * @param flags the optional flags
+	 * @see Flags
 	 */
-	Option(const std::string &name,
-	       const std::string &help,
-	       Type type = Type::Switch,
-	       int flags = 0);
-
-	/**
-	 * Get the option name.
-	 *
-	 * @return the name
-	 */
-	const std::string &name() const;
+	inline Option(std::string key, std::string full, int flags = 0)
+		: m_key(std::move(key))
+		, m_full(std::move(full))
+		, m_flags(flags)
+	{
+	}
 
 	/**
-	 * Get the real token to match the option.
+	 * Get the short name (e.g v)
 	 *
-	 * @return the token
+	 * @return the short name
 	 */
-	const std::string &token() const;
+	inline const std::string &key() const noexcept
+	{
+		return m_key;
+	}
 
 	/**
-	 * Get the help.
+	 * Get the long name (e.g verbose)
 	 *
-	 * @return the help
+	 * @return the long name
 	 */
-	const std::string &help() const;
-
-	/**
-	 * Get the option type.
-	 *
-	 * @return the type
-	 */
-	Type type() const;
+	inline const std::string &full() const noexcept
+	{
+		return m_full;
+	}
 
 	/**
 	 * Get the flags.
 	 *
 	 * @return the flags
+	 * @see Flags
 	 */
-	int flags() const;
+	inline int flags() const noexcept
+	{
+		return m_flags;
+	}
 };
 
 /**
  * @class OptionValue
- * @brief Describe the result of an option
- *
- * This class is instanciated when an option has been declared and successfully parsed.
- *
- * One should use the operator bool for boolean options and operator std::string (or value())
- * for options with arguments.
+ * @brief Result of an option parse
  */
-class OptionValue final {
-private:
-	friend class OptionParser;
-
-	std::string	m_name;
-	std::string	m_value;
-	bool		m_enabled = false;
-
+class OptionValue {
 private:
-	/**
-	 * Construct an option value. This is constructor is used
-	 * with Option::Switch.
-	 *
-	 * @param name the option name
-	 */
-	OptionValue(const std::string &name);
-
-	/**
-	 * Construct a value with a parameter.
-	 *
-	 * @param name the name
-	 * @param value the value
-	 */
-	OptionValue(const std::string &name, const std::string &value);
+	std::string m_key;
+	std::string m_full;
+	std::string m_value;
 
 public:
 	/**
-	 * Get the option name.
+	 * Construct an option value
 	 *
-	 * @return the name
+	 * @param option the option
+	 * @param value the value
 	 */
-	const std::string &name() const;
+	inline OptionValue(const Option &option, std::string value)
+		: m_key(option.key())
+		, m_full(option.full())
+		, m_value(std::move(value))
+	{
+	}
 
 	/**
-	 * Get the value.
+	 * Get the value (if the option requires an argument).
 	 *
 	 * @return the value
 	 */
-	const std::string &value() const;
+	inline const std::string &value() const noexcept
+	{
+		return m_value;
+	}
 
-	/**
-	 * Check if the option is enabled (only with Option::Switch)
-	 *
-	 * @return true if enabled
-	 */
-	operator bool() const;
-
-	/**
-	 * Convert the value to string.
-	 *
-	 * @return the value
-	 */
-	operator std::string() const;
+	friend bool operator==(const OptionValue &o1, const std::string &name);
 };
 
 /**
- * @class OptionResult
- * @brief Result of a command line parse
+ * Test the option value with the specified option name.
+ *
+ * You can use both the short option or the long option name depending
+ * on what you have registered to the OptionParser class.
+ *
+ * @param o the option
+ * @param name the short or the full name
+ * @return true if matches
  */
-class OptionResult final {
+inline bool operator==(const OptionValue &o, const std::string &name)
+{
+	return o.m_key == name || o.m_full == name;
+}
+
+/**
+ * @class OptionPack
+ * @brief Object containing results of a parse
+ *
+ * Because parsing bad options does not throw exceptions, this class is
+ * convertible to bool and has the error contained.
+ *
+ * It also have the number of arguments parsed so you can cut your options
+ * depending on the full command line.
+ *
+ * Example:
+ *	-y install -d foo
+ *	-y remove -f
+ *
+ * In that case, you can do two parsing, it will stops (unless Unstrict is set)
+ * until install or remove.
+ */
+class OptionPack : public std::vector<OptionValue> {
 private:
 	friend class OptionParser;
 
-	using Values	= std::vector<OptionValue>;
-
-	Values		m_values;
-	int		m_total = 0;
+	std::string m_error{"No error"};
+	int m_argsParsed{0};
 
 public:
 	/**
-	 * Default constructor.
-	 */
-	OptionResult() = default;
-
-	/**
-	 * Move constructor.
+	 * Get the error.
 	 *
-	 * @param other the other
-	 */
-	OptionResult(OptionResult &&other) = default;
-
-	/**
-	 * Returns an iterator to the beginning of values.
-	 *
-	 * @return the iterator
+	 * @return the error
 	 */
-	auto begin() -> decltype(m_values.begin());
-
-	/**
-	 * Returns a const iterator to the beginning of values.
-	 *
-	 * @return the iterator
-	 */
-	auto begin() const -> decltype(m_values.begin());
-
-	/**
-	 * Returns a const iterator to the beginning of values.
-	 *
-	 * @return the iterator
-	 */
-	auto cbegin() const -> decltype(m_values.cbegin());
+	inline const std::string &error() const noexcept
+	{
+		return m_error;
+	}
 
 	/**
-	 * Returns an iterator to the end of values.
-	 *
-	 * @return the iterator
-	 */
-	auto end() -> decltype(m_values.end());
-
-	/**
-	 * Returns a const iterator to the end of values.
+	 * Get the number of arguments parsed <strong>not the number of
+	 * options</strong>.
 	 *
-	 * @return the iterator
+	 * @return the number of arguments parsed
 	 */
-	auto end() const -> decltype(m_values.end());
-
-	/**
-	 * Returns a const iterator to the end of values.
-	 *
-	 * @return the iterator
-	 */
-	auto cend() const -> decltype(m_values.cend());
+	inline int parsed() const noexcept
+	{
+		return m_argsParsed;
+	}
 
 	/**
-	 * Count the number of parsed options.
-	 *
-	 * @return the number
-	 */
-	int count() const;
-
-	/**
-	 * Get the total number of tokens parsed from the argv vector.
+	 * Convert to true on success.
 	 *
-	 * @return the total number
+	 * @return true on success
 	 */
-	int total() const;
-
-	/**
-	 * Move assignment operator.
-	 *
-	 * @param other the other
-	 * @return the result
-	 */
-	OptionResult &operator=(OptionResult &&other) = default;
+	inline operator bool() const noexcept
+	{
+		return m_error == "No error";
+	}
 };
 
 /**
  * @class OptionParser
- * @brief Parse command line options
+ * @brief Base class for parsing command line options
+ *
+ * The option parser is a replacement for getopt(3) which is reentrant
+ * and does not use globals.
  */
 class OptionParser {
-private:
-	using Placed	= std::unordered_set<std::string>;
-
 public:
-	using Options	= std::map<std::string, Option>;
-	using Args	= std::vector<std::string>;
+	using Map = std::vector<Option>;
+	using Args = std::vector<std::string>;
 
-	enum Style {
-		Getopt,					//!< using dash "-"
-		Windows					//!< using slash "/"
-	};
-
-	enum {
-		BreakNonOption	= (1 << 0),		//!< break on first non option
-		Strict		= (1 << 1),		//!< abort on first error
-		Silent		= (1 << 2),		//!< do not / call any log handler
-		NoCompact	= (1 << 3)		//!< disable -abc instead of -a -b -c (Getopt only)
+	enum Flags {
+		Unstrict =	(1 << 0)
 	};
 
 private:
-	Placed		m_placed;			//!< already placed options
-	Style		m_style;			//!< style to use
+	Map m_options;
 
-	Args unpack(const Args &args) const;
-
-protected:
-	Options		m_options;			//!< list of options descriptions
-	unsigned	m_maxlength = 0;		//!< max option length
+	const Option &get(const std::string &arg) const;
+	std::string key(const std::string &arg) const;
+	bool isDefined(const std::string &arg) const;
+	bool isToggle(const std::string &arg) const;
+	bool isShortCompacted(const std::string &arg) const;
+	bool isShort(const std::string &arg) const;
+	bool isLong(const std::string &arg) const;
+	bool isOption(const std::string &arg) const;
+	void readShort(OptionPack &pack, Args::const_iterator &it, Args::const_iterator end) const;
+	void readFull(OptionPack &pack, Args::const_iterator &it, Args::const_iterator end) const;
+	OptionPack parse(Args::const_iterator it, Args::const_iterator end, int flags) const;
 
 public:
 	/**
-	 * Construct an option parser.
+	 * Construct an option parser from an initializer_list of options.
 	 *
-	 * @param style the style
+	 * @param options the list of options
 	 */
-	OptionParser(Style style = Getopt);
-
-	/**
-	 * Default destructor.
-	 */
-	virtual ~OptionParser() = default;
+	OptionParser(std::initializer_list<Option> options);
 
 	/**
-	 * Move an option.
+	 * Construct an option parser from a vector of options.
 	 *
-	 * @param option the option
+	 * @param options the options
 	 */
-	void add(Option &&option);
-
-	/**
-	 * Add an option.
-	 *
-	 * @param option the option
-	 */
-	void add(const Option &option);
+	OptionParser(std::vector<Option> options);
 
 	/**
-	 * Parse options.
+	 * Parse the arguments from main arguments.
 	 *
-	 * @param argc the number of args
+	 * @param argc the number of arguments
 	 * @param argv the arguments
 	 * @param flags the optional flags
-	 * @return the result
+	 * @return the packed result
 	 */
-	OptionResult parse(int argc, const char * const *argv, int flags = 0);
+	OptionPack parse(int argc, char **argv, int flags = 0) const;
 
 	/**
-	 * Parse options from a string list.
+	 * Parse the arguments from a vector.
 	 *
-	 * @param args the list of arguments
+	 * @param args the arguments
 	 * @param flags the optional flags
-	 * @return the result
+	 * @return the packed result
 	 */
-	OptionResult parse(const Args &args, int flags = 0);
-
-	/**
-	 * Show the usage. Called on error only if silent and if is strict is set, because
-	 * it will continue parsing and may show usage multiple times.
-	 */
-	virtual void usage();
-
-	/**
-	 * On error log.
-	 *
-	 * @param option the option
-	 * @param error the error
-	 */
-	virtual void log(const std::string &option, const std::string &error);
+	OptionPack parse(const std::vector<std::string> &args, int flags = 0) const;
 };
 
 #endif // !_OPTION_PARSER_H_
--- a/C++/Tests/OptionParser/main.cpp	Tue Mar 03 18:57:50 2015 +0100
+++ b/C++/Tests/OptionParser/main.cpp	Fri Mar 06 18:46:53 2015 +0100
@@ -20,232 +20,205 @@
 
 #include <OptionParser.h>
 
-#define LENGTH(x)	(sizeof (x) / sizeof (x[0]))
-
-/*
- * -a hello -a ok -v -c -p test
- */
-TEST(OptionParser, simple)
-{
-	OptionParser parser;
-	const char *argv[] = { "-a", "hello", "-a", "ok", "-v", "-c", "-p", "test" };
+/* --------------------------------------------------------
+ * Short options
+ * -------------------------------------------------------- */
 
-	parser.add(Option("a", "attribute", Option::Argument));
-	parser.add(Option("v", "verbose"));
-	parser.add(Option("c", "check"));
-	parser.add(Option("p", "plugin", Option::Argument));
+TEST(Short, simpleNoArg)
+{
+	OptionParser parser{
+		{ "a", "",	Option::NoArg	},
+		{ "b", "",	Option::NoArg	}
+	};
 
-	auto result = parser.parse(LENGTH(argv), argv, OptionParser::Strict);
-	auto begin = result.cbegin();
+	OptionPack pack = parser.parse({ "-a", "-b" });
 
-	ASSERT_EQ(5, result.count());
-	ASSERT_EQ(8, result.total());
+	ASSERT_TRUE(pack);
+
+	ASSERT_EQ(2, static_cast<int>(pack.size()));
+	ASSERT_EQ(2, pack.parsed());
 
-	// -a hello
-	ASSERT_EQ("a", begin->name());
-	ASSERT_EQ("hello", begin->value());
+	ASSERT_TRUE(pack[0] == "a");
+	ASSERT_TRUE(pack[1] == "b");
+}
 
-	// -a ok
-	std::advance(begin, 1);
-	ASSERT_EQ("a", begin->name());
-	ASSERT_EQ("ok", begin->value());
+TEST(Short, simpleArg)
+{
+	OptionParser parser{
+		{ "v", "",	Option::NoArg	},
+		{ "c", "",			}
+	};
 
-	// -v
-	std::advance(begin, 1);
-	ASSERT_EQ("v", begin->name());
-	ASSERT_EQ("", begin->value());
+	OptionPack pack = parser.parse({ "-v", "-cfoo.conf" });
+
+	ASSERT_TRUE(pack);
 
-	// -c
-	std::advance(begin, 1);
-	ASSERT_EQ("c", begin->name());
-	ASSERT_EQ("", begin->value());
+	ASSERT_EQ(2, static_cast<int>(pack.size()));
+	ASSERT_EQ(2, pack.parsed());
 
-	// -p test
-	std::advance(begin, 1);
-	ASSERT_EQ("p", begin->name());
-	ASSERT_EQ("test", begin->value());
+	ASSERT_TRUE(pack[0] == "v");
+	ASSERT_TRUE(pack[1] == "c");
+	ASSERT_EQ("foo.conf", pack[1].value());
 }
 
-/*
- * -v install -y -x -p package
- *
- * Split as two kind of groups,
- *	-v are general options
- *	-y, -x -p are dedicated to install
- */
-TEST(OptionParser, group)
+TEST(Short, spacedArg)
 {
-	OptionParser parser;
-	const char *argv[] = { "-v", "install", "-y", "-x", "-p", "irccd" };
+	OptionParser parser{
+		{ "v", "",	Option::NoArg	},
+		{ "c", "",			}
+	};
+
+	OptionPack pack = parser.parse({ "-v", "-c", "foo.conf" });
+
+	ASSERT_TRUE(pack);
 
-	parser.add(Option("v", "verbose"));
-	parser.add(Option("y", "auto answer yes"));
-	parser.add(Option("x", "use regex"));
-	parser.add(Option("p", "package", Option::Argument));
+	ASSERT_EQ(2, static_cast<int>(pack.size()));
+	ASSERT_EQ(3, pack.parsed());
+
+	ASSERT_TRUE(pack[0] == "v");
+	ASSERT_TRUE(pack[1] == "c");
+	ASSERT_EQ("foo.conf", pack[1].value());
+}
 
-	/*
-	 * First part: -v install
-	 */
-
-	auto result = parser.parse(LENGTH(argv), argv, OptionParser::BreakNonOption);
-	auto option = result.cbegin();
-
-	ASSERT_EQ(1, result.count());
-	ASSERT_EQ(1, result.total());
+TEST(Short, compacted)
+{
+	OptionParser parser{
+		{ "a", "",	Option::NoArg	},
+		{ "b", "",	Option::NoArg	},
+		{ "c", "",	Option::NoArg	},
+	};
 
-	// "-v"
-	ASSERT_EQ("v", option->name());
-	ASSERT_EQ("", option->value());
+	OptionPack pack = parser.parse({ "-abc" });
+
+	ASSERT_TRUE(pack);
 
-	/*
-	 * Second part: -y -x package
-	 */
+	ASSERT_EQ(3, static_cast<int>(pack.size()));
+	ASSERT_EQ(1, pack.parsed());
 
-	result = parser.parse(LENGTH(argv) - 2, argv + 2, OptionParser::Strict);
-	option = result.cbegin();
-
-	ASSERT_EQ(3, result.count());
-	ASSERT_EQ(4, result.total());
+	ASSERT_TRUE(pack[0] == "a");
+	ASSERT_TRUE(pack[1] == "b");
+	ASSERT_TRUE(pack[2] == "c");
+}
 
-	// -y	
-	ASSERT_EQ("y", option->name());
-	ASSERT_EQ("", option->value());
+TEST(Short, compactedArg)
+{
+	OptionParser parser{
+		{ "a", "",			},
+		{ "b", "",			},
+		{ "c", "",			},
+	};
 
-	// -x
-	std::advance(option, 1);
-	ASSERT_EQ("x", option->name());
-	ASSERT_EQ("", option->value());
+	OptionPack pack = parser.parse({ "-abc" });
+
+	ASSERT_TRUE(pack);
 
-	// -p irccd
-	std::advance(option, 1);
-	ASSERT_EQ("p", option->name());
-	ASSERT_EQ("irccd", option->value());
+	ASSERT_EQ(1, static_cast<int>(pack.size()));
+	ASSERT_EQ(1, pack.parsed());
+
+	ASSERT_TRUE(pack[0] == "a");
 }
 
-/*
- * Compact
- *
- * -abc -> -a -b -c
- */
-TEST(OptionParser, compact)
+/* --------------------------------------------------------
+ * Long options
+ * -------------------------------------------------------- */
+
+TEST(Long, simple)
 {
-	OptionParser parser;
-	const char *argv[] = { "-abc" };
+	OptionParser parser{
+		{ "",	"verbose",	Option::NoArg	},
+		{ "",	"fullscreen",	Option::NoArg	}
+	};
 
-	parser.add(Option("a", ""));
-	parser.add(Option("b", ""));
-	parser.add(Option("c", ""));
+	OptionPack pack = parser.parse({ "--fullscreen" });
 
-	auto result = parser.parse(LENGTH(argv), argv);
-	auto option = result.cbegin();
+	ASSERT_TRUE(pack);
+
+	ASSERT_EQ(1, static_cast<int>(pack.size()));
+	ASSERT_EQ(1, pack.parsed());
 
-	ASSERT_EQ(3, result.count());
-	ASSERT_EQ(3, result.total());
+	ASSERT_TRUE(pack[0] == "fullscreen");
+}
 
-	// -a
-	ASSERT_EQ("a", option->name());
-	ASSERT_EQ("", option->value());
+TEST(Long, simpleArg)
+{
+	OptionParser parser{
+		{ "", "config",			},
+		{ "", "level",			}
+	};
 
-	// -b
-	std::advance(option, 1);
-	ASSERT_EQ("b", option->name());
-	ASSERT_EQ("", option->value());
+	OptionPack pack = parser.parse({ "--config", "config.conf", "--level", "2" });
+
+	ASSERT_TRUE(pack);
 
-	// -c
-	std::advance(option, 1);
-	ASSERT_EQ("c", option->name());
-	ASSERT_EQ("", option->value());
+	ASSERT_EQ(2, static_cast<int>(pack.size()));
+	ASSERT_EQ(4, pack.parsed());
+
+	ASSERT_TRUE(pack[0] == "config");
+	ASSERT_EQ("config.conf", pack[0].value());
+	ASSERT_TRUE(pack[1] == "level");
+	ASSERT_EQ("2", pack[1].value());
 }
 
-/*
- * -o readonly -o loop -v -v -f true -f false
- */
-TEST(OptionParser, numbered)
-{
-	OptionParser parser;
-	const char *argv[] = { "-o", "readonly", "-o", "loop", "-v", "-v", "-f", "true", "-f", "false" };
-
-	parser.add(Option("o", "option", Option::Argument));
-	parser.add(Option("v", "verbose"));
-	parser.add(Option("f", "fullscreen", Option::Argument, Option::Single));
-
-	auto result = parser.parse(LENGTH(argv), argv, OptionParser::Silent);
-	auto option = result.cbegin();
-
-	ASSERT_EQ(6, result.count());
-	ASSERT_EQ(10, result.total());
-
-	// -o readonly
-	ASSERT_EQ("o", option->name());
-	ASSERT_EQ("readonly", option->value());
+/* --------------------------------------------------------
+ * Combined
+ * -------------------------------------------------------- */
 
-	// -o loop
-	std::advance(option, 1);
-	ASSERT_EQ("o", option->name());
-	ASSERT_EQ("loop", option->value());
-
-	// -v
-	std::advance(option, 1);
-	ASSERT_EQ("v", option->name());
-	ASSERT_EQ("", option->value());
+TEST(Combined, simple)
+{
+	OptionParser parser{
+		{ "v", "verbose",	Option::NoArg	},
+		{ "l", "level",		Option::NoArg	}
+	};
 
-	// -v
-	std::advance(option, 1);
-	ASSERT_EQ("v", option->name());
-	ASSERT_EQ("", option->value());
+	OptionPack pack = parser.parse({ "-v", "-l", "--verbose", "--level" });
+
+	ASSERT_TRUE(pack);
 
-	// -f true
-	std::advance(option, 1);
-	ASSERT_EQ("f", option->name());
-	ASSERT_EQ("true", option->value());
-
-	// -f false
-	std::advance(option, 1);
-	ASSERT_EQ("f", option->name());
-	ASSERT_EQ("false", option->value());
+	ASSERT_TRUE(pack[0] == "v" && pack[0] == "verbose");
+	ASSERT_TRUE(pack[1] == "l" && pack[1] == "level");
+	ASSERT_TRUE(pack[2] == "v" && pack[2] == "verbose");
+	ASSERT_TRUE(pack[3] == "l" && pack[3] == "level");
 }
 
-/*
- * -o readonly -o loop -v -v -f true -f false
- */
-TEST(OptionParser, numberedstrict)
-{
-	OptionParser parser;
-	const char *argv[] = { "-o", "readonly", "-o", "loop", "-v", "-v", "-f", "true", "-f", "false" };
+/* --------------------------------------------------------
+ * Flags
+ * -------------------------------------------------------- */
 
-	parser.add(Option("o", "option", Option::Argument));
-	parser.add(Option("v", "verbose"));
-	parser.add(Option("f", "fullscreen", Option::Argument, Option::Single));
+TEST(Flags, standard)
+{
+	// No flags, parse unless there is an argument which is not an option
+	OptionParser parser{
+		{ "v", "",		Option::NoArg	}
+	};
 
-	auto result = parser.parse(LENGTH(argv), argv, OptionParser::Silent | OptionParser::Strict);
-	auto option = result.cbegin();
+	OptionPack pack = parser.parse({ "-v", "install", "malikania" });
 
-	ASSERT_EQ(5, result.count());
-	ASSERT_EQ(8, result.total());
+	ASSERT_FALSE(pack);
+
+	ASSERT_EQ(1, static_cast<int>(pack.size()));
+	ASSERT_EQ(1, pack.parsed());
 
-	// -o readonly
-	ASSERT_EQ("o", option->name());
-	ASSERT_EQ("readonly", option->value());
+	ASSERT_TRUE(pack[0] == "v");
+}
 
-	// -o loop
-	std::advance(option, 1);
-	ASSERT_EQ("o", option->name());
-	ASSERT_EQ("loop", option->value());
+TEST(Flags, unstrict)
+{
+	// No flags, parse unless there is an argument which is not an option
+	OptionParser parser{
+		{ "v", "",		Option::NoArg	},
+		{ "d", "",				}
+	};
 
-	// -v
-	std::advance(option, 1);
-	ASSERT_EQ("v", option->name());
-	ASSERT_EQ("", option->value());
+	OptionPack pack = parser.parse({ "-v", "install", "malikania", "-d", "/usr/local" }, OptionParser::Unstrict);
+
+	ASSERT_TRUE(pack);
 
-	// -v
-	std::advance(option, 1);
-	ASSERT_EQ("v", option->name());
-	ASSERT_EQ("", option->value());
+	ASSERT_EQ(2, static_cast<int>(pack.size()));
+	ASSERT_EQ(5, pack.parsed());
 
-	// -f true
-	std::advance(option, 1);
-	ASSERT_EQ("f", option->name());
-	ASSERT_EQ("true", option->value());
+	ASSERT_TRUE(pack[0] == "v");
+	ASSERT_TRUE(pack[1] == "d");
+	ASSERT_EQ("/usr/local", pack[1].value());
 }
 
 int main(int argc, char **argv)