| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #pragma once |
|
|
| |
| |
| |
|
|
| #include <cassert> |
| #include <cstring> |
|
|
| #include <any> |
| #include <charconv> |
| #include <deque> |
| #include <filesystem> |
| #include <fstream> |
| #include <memory> |
| #include <optional> |
| #include <type_traits> |
| #include <vector> |
|
|
| #include <mcfp/error.hpp> |
| #include <mcfp/text.hpp> |
| #include <mcfp/utilities.hpp> |
| #include <mcfp/detail/options.hpp> |
|
|
| namespace mcfp |
| { |
|
|
| |
| |
| |
| |
| |
| |
|
|
| class config |
| { |
| using option_base = detail::option_base; |
|
|
| public: |
|
|
| |
| |
| |
| |
| |
| void set_usage(std::string_view usage) |
| { |
| m_usage = usage; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| template <typename... Options> |
| void init(std::string_view usage, Options... options) |
| { |
| m_usage = usage; |
| m_ignore_unknown = false; |
| m_impl.reset(new config_impl(std::forward<Options>(options)...)); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| void set_ignore_unknown(bool ignore_unknown) |
| { |
| m_ignore_unknown = ignore_unknown; |
| } |
|
|
| |
| |
| |
| |
| |
| static config &instance() |
| { |
| static std::unique_ptr<config> s_instance; |
| if (not s_instance) |
| s_instance.reset(new config); |
| return *s_instance; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| bool has(std::string_view name) const |
| { |
| auto opt = m_impl->get_option(name); |
| return opt != nullptr and (opt->m_seen > 0 or opt->m_has_default); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| int count(std::string_view name) const |
| { |
| auto opt = m_impl->get_option(name); |
| return opt ? opt->m_seen : 0; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T> |
| auto get(std::string_view name) const |
| { |
| using return_type = std::remove_cv_t<T>; |
|
|
| std::error_code ec; |
| return_type result = get<T>(name, ec); |
|
|
| if (ec) |
| throw std::system_error(ec, std::string{ name }); |
|
|
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T> |
| auto get(std::string_view name, std::error_code &ec) const |
| { |
| using return_type = std::remove_cv_t<T>; |
|
|
| return_type result{}; |
| auto opt = m_impl->get_option(name); |
|
|
| if (opt == nullptr) |
| ec = make_error_code(config_error::unknown_option); |
| else |
| { |
| std::any value = opt->get_value(); |
|
|
| if (not value.has_value()) |
| ec = make_error_code(config_error::option_not_specified); |
| else |
| { |
| try |
| { |
| result = std::any_cast<T>(value); |
| } |
| catch (const std::bad_cast &) |
| { |
| ec = make_error_code(config_error::wrong_type_cast); |
| } |
| } |
| } |
|
|
| return result; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| std::string get(std::string_view name) const |
| { |
| return get<std::string>(name); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| std::string get(std::string_view name, std::error_code &ec) const |
| { |
| return get<std::string>(name, ec); |
| } |
|
|
| |
| |
| |
| |
| |
| const std::vector<std::string> &operands() const |
| { |
| return m_impl->m_operands; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| friend std::ostream &operator<<(std::ostream &os, const config &conf) |
| { |
| size_t terminal_width = get_terminal_width(); |
|
|
| if (not conf.m_usage.empty()) |
| os << conf.m_usage << std::endl; |
|
|
| size_t options_width = conf.m_impl->get_option_width(); |
|
|
| if (options_width > terminal_width / 2) |
| options_width = terminal_width / 2; |
|
|
| conf.m_impl->write(os, options_width); |
|
|
| return os; |
| } |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| void parse(int argc, const char *const argv[]) |
| { |
| std::error_code ec; |
| parse(argc, argv, ec); |
| if (ec) |
| throw std::system_error(ec); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void parse_config_file(std::string_view config_option, std::string_view config_file_name, |
| std::initializer_list<std::string_view> search_dirs) |
| { |
| std::error_code ec; |
| parse_config_file(config_option, config_file_name, search_dirs, ec); |
| if (ec) |
| throw std::system_error(ec); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| void parse_config_file(std::string_view config_option, std::string_view config_file_name, |
| std::initializer_list<std::string_view> search_dirs, std::error_code &ec) |
| { |
| std::string file_name{ config_file_name }; |
| bool parsed_config_file = false; |
|
|
| if (has(config_option)) |
| file_name = get<std::string>(config_option); |
|
|
| for (std::filesystem::path dir : search_dirs) |
| { |
| std::ifstream file(dir / file_name); |
|
|
| if (not file.is_open()) |
| continue; |
|
|
| parse_config_file(file, ec); |
| parsed_config_file = true; |
| break; |
| } |
|
|
| if (not parsed_config_file and has(config_option)) |
| ec = make_error_code(config_error::config_file_not_found); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| void parse_config_file(const std::filesystem::path &file, std::error_code &ec) |
| { |
| std::ifstream is(file); |
| if (is.is_open()) |
| parse_config_file(is, ec); |
| } |
|
|
| private: |
|
|
| static bool is_name_char(int ch) |
| { |
| return std::isalnum(ch) or ch == '_' or ch == '-'; |
| } |
|
|
| static constexpr bool is_eoln(int ch) |
| { |
| return ch == '\n' or ch == '\r' or ch == std::char_traits<char>::eof(); |
| } |
|
|
| public: |
|
|
| |
| |
| |
| |
| |
| |
| |
| void parse_config_file(std::istream &is, std::error_code &ec) |
| { |
| auto &buffer = *is.rdbuf(); |
|
|
| enum class State |
| { |
| NAME_START, |
| COMMENT, |
| NAME, |
| ASSIGN, |
| VALUE_START, |
| VALUE |
| } state = State::NAME_START; |
|
|
| std::string name, value; |
|
|
| for (;;) |
| { |
| auto ch = buffer.sbumpc(); |
|
|
| switch (state) |
| { |
| case State::NAME_START: |
| if (is_name_char(ch)) |
| { |
| name = { static_cast<char>(ch) }; |
| value.clear(); |
| state = State::NAME; |
| } |
| else if (ch == '#' or ch == ';') |
| state = State::COMMENT; |
| else if (ch != ' ' and ch != '\t' and not is_eoln(ch)) |
| ec = make_error_code(config_error::invalid_config_file); |
| break; |
|
|
| case State::COMMENT: |
| if (is_eoln(ch)) |
| state = State::NAME_START; |
| break; |
|
|
| case State::NAME: |
| if (is_name_char(ch)) |
| name.insert(name.end(), static_cast<char>(ch)); |
| else if (is_eoln(ch)) |
| { |
| auto opt = m_impl->get_option(name); |
|
|
| if (opt == nullptr) |
| { |
| if (not m_ignore_unknown) |
| ec = make_error_code(config_error::unknown_option); |
| } |
| else if (not opt->m_is_flag) |
| ec = make_error_code(config_error::missing_argument_for_option); |
| else |
| ++opt->m_seen; |
|
|
| state = State::NAME_START; |
| } |
| else |
| { |
| buffer.sungetc(); |
| state = State::ASSIGN; |
| } |
| break; |
|
|
| case State::ASSIGN: |
| if (ch == '=') |
| state = State::VALUE_START; |
| else if (is_eoln(ch)) |
| { |
| auto opt = m_impl->get_option(name); |
|
|
| if (opt == nullptr) |
| { |
| if (not m_ignore_unknown) |
| ec = make_error_code(config_error::unknown_option); |
| } |
| else if (not opt->m_is_flag) |
| ec = make_error_code(config_error::missing_argument_for_option); |
| else |
| ++opt->m_seen; |
|
|
| state = State::NAME_START; |
| } |
| else if (ch != ' ' and ch != '\t') |
| ec = make_error_code(config_error::invalid_config_file); |
| break; |
|
|
| case State::VALUE_START: |
| case State::VALUE: |
| if (is_eoln(ch)) |
| { |
| auto opt = m_impl->get_option(name); |
|
|
| if (opt == nullptr) |
| { |
| if (not m_ignore_unknown) |
| ec = make_error_code(config_error::unknown_option); |
| } |
| else if (opt->m_is_flag) |
| ec = make_error_code(config_error::option_does_not_accept_argument); |
| else if (not value.empty() and (opt->m_seen == 0 or opt->m_multi)) |
| { |
| opt->set_value(value, ec); |
| ++opt->m_seen; |
| } |
|
|
| state = State::NAME_START; |
| } |
| else if (state == State::VALUE) |
| value.insert(value.end(), static_cast<char>(ch)); |
| else if (ch != ' ' and ch != '\t') |
| { |
| value = { static_cast<char>(ch) }; |
| state = State::VALUE; |
| } |
| break; |
| } |
|
|
| if (ec or ch == std::char_traits<char>::eof()) |
| break; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| void parse(int argc, const char *const argv[], std::error_code &ec) |
| { |
| using namespace std::literals; |
|
|
| enum class State |
| { |
| options, |
| operands |
| } state = State::options; |
|
|
| for (int i = 1; i < argc and not ec; ++i) |
| { |
| const char *arg = argv[i]; |
|
|
| if (arg == nullptr) |
| break; |
|
|
| if (state == State::options) |
| { |
| if (*arg != '-') |
| |
| { |
| m_impl->m_operands.emplace_back(arg); |
| continue; |
| } |
| else if (arg[1] == '-' and arg[2] == 0) |
| { |
| state = State::operands; |
| continue; |
| } |
| } |
|
|
| if (state == State::operands) |
| { |
| m_impl->m_operands.emplace_back(arg); |
| continue; |
| } |
|
|
| option_base *opt = nullptr; |
| std::string_view opt_arg; |
|
|
| assert(*arg == '-'); |
| ++arg; |
|
|
| if (*arg == '-') |
| { |
| ++arg; |
|
|
| assert(*arg != 0); |
|
|
| std::string_view s_arg(arg); |
| std::string_view::size_type p = s_arg.find('='); |
|
|
| if (p != std::string_view::npos) |
| { |
| opt_arg = s_arg.substr(p + 1); |
| s_arg = s_arg.substr(0, p); |
| } |
|
|
| opt = m_impl->get_option(s_arg); |
| if (opt == nullptr) |
| { |
| if (not m_ignore_unknown) |
| ec = make_error_code(config_error::unknown_option); |
| continue; |
| } |
|
|
| if (opt->m_is_flag) |
| { |
| if (not opt_arg.empty()) |
| ec = make_error_code(config_error::option_does_not_accept_argument); |
|
|
| ++opt->m_seen; |
| continue; |
| } |
|
|
| ++opt->m_seen; |
| } |
| else |
| { |
| bool expect_option_argument = false; |
|
|
| while (*arg != 0 and not ec) |
| { |
| opt = m_impl->get_option(*arg++); |
|
|
| if (opt == nullptr) |
| { |
| if (not m_ignore_unknown) |
| ec = make_error_code(config_error::unknown_option); |
| continue; |
| } |
|
|
| ++opt->m_seen; |
| if (opt->m_is_flag) |
| continue; |
|
|
| opt_arg = arg; |
| expect_option_argument = true; |
| break; |
| } |
|
|
| if (not expect_option_argument) |
| continue; |
| } |
|
|
| if (opt_arg.empty() and i + 1 < argc) |
| { |
| ++i; |
| opt_arg = argv[i]; |
| } |
|
|
| if (opt_arg.empty()) |
| ec = make_error_code(config_error::missing_argument_for_option); |
| else |
| opt->set_value(opt_arg, ec); |
| } |
| } |
|
|
| private: |
| config() = default; |
| config(const config &) = delete; |
| config &operator=(const config &) = delete; |
|
|
| |
|
|
| struct config_impl_base |
| { |
| virtual ~config_impl_base() = default; |
|
|
| virtual option_base *get_option(std::string_view name) = 0; |
| virtual option_base *get_option(char short_name) = 0; |
|
|
| virtual size_t get_option_width() const = 0; |
| virtual void write(std::ostream &os, size_t width) const = 0; |
|
|
| std::vector<std::string> m_operands; |
| }; |
|
|
| template <typename... Options> |
| struct config_impl : public config_impl_base |
| { |
| static constexpr size_t N = sizeof...(Options); |
|
|
| config_impl(Options... options) |
| : m_options(std::forward<Options>(options)...) |
| { |
| } |
|
|
| option_base *get_option(std::string_view name) override |
| { |
| return get_option<0>(name); |
| } |
|
|
| template <size_t Ix> |
| option_base *get_option([[maybe_unused]] std::string_view name) |
| { |
| if constexpr (Ix == N) |
| return nullptr; |
| else |
| { |
| option_base &opt = std::get<Ix>(m_options); |
| return (opt.m_name == name) ? &opt : get_option<Ix + 1>(name); |
| } |
| } |
|
|
| option_base *get_option(char short_name) override |
| { |
| return get_option<0>(short_name); |
| } |
|
|
| template <size_t Ix> |
| option_base *get_option([[maybe_unused]] char short_name) |
| { |
| if constexpr (Ix == N) |
| return nullptr; |
| else |
| { |
| option_base &opt = std::get<Ix>(m_options); |
| return (opt.m_short_name == short_name) ? &opt : get_option<Ix + 1>(short_name); |
| } |
| } |
|
|
| virtual size_t get_option_width() const override |
| { |
| return std::apply([](Options const& ...opts) { |
| size_t width = 0; |
| ((width = std::max(width, opts.width())), ...); |
| return width; |
| }, m_options); |
| } |
|
|
| virtual void write(std::ostream &os, size_t width) const override |
| { |
| std::apply([&os,width](Options const& ...opts) { |
| (opts.write(os, width), ...); |
| }, m_options); |
| } |
|
|
| std::tuple<Options...> m_options; |
| }; |
|
|
| std::unique_ptr<config_impl_base> m_impl; |
| bool m_ignore_unknown = false; |
| std::string m_usage; |
|
|
| |
| }; |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T = void, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0> |
| auto make_option(std::string_view name, std::string_view description) |
| { |
| return detail::option<T>(name, description, false); |
| } |
|
|
| template <typename T, std::enable_if_t<detail::is_container_type_v<T>, int> = 0> |
| auto make_option(std::string_view name, std::string_view description) |
| { |
| return detail::multiple_option<T>(name, description, false); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0> |
| auto make_option(std::string_view name, const T &v, std::string_view description) |
| { |
| return detail::option<T>(name, v, description, false); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T = void, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0> |
| auto make_hidden_option(std::string_view name, std::string_view description) |
| { |
| return detail::option<T>(name, description, true); |
| } |
|
|
| template <typename T, std::enable_if_t<detail::is_container_type_v<T>, int> = 0> |
| auto make_hidden_option(std::string_view name, std::string_view description) |
| { |
| return detail::multiple_option<T>(name, description, true); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| template <typename T, std::enable_if_t<not detail::is_container_type_v<T>, int> = 0> |
| auto make_hidden_option(std::string_view name, const T &v, std::string_view description) |
| { |
| return detail::option<T>(name, v, description, true); |
| } |
|
|
| } |
|
|
| namespace std |
| { |
|
|
| template <> |
| struct is_error_condition_enum<mcfp::config_error> |
| : public true_type |
| { |
| }; |
|
|
| } |
|
|