.. _program_listing_file_mcfp_mcfp.hpp: Program Listing for File mcfp.hpp ================================= |exhale_lsh| :ref:`Return to documentation for file ` (``mcfp/mcfp.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022 Maarten L. Hekkelman * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mcfp { // -------------------------------------------------------------------- class config { using option_base = detail::option_base; public: void set_usage(std::string_view usage) { m_usage = usage; } template void init(std::string_view usage, Options... options) { m_usage = usage; m_ignore_unknown = false; m_impl.reset(new config_impl(std::forward(options)...)); } void set_ignore_unknown(bool ignore_unknown) { m_ignore_unknown = ignore_unknown; } static config &instance() { static std::unique_ptr 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 auto get(std::string_view name) const { using return_type = std::remove_cv_t; std::error_code ec; return_type result = get(name, ec); if (ec) throw std::system_error(ec, std::string{ name }); return result; } template auto get(std::string_view name, std::error_code &ec) const { using return_type = std::remove_cv_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(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(name); } std::string get(std::string_view name, std::error_code &ec) const { return get(name, ec); } const std::vector &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 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 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(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::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(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(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(ch)); else if (ch != ' ' and ch != '\t') { value = { static_cast(ch) }; state = State::VALUE; } break; } if (ec or ch == std::char_traits::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) // should not happen break; if (state == State::options) { if (*arg != '-') // according to POSIX this is the end of options, start operands // state = State::operands; { // however, people nowadays expect to be able to mix operands and options 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 == '-') // double --, start of new argument { ++arg; assert(*arg != 0); // this should not happen, as it was checked for before 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 // single character options { 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) // So, the = character was not present, the next arg must be the option argument { ++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 m_operands; }; template struct config_impl : public config_impl_base { static constexpr size_t N = sizeof...(Options); config_impl(Options... options) : m_options(std::forward(options)...) { } option_base *get_option(std::string_view name) override { return get_option<0>(name); } template option_base *get_option([[maybe_unused]] std::string_view name) { if constexpr (Ix == N) return nullptr; else { option_base &opt = std::get(m_options); return (opt.m_name == name) ? &opt : get_option(name); } } option_base *get_option(char short_name) override { return get_option<0>(short_name); } template option_base *get_option([[maybe_unused]] char short_name) { if constexpr (Ix == N) return nullptr; else { option_base &opt = std::get(m_options); return (opt.m_short_name == short_name) ? &opt : get_option(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 m_options; }; std::unique_ptr m_impl; bool m_ignore_unknown = false; std::string m_usage; }; // -------------------------------------------------------------------- template , int> = 0> auto make_option(std::string_view name, std::string_view description) { return detail::option(name, description, false); } template , int> = 0> auto make_option(std::string_view name, std::string_view description) { return detail::multiple_option(name, description, false); } template , int> = 0> auto make_option(std::string_view name, const T &v, std::string_view description) { return detail::option(name, v, description, false); } template , int> = 0> auto make_hidden_option(std::string_view name, std::string_view description) { return detail::option(name, description, true); } template , int> = 0> auto make_hidden_option(std::string_view name, std::string_view description) { return detail::multiple_option(name, description, true); } template , int> = 0> auto make_hidden_option(std::string_view name, const T &v, std::string_view description) { return detail::option(name, v, description, true); } } // namespace mcfp namespace std { template <> struct is_error_condition_enum : public true_type { }; } // namespace std