/** * @file options.hpp * @brief Minimalist header-only C++17 command line parsing function. * @author Mattéo Delabre * @see https://forge.delab.re/matteo/options * @license * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * For more information, please refer to */ #ifndef OPTIONS_HPP #define OPTIONS_HPP #include #include #include namespace options { using Key = std::string; 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. * * This implementation strives to follow (in decreasing precedence) the * guidelines laid out in Chapter 12 of the POSIX.1-2017 specification (see * ); * 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 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 * 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 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