options/options.hpp

117 lines
3.8 KiB
C++
Raw Normal View History

2019-08-08 17:54:29 +00:00
#ifndef OPTIONS_HPP
#define OPTIONS_HPP
#include <string>
#include <map>
#include <vector>
namespace options
{
using Key = std::string;
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.
*
* This implementation strives to follow (in decreasing precedence) the
* guidelines laid out in Chapter 12 of the POSIX.1-2017 specification (see
* <http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html>);
* the conventions of Chapter 10 in The Art of Unix Programming by Eric S.
* Raymond; and the author's personal habits.
*
* @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
* 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.
*/
template<typename Iterator>
auto parse(Iterator start, Iterator end)
{
Dictionary opts;
// 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;
for (; start != end; ++start)
{
const auto current = *start;
if (current[0] == '-' && current[1] != '\0' && !operands_only)
{
if (current[1] == '-')
{
if (current[2] == '\0')
{
operands_only = true;
item = operands_item;
}
else
{
// GNU-style long option. The option's argument can either
// be inside the same argument, separated by an “=”
// character, or inside the following argument (therefore
// simply separated by whitespace)
const char* key_end = current + 2;
while (*key_end != '\0' && *key_end != '=') ++key_end;
std::tie(item, _) = opts.emplace(
std::string(current + 2, key_end - current - 2),
Values{}
);
if (*key_end == '=')
{
item->second.emplace_back(key_end + 1);
item = operands_item;
}
}
}
else
{
// Unix-style single-character option. Several short options
// can be grouped together inside the same argument; in this
// case, only the last option can have an option-argument
const char* letter = current + 1;
while (*letter != '\0')
{
std::tie(item, _) = opts.emplace(Key{*letter}, Values{});
++letter;
}
}
}
else
{
// Either an option-argument or an operand
item->second.emplace_back(current);
item = operands_item;
}
}
return opts;
}
}
#endif // OPTIONS_HPP