Mercurial > code
diff cpp/options/options.hpp @ 657:11fa64b69530
options: reintroduce a very basic getopt(3) alternative
author | David Demelier <markand@malikania.fr> |
---|---|
date | Mon, 15 Jul 2019 13:54:17 +0200 |
parents | |
children | 868663a44b5e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cpp/options/options.hpp Mon Jul 15 13:54:17 2019 +0200 @@ -0,0 +1,156 @@ +/* + * options.hpp -- getopt(3) similar interface for C++ + * + * Copyright (c) 2019 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef OPTIONS_HPP +#define OPTIONS_HPP + +/** + * \file options.hpp + * \brief C++ alternative to getopt(3). + */ + +#include <stdexcept> +#include <string> +#include <string_view> +#include <tuple> +#include <unordered_map> +#include <vector> + +/** + * \brief C++ alternative to getopt(3). + */ +namespace options { + +/** + * Store the positional arguments and options. + */ +using pack = std::tuple< + std::vector<std::string>, + std::unordered_multimap<char, std::string> +>; + +/** + * Parse a collection of options and arguments. + * + * This function uses the same format as getopt(3) function, you need specify + * each option in the fmt string and add a colon after the option character if + * it requires a value. + * + * If a -- option appears in the argument list, it stops option parsing and all + * next tokens are considered arguments even if they start with an hyphen. + * + * Example of format strings: + * + * - "abc": are all three boolean options, + * - "c:v": v is a boolean option c requires a value. + * + * Example of invocation: + * + * - `mycli -v -a`: is similar to `-va` if both 'v' and 'a' are boolean options, + * - `mycli -v -- -c`: -c will be a positional argument rather than an option + * but '-v' is still an option. + * + * \tparam InputIt must dereference a string type (literal, std::string_view or + * std::string) + * \param it the first item + * \param end the next item + * \param fmt the format string + * \return the result + */ +template <typename InputIt> +inline auto parse(InputIt it, InputIt end, std::string_view fmt) -> pack +{ + pack result; + + for (; it != end; ++it) { + const std::string_view token(*it); + + if (token == "--") { + for (++it; it != end; ++it) + std::get<0>(result).push_back(std::string(*it)); + break; + } + + if (token.size() > 0 && token[0] != '-') { + std::get<0>(result).push_back(std::string(token)); + continue; + } + + const auto sub = it->substr(1); + + for (std::size_t i = 0U; i < sub.size(); ++i) { + const auto idx = fmt.find(sub[i]); + + if (idx == std::string_view::npos) + throw std::runtime_error("invalid option"); + + if (idx + 1U == fmt.size() || fmt[idx + 1] != ':') { + std::get<1>(result).emplace(sub[i], ""); + continue; + } + + if (idx + 1U < sub.size()) { + std::get<1>(result).emplace(sub[i], std::string(sub.substr(i + 1))); + break; + } + + if (++it == end || std::string_view(*it).compare(0U, 1U, "-") == 0) + throw std::runtime_error("option require a value"); + + std::get<1>(result).emplace(sub[i], std::string(*it)); + } + } + + return result; +} + +/** + * Convenient overload with an initializer_list. + * + * \tparam StringType must be either a std::string or std::string_view + * \param args the arguments + * \param fmt the format string + * \return the result + */ +inline auto parse(std::initializer_list<std::string_view> args, std::string_view fmt) -> pack +{ + return parse(args.begin(), args.end(), fmt); +} + +/** + * Convenient overload for main() arguments. + * + * \param argc the number of arguments + * \param argv the arguments + * \param fmt the format string + * \return the result + */ +inline auto parse(int argc, char** argv, std::string_view fmt) -> pack +{ + std::vector<std::string_view> args(argc); + + for (int i = 0; i < argc; ++i) + args[i] = argv[i]; + + return parse(args.begin(), args.end(), fmt); +} + + +} // !options + +#endif // !OPTIONS_HPP