From 087353c4877f0653344a71c5908948ab1a4fc7e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Tue, 18 Aug 2020 22:07:51 +0200 Subject: [PATCH] Return operands as a separate list from options --- README.md | 49 ++--------------------------- main.cpp | 58 ++++++++++++++++------------------- options.hpp | 47 ++++++++++++---------------- tests.cpp | 88 ++++++++++++++++++++++++++++++++--------------------- 4 files changed, 102 insertions(+), 140 deletions(-) diff --git a/README.md b/README.md index d5d6dca..966cc40 100644 --- a/README.md +++ b/README.md @@ -38,56 +38,11 @@ This implementation strives to follow (in decreasing precedence) the guidelines * `start`: Iterator to the initial argument. * `end`: Past-the-end iterator to signal the argument list’s end. -**Return value:** Dictionary containing, for each option present on the command line, a key-value pair with the option’s name as the key and the list of option-arguments associated to that option, following the order of the command line, as the value. If an option is has no associated option-arguments (i.e. is a flag), the value in the dictionary is an empty list. Operands are grouped in a special item using `options::operands` as its key and also follow the order of the command line. +**Return value:** A pair with, first: a dictionary containing for each option present on the command line a key-value pair, with the option’s name as the key, and the list of option-arguments associated to that option, following the order of the command line, as the value. If an option has no associated option-arguments (i.e. is a flag), the value in the dictionary is an empty list; second: a list in which all operands are grouped following the order of the command line. ### Example -This simple program prints all options that are passed to it on the command line. - -```cpp -#include "options.hpp" -#include - -int main(int argc, char** argv) -{ - auto opts = options::parse(argv + 1, argv + argc); - - for (const auto& [name, args] : opts) - { - if (name == options::operands) - { - std::cerr << "Operands: "; - } - else - { - std::cerr << '"' << name << "\": "; - } - - if (args.empty()) - { - std::cerr << "(no arguments)"; - } - else - { - for ( - auto value_it = std::cbegin(args); - value_it != std::cend(args); - ++value_it - ) - { - std::cerr << '"' << *value_it << '"'; - - if (std::next(value_it) != std::cend(args)) - { - std::cerr << ", "; - } - } - } - - std::cerr << '\n'; - } -} -``` +[This program](main.cpp) prints all options and operands that are passed to it on the command line. ## Other libraries diff --git a/main.cpp b/main.cpp index ad6e448..ef4771a 100644 --- a/main.cpp +++ b/main.cpp @@ -1,42 +1,38 @@ #include "options.hpp" #include +template +void print_list(std::ostream& out, Iterator start, Iterator end) +{ + if (start == end) + { + out << ""; + return; + } + + for (; start != end; ++start) + { + out << '"' << *start << '"'; + + if (std::next(start) != end) + { + out << ", "; + } + } +} + int main(int argc, char** argv) { - auto opts = options::parse(argv + 1, argv + argc); + auto [opts, operands] = options::parse(argv + 1, argv + argc); + + std::cerr << "Operands: "; + print_list(std::cerr, std::cbegin(operands), std::cend(operands)); + std::cerr << '\n'; for (const auto& [name, args] : opts) { - if (name == options::operands) - { - std::cerr << "Operands: "; - } - else - { - std::cerr << '"' << name << "\": "; - } - - if (args.empty()) - { - std::cerr << "(no arguments)"; - } - else - { - for ( - auto value_it = std::cbegin(args); - value_it != std::cend(args); - ++value_it - ) - { - std::cerr << '"' << *value_it << '"'; - - if (std::next(value_it) != std::cend(args)) - { - std::cerr << ", "; - } - } - } - + std::cerr << "“" << name << "”: "; + print_list(std::cerr, std::cbegin(args), std::cend(args)); std::cerr << '\n'; } } diff --git a/options.hpp b/options.hpp index 4fa8a3a..52aec51 100644 --- a/options.hpp +++ b/options.hpp @@ -44,12 +44,6 @@ using Value = std::string; using Values = std::vector; using Dictionary = std::map; -/** - * @var Dictionary key under which operands (i.e. arguments that are neither - * option nor options-arguments) are stored. - */ -constexpr auto operands = ""; - /** * Parse a list of command line arguments into a set of program options. * @@ -61,28 +55,26 @@ constexpr auto operands = ""; * * @param start Iterator to the initial argument. * @param end Past-the-end iterator to signal the argument list’s end. - * @return Dictionary containing, for each option present on the command line, - * a key-value pair with the option’s name as the key and the list of - * option-arguments associated to that option, following the order of the - * command line, as the value. If an option is has no associated + * @return A pair with, first: a dictionary containing for each option present + * on the command line a key-value pair, with the option’s name as the key, and + * the list of option-arguments associated to that option, following the order + * of the command line, as the value. If an option has no associated * option-arguments (i.e. is a flag), the value in the dictionary is an empty - * list. Operands are grouped in a special item using `options::operands` as - * its key and also follow the order of the command line. + * list; second: a list in which all operands are grouped following the order + * of the command line. */ template auto parse(Iterator start, Iterator end) { Dictionary opts; + Values operands; // Signals whether a “--” argument was encountered, which implies that // all further arguments are to be treated as operands bool operands_only = false; - // Dictionary item into which the next value should be pushed - auto [item, _] = opts.emplace(operands, Values{}); - - // Dictionary item into which all operands are pushed - const auto operands_item = item; + // Array into which the next value should be pushed + Values* value_collector = &operands; for (; start != end; ++start) { @@ -95,7 +87,7 @@ auto parse(Iterator start, Iterator end) if (current[2] == '\0') { operands_only = true; - item = operands_item; + value_collector = &operands; } else { @@ -106,15 +98,15 @@ auto parse(Iterator start, Iterator end) const char* key_end = current + 2; while (*key_end != '\0' && *key_end != '=') ++key_end; - std::tie(item, _) = opts.emplace( + auto [item, _] = opts.emplace( std::string(current + 2, key_end - current - 2), - Values{} - ); + Values{}); + value_collector = &item->second; if (*key_end == '=') { - item->second.emplace_back(key_end + 1); - item = operands_item; + value_collector->emplace_back(key_end + 1); + value_collector = &operands; } } } @@ -127,7 +119,8 @@ auto parse(Iterator start, Iterator end) while (*letter != '\0') { - std::tie(item, _) = opts.emplace(Key{*letter}, Values{}); + auto [item, _] = opts.emplace(Key{*letter}, Values{}); + value_collector = &item->second; ++letter; } } @@ -136,12 +129,12 @@ auto parse(Iterator start, Iterator end) else { // Either an option-argument or an operand - item->second.emplace_back(current); - item = operands_item; + value_collector->emplace_back(current); + value_collector = &operands; } } - return opts; + return std::make_pair(opts, operands); } } diff --git a/tests.cpp b/tests.cpp index 2725740..1de0fe3 100644 --- a/tests.cpp +++ b/tests.cpp @@ -6,35 +6,42 @@ int main() /* Skeleton. { - std::array args{…}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{…}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{…})); assert((opts == options::Dictionary{ - {options::operands, {…}}, + {"option", {"value", …}}, … })); } */ + /* Empty. */ + { + std::array l{}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + + assert((oper == options::Values{})); + assert((opts == options::Dictionary{})); + } + /* Operands. */ { - std::array args{"all", "these", "are", "operands"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"all", "these", "are", "operands"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); - assert((opts == options::Dictionary{ - {options::operands, { - "all", "these", "are", "operands" - }} - })); + assert((oper == options::Values{"all", "these", "are", "operands"})); + assert((opts == options::Dictionary{})); } /* Short Unix options. */ { - std::array args{"-a", "-b", "-c"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"-a", "-b", "-c"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{})); assert((opts == options::Dictionary{ - {options::operands, {}}, {"a", {}}, {"b", {}}, {"c", {}} @@ -43,11 +50,11 @@ int main() /* Short Unix options with arguments. */ { - std::array args{"-v", "value", "not-a-value", "-w", "-v", "other"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"-v", "value", "not-a-value", "-w", "-v", "other"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{"not-a-value"})); assert((opts == options::Dictionary{ - {options::operands, {"not-a-value"}}, {"v", {"value", "other"}}, {"w", {}} })); @@ -55,11 +62,11 @@ int main() /* Short Unix options shorthand. */ { - std::array args{"-abcdef", "value", "not-a-value"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"-abcdef", "value", "not-a-value"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{"not-a-value"})); assert((opts == options::Dictionary{ - {options::operands, {"not-a-value"}}, {"a", {}}, {"b", {}}, {"c", {}}, @@ -71,35 +78,46 @@ int main() /* Long GNU options. */ { - std::array args{"--long", "--option"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"--long", "--option"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{})); assert((opts == options::Dictionary{ - {options::operands, {}}, {"long", {}}, {"option", {}} })); } - /* Long GNU options with arguments. */ + /* Long GNU options with arguments separated by whitespace. */ { - std::array args{"--value", "v", "--value", "-v", "value", "--value"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"--value", "v", "--value", "-v", "value", "--value"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{})); assert((opts == options::Dictionary{ - {options::operands, {}}, {"v", {"value"}}, {"value", {"v"}} })); } + /* Long GNU options with arguments separated by equals sign. */ + { + std::array l{"--value=-2", "--value=-1", "--value=0"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + + assert((oper == options::Values{})); + assert((opts == options::Dictionary{ + {"value", {"-2", "-1", "0"}} + })); + } + /* Single dash as option-argument. */ { - std::array args{"--output", "-", "--input", "-", "-"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"--output", "-", "--input", "-", "-"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{"-"})); assert((opts == options::Dictionary{ - {options::operands, {"-"}}, {"output", {"-"}}, {"input", {"-"}} })); @@ -107,11 +125,11 @@ int main() /* Long, short and shorthand options mixed. */ { - std::array args{"-abc", "content", "--long", "-short", "tree", "out"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"-abc", "content", "--long", "-short", "tree", "out"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{"out"})); assert((opts == options::Dictionary{ - {options::operands, {"out"}}, {"a", {}}, {"b", {}}, {"c", {"content"}}, @@ -126,11 +144,11 @@ int main() /* Operand separator. */ { - std::array args{"--option", "--", "--not-option"}; - auto opts = options::parse(std::cbegin(args), std::cend(args)); + std::array l{"--option", "--", "--not-option"}; + auto [opts, oper] = options::parse(std::cbegin(l), std::cend(l)); + assert((oper == options::Values{"--not-option"})); assert((opts == options::Dictionary{ - {options::operands, {"--not-option"}}, {"option", {}} })); }