Return operands as a separate list from options

This commit is contained in:
Mattéo Delabre 2020-08-18 22:07:51 +02:00
parent 5619ec178c
commit 087353c487
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
4 changed files with 102 additions and 140 deletions

View File

@ -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 lists end.
**Return value:** Dictionary containing, for each option present on the command line, a key-value pair with the options 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 options 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 <iostream>
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

View File

@ -1,42 +1,38 @@
#include "options.hpp"
#include <iostream>
template<typename Iterator>
void print_list(std::ostream& out, Iterator start, Iterator end)
{
if (start == end)
{
out << "<empty>";
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';
}
}

View File

@ -44,12 +44,6 @@ using Value = std::string;
using Values = std::vector<Value>;
using Dictionary = std::map<Key, Values>;
/**
* @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 lists end.
* @return Dictionary containing, for each option present on the command line,
* a key-value pair with the options 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 options 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<typename Iterator>
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);
}
}

View File

@ -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<const char*, 0> 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", {}}
}));
}