# HG changeset patch # User David Demelier # Date 1533124683 -7200 # Node ID 29e6ef5cd606d4ddcf2069652ef87984455ee959 # Parent 18aa7181e0c3c760cf76403fc7350d59a6945c19 options: use Boost.ProgramOptions instead diff -r 18aa7181e0c3 -r 29e6ef5cd606 cpp/CMakeLists.txt --- a/cpp/CMakeLists.txt Wed Aug 01 13:56:54 2018 +0200 +++ b/cpp/CMakeLists.txt Wed Aug 01 13:58:03 2018 +0200 @@ -20,5 +20,4 @@ add_subdirectory(is_number) add_subdirectory(join) add_subdirectory(json_util) -add_subdirectory(options) add_subdirectory(to_int) diff -r 18aa7181e0c3 -r 29e6ef5cd606 cpp/options/CMakeLists.txt --- a/cpp/options/CMakeLists.txt Wed Aug 01 13:56:54 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -# -# CMakeLists.txt -- options module -# -# Copyright (c) 2015-2018 David Demelier -# -# 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. -# - -project(options) - -code_define_module( - NAME options - SOURCES - ${options_SOURCE_DIR}/options.cpp - ${options_SOURCE_DIR}/options.hpp -) diff -r 18aa7181e0c3 -r 29e6ef5cd606 cpp/options/options.cpp --- a/cpp/options/options.cpp Wed Aug 01 13:56:54 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,187 +0,0 @@ -/* - * options.cpp -- parse Unix command line options - * - * Copyright (c) 2015-2018 David Demelier - * - * 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. - */ - -#include - -#include "options.hpp" - -namespace option { - -namespace { - -using iterator = std::vector::iterator; -using args = std::vector; - -inline bool is_option(const std::string& arg) noexcept -{ - return arg.size() >= 2 && arg[0] == '-'; -} - -inline bool is_long_option(const std::string& arg) noexcept -{ - assert(is_option(arg)); - - return arg.size() >= 3 && arg[1] == '-'; -} - -inline bool is_short_simple(const std::string& arg) noexcept -{ - assert(is_option(arg) && !is_long_option(arg)); - - return arg.size() == 2; -} - -void parse_long_option(result& result, args& args, iterator& it, iterator& end, const options& definition) -{ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - // Need argument? - if (opt->second) { - if (it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } -} - -void parse_short_option_simple(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - /* - * Here two cases: - * - * -v (no option) - * -c value - */ - auto arg = *it++; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - // Need argument? - if (opt->second) { - if (it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it++)); - it = args.erase(args.begin(), it); - end = args.end(); - } else { - result.insert(std::make_pair(arg, "")); - it = args.erase(args.begin()); - end = args.end(); - } -} - -void parse_short_option_compressed(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - /* - * Here multiple scenarios: - * - * 1. -abc (-a -b -c if all are simple boolean arguments) - * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) - * 3. -vcfoo.conf (-v -c foo.conf also) - */ - auto value = it->substr(1); - auto len = value.length(); - int toremove = 1; - - for (std::size_t i = 0; i < len; ++i) { - auto arg = std::string{'-'} + value[i]; - auto opt = definition.find(arg); - - if (opt == definition.end()) - throw invalid_option(arg); - - if (opt->second) { - if (i == (len - 1)) { - // End of string, get the next argument (see 2.). - if (++it == end || is_option(*it)) - throw missing_value(arg); - - result.insert(std::make_pair(arg, *it)); - toremove += 1; - } else { - result.insert(std::make_pair(arg, value.substr(i + 1))); - i = len; - } - } else - result.insert(std::make_pair(arg, "")); - } - - it = args.erase(args.begin(), args.begin() + toremove); - end = args.end(); -} - -void parse_short_option(result& result, args& args, iterator& it, iterator &end, const options& definition) -{ - if (is_short_simple(*it)) - parse_short_option_simple(result, args, it, end, definition); - else - parse_short_option_compressed(result, args, it, end, definition); -} - -} // !namespace - -result read(std::vector& args, const options& definition) -{ - result result; - - auto it = args.begin(); - auto end = args.end(); - - while (it != end) { - if (!is_option(*it)) - break; - - if (is_long_option(*it)) - parse_long_option(result, args, it, end, definition); - else - parse_short_option(result, args, it, end, definition); - } - - return result; -} - -result read(int& argc, char**& argv, const options& definition) -{ - std::vector args; - - for (int i = 0; i < argc; ++i) - args.push_back(argv[i]); - - auto before = args.size(); - auto result = read(args, definition); - - argc -= before - args.size(); - argv += before - args.size(); - - return result; -} - -} // !option diff -r 18aa7181e0c3 -r 29e6ef5cd606 cpp/options/options.hpp --- a/cpp/options/options.hpp Wed Aug 01 13:56:54 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,191 +0,0 @@ -/* - * options.hpp -- parse Unix command line options - * - * Copyright (c) 2015-2018 David Demelier - * - * 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 Basic Unix options parser. - */ - -/** - * \page options Options parser. - * - * ## Export macros - * - * You must define `OPTIONS_DLL` globally and `OPTIONS_BUILDING_DLL` when - * compiling the library if you want a DLL, alternatively you can provide your - * own `OPTIONS_EXPORT` macro instead. - */ - -/** - * \cond OPTIONS_HIDDEN_SYMBOLS - */ - -#if !defined(OPTIONS_EXPORT) -# if defined(OPTIONS_DLL) -# if defined(_WIN32) -# if defined(OPTIONS_BUILDING_DLL) -# define OPTIONS_EXPORT __declspec(dllexport) -# else -# define OPTIONS_EXPORT __declspec(dllimport) -# endif -# else -# define OPTIONS_EXPORT -# endif -# else -# define OPTIONS_EXPORT -# endif -#endif - -/** - * \endcond - */ - -#include -#include -#include -#include -#include - -/** - * Namespace for options parsing. - */ -namespace option { - -/** - * \brief This exception is thrown when an invalid option has been found. - */ -class invalid_option : public std::exception { -private: - std::string message_; - std::string name_; - -public: - /** - * Construct the exception. - * - * \param name the argument missing - */ - inline invalid_option(std::string name) - : name_(std::move(name)) - { - message_ = std::string("invalid option: ") + name_; - } - - /** - * Get the option name. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the error message. - * - * \return the error message - */ - const char* what() const noexcept override - { - return message_.c_str(); - } -}; - -/** - * \brief This exception is thrown when an option requires a value and no value - * has been given. - */ -class missing_value : public std::exception { -private: - std::string message_; - std::string name_; - -public: - /** - * Construct the exception. - * - * \param name the option that requires a value - */ - inline missing_value(std::string name) - : name_(std::move(name)) - { - message_ = std::string("missing argument for: ") + name_; - } - - /** - * Get the option name. - * - * \return the name - */ - inline const std::string& name() const noexcept - { - return name_; - } - - /** - * Get the error message. - * - * \return the error message - */ - const char* what() const noexcept override - { - return message_.c_str(); - } -}; - -/** - * Packed multimap of options. - */ -using result = std::multimap; - -/** - * Define the allowed options. - */ -using options = std::map; - -/** - * Extract the command line options and return a result. - * - * \param args the arguments - * \param definition - * \warning the arguments vector is modified in place to remove parsed options - * \throw missing_value - * \throw invalid_option - */ -OPTIONS_EXPORT result read(std::vector& args, const options& definition); - -/** - * Overloaded function for usage with main() arguments. - * - * \param argc the number of arguments - * \param argv the argument vector - * \param definition - * \note don't forget to remove the first argv[0] argument - * \warning the argc and argv are modified in place to remove parsed options - * \throw missing_value - * \throw invalid_option - */ -OPTIONS_EXPORT result read(int& argc, char**& argv, const options& definition); - -} // !option - -#endif // !OPTIONS_HPP diff -r 18aa7181e0c3 -r 29e6ef5cd606 cpp/options/test/main.cpp --- a/cpp/options/test/main.cpp Wed Aug 01 13:56:54 2018 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,233 +0,0 @@ -/* - * main.cpp -- main test file for options - * - * Copyright (c) 2015-2018 David Demelier - * - * 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. - */ - -#define BOOST_TEST_MODULE "Options" -#include - -#include - -/* - * Short options. - * ------------------------------------------------------------------ - */ - -BOOST_AUTO_TEST_SUITE(short_options) - -BOOST_AUTO_TEST_CASE(simple) -{ - std::vector args{"-a", "-b"}; - - auto pack = option::read(args, { - { "-a", false }, - { "-b", false } - }); - - BOOST_TEST(pack.size() == 2U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("-a") != 0); - BOOST_TEST(pack.count("-b") != 0); -} - -BOOST_AUTO_TEST_CASE(simple_arg) -{ - std::vector args{"-v", "-cfoo.conf"}; - - auto pack = option::read(args, { - { "-v", false }, - { "-c", true } - }); - - BOOST_TEST(pack.size() == 2U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("-v") != 0); - BOOST_TEST(pack.find("-c")->second == "foo.conf"); -} - -BOOST_AUTO_TEST_CASE(spaced_arg) -{ - std::vector args{"-v", "-c", "foo.conf"}; - - auto pack = option::read(args, { - { "-v", false }, - { "-c", true } - }); - - BOOST_TEST(pack.size() == 2U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("-v") != 0); - BOOST_TEST(pack.find("-c")->second == "foo.conf"); -} - -BOOST_AUTO_TEST_CASE(compacted) -{ - std::vector args{"-abc"}; - - auto pack = option::read(args, { - { "-a", false }, - { "-b", false }, - { "-c", false }, - }); - - BOOST_TEST(pack.size() == 3U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("-a") != 0); - BOOST_TEST(pack.count("-b") != 0); - BOOST_TEST(pack.count("-c") != 0); -} - -BOOST_AUTO_TEST_CASE(compacted_arg) -{ - std::vector args{"-vdcfoo.conf"}; - - auto pack = option::read(args, { - { "-v", false }, - { "-d", false }, - { "-c", true }, - }); - - BOOST_TEST(pack.size() == 3U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("-v") != 0); - BOOST_TEST(pack.count("-d") != 0); - BOOST_TEST(pack.find("-c")->second == "foo.conf"); -} - -BOOST_AUTO_TEST_SUITE_END() - -/* - * Long options. - * ------------------------------------------------------------------ - */ - -BOOST_AUTO_TEST_SUITE(long_options) - -BOOST_AUTO_TEST_CASE(simple) -{ - std::vector args{"--fullscreen"}; - - auto pack = option::read(args, { - { "--verbose", false }, - { "--fullscreen", false } - }); - - BOOST_TEST(pack.size() == 1U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.count("--fullscreen") != 0); -} - -BOOST_AUTO_TEST_CASE(simple_arg) -{ - std::vector args{"--config", "config.conf", "--level", "2"}; - - auto pack = option::read(args, { - { "--config", true }, - { "--level", true } - }); - - BOOST_TEST(pack.size() == 2U); - BOOST_TEST(args.size() == 0U); - BOOST_TEST(pack.find("--config")->second == "config.conf"); - BOOST_TEST(pack.find("--level")->second == "2"); -} - -BOOST_AUTO_TEST_SUITE_END() - -/* - * Errors. - * ------------------------------------------------------------------ - */ - -BOOST_AUTO_TEST_SUITE(errors) - -BOOST_AUTO_TEST_CASE(stop) -{ - std::vector args{"-v", "install", "-y", "irccd"}; - std::vector expected{"install", "-y", "irccd"}; - - auto pack = option::read(args, {{ "-v", false }}); - - BOOST_TEST(pack.size() == 1U); - BOOST_TEST(args.size() == 3U); - BOOST_TEST(pack.count("-v") != 0); - BOOST_TEST(expected == args); -} - -BOOST_AUTO_TEST_CASE(missing_short_arg) -{ - std::vector args{"-c"}; - - BOOST_REQUIRE_THROW(option::read(args, {{ "-c", true }}), option::missing_value); -} - -BOOST_AUTO_TEST_CASE(missing_short_arg2) -{ - const option::options options{ - { "-v", false }, - { "-c", true } - }; - - std::vector args{"-vc"}; - - BOOST_REQUIRE_THROW(option::read(args, options), option::missing_value); -} - -BOOST_AUTO_TEST_CASE(missing_long_arg) -{ - const option::options options{ - { "--config", true } - }; - - std::vector args{"--config"}; - - BOOST_REQUIRE_THROW(option::read(args, options), option::missing_value); -} - -BOOST_AUTO_TEST_CASE(invalid_option) -{ - const option::options options{ - { "-v", true } - }; - - std::vector args{"-x"}; - - BOOST_REQUIRE_THROW(option::read(args, options), option::invalid_option); -} - -BOOST_AUTO_TEST_CASE(invalid_option2) -{ - const option::options options{ - { "--verbose", true } - }; - - std::vector args{"--destroy"}; - - BOOST_REQUIRE_THROW(option::read(args, options), option::invalid_option); -} - -BOOST_AUTO_TEST_CASE(invalid_option3) -{ - const option::options options{ - { "-x", true } - }; - - std::vector args{"-vx"}; - - BOOST_REQUIRE_THROW(option::read(args, options), option::invalid_option); -} - -BOOST_AUTO_TEST_SUITE_END()