Mercurial > code
changeset 331:9e223d1de96f
Merge optionparser to default
author | David Demelier <markand@malikania.fr> |
---|---|
date | Fri, 06 Mar 2015 22:04:38 +0100 |
parents | 14d9c7a4f358 (current diff) 99e83685d4da (diff) |
children | 4f282297625b |
files | |
diffstat | 3 files changed, 454 insertions(+), 658 deletions(-) [+] |
line wrap: on
line diff
--- a/C++/OptionParser.cpp Fri Mar 06 22:01:40 2015 +0100 +++ b/C++/OptionParser.cpp Fri Mar 06 22:04:38 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 Fri Mar 06 22:01:40 2015 +0100 +++ b/C++/OptionParser.h Fri Mar 06 22:04:38 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 Fri Mar 06 22:01:40 2015 +0100 +++ b/C++/Tests/OptionParser/main.cpp Fri Mar 06 22:04:38 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)