.. _program_listing_file_mcfp_options.hpp: Program Listing for File options.hpp ==================================== |exhale_lsh| :ref:`Return to documentation for file ` (``mcfp/options.hpp``) .. |exhale_lsh| unicode:: U+021B0 .. UPWARDS ARROW WITH TIP LEFTWARDS .. code-block:: cpp /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2022-2025 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 "mcfp/charconv.hpp" #include "mcfp/error.hpp" #include "mcfp/text.hpp" #include #include #include #include #include #include #include #include #include namespace mcfp { // -------------------------------------------------------------------- // Some template wizardry to detect containers, needed to have special // handling of options that can be repeated. template using iterator_t = typename T::iterator; template using value_type_t = typename T::value_type; template using std_string_npos_t = decltype(T::npos); template struct is_container_type : std::false_type { }; template requires(is_detected_v and is_detected_v and not is_detected_v) struct is_container_type : std::true_type { }; template inline constexpr bool is_container_type_v = is_container_type::value; static_assert(is_container_type_v>); static_assert(is_container_type_v>); // -------------------------------------------------------------------- // Some helper classes, to allow compile time checking of options strings // This error reporting function is not constexpr and thus when it is // called by the checking the format of options strings, it will cause // a compile time error. [[noreturn]] inline void report_error(const char *msg) { (void)fputs(msg, stderr); exit(1); } // -------------------------------------------------------------------- template class string_view_base { public: using char_type = CharT; using value_type = char_type; using iterator = const char_type *; constexpr explicit string_view_base(const char *s) noexcept : m_data(s) { while (m_data[m_size] != 0) ++m_size; } constexpr string_view_base(const char *s, size_t N) noexcept : m_data(s) , m_size(N) { } template constexpr explicit string_view_base(const char (&s)[N]) noexcept : m_data(s) , m_size(N - 1) { } constexpr string_view_base() noexcept = default; constexpr string_view_base(const string_view_base &) noexcept = default; constexpr string_view_base & operator=(const string_view_base &) noexcept = default; // constexpr string_view_base(nullptr_t) = delete; template // requires(std::is_same_v) constexpr explicit string_view_base(const StringType &s) noexcept : m_data(s.data()) , m_size(s.size()) { } [[nodiscard]] constexpr const char_type *data() const noexcept { return m_data; } [[nodiscard]] constexpr size_t size() const noexcept { return m_size; } [[nodiscard]] constexpr iterator begin() const noexcept { return m_data; } [[nodiscard]] constexpr iterator end() const noexcept { return m_data + m_size; } [[nodiscard]] constexpr char_type operator[](size_t ix) const noexcept { return m_data[ix]; } [[nodiscard]] constexpr char_type front() const noexcept { return m_data[0]; } [[nodiscard]] constexpr char_type back() const noexcept { return m_data[m_size - 1]; } [[nodiscard]] constexpr string_view_base substr(size_t pos, size_t len) const noexcept { return { m_data + pos, len }; } private: const char_type *m_data = nullptr; size_t m_size = 0; }; using string_view = string_view_base; // -------------------------------------------------------------------- // A parsed options string, that is, split out the short and long names struct ostring { string_view m_str; string_view m_long; string_view m_short; template consteval inline ostring(const char (&s)[N]) // NOLINT(hicpp-explicit-conversions) : m_str(s, N - 1) { parse(); } constexpr void parse(); }; constexpr inline bool is_alnum(int ch) noexcept { return (ch >= '0' and ch <= '9') or (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z'); } constexpr inline bool is_valid_option_char(char ch) noexcept { return ch == '-' or ch == '_' or is_alnum(ch); } constexpr void ostring::parse() { if (m_str.size() < 1) report_error("Empty string is not allowed for an option"); if (m_str.front() == '-') report_error("Option strings should not start with a hyphen"); auto len = m_str.size(); if (m_str.size() == 1) { if (not is_alnum(m_str.front())) report_error("Single character options should be alnum"); m_long = m_short = m_str; } else { if (m_str.size() > 2 and m_str[m_str.size() - 2] == ',') { if (not is_alnum(m_str.back())) report_error("Short variant of option should be alnum"); m_short = m_str.substr(m_str.size() - 1, 1); len -= 2; } for (size_t ix = 0; auto ch : m_str) { if (ix++ == len) break; if (not is_valid_option_char(ch)) report_error("Short variant of option should be alnum"); } m_long = m_str.substr(0, len); } } // -------------------------------------------------------------------- // The options classes // The option traits classes are used to convert from the string-based // command line argument to the type that should be stored. // In fact, here is where the command line arguments are checked for // proper formatting. template struct option_traits; template requires(std::is_arithmetic_v) struct option_traits { using value_type = T; static value_type set_value(std::string_view argument, std::error_code &ec) { value_type value{}; auto r = from_chars(argument.data(), argument.data() + argument.length(), value); if (r.ec != std::errc()) ec = std::make_error_code(r.ec); else if (*r.ptr != 0) ec = std::make_error_code(std::errc::invalid_argument); return value; } static std::string to_string(const T &value) { char b[32]; auto r = std::to_chars(b, b + sizeof(b), value); if (r.ec != std::errc()) throw std::system_error(std::make_error_code(r.ec)); return { b, r.ptr }; } }; template <> struct option_traits { using value_type = std::filesystem::path; static value_type set_value(std::string_view argument, std::error_code & /*ec*/) { return value_type{ argument }; } static std::string to_string(const std::filesystem::path &value) { return value.string(); } }; template requires(not std::is_arithmetic_v and std::is_assignable_v) struct option_traits { using value_type = std::string; static value_type set_value(std::string_view argument, std::error_code & /*ec*/) { return value_type{ argument }; } static std::string to_string(const T &value) { return { value }; } }; // The Options. The reason to have this weird constructing of // polymorphic options based on templates is to have a very // simple interface. The disadvantage is that the options have // to be copied during the construction of the config object. struct option_base { std::string m_name; std::string m_desc; char m_short_name; bool m_is_flag = true, m_multi = false, m_hidden; int m_seen = 0; // We store the actual data in the argument list, i.e. strings std::vector m_value; std::optional m_default_value; option_base(const option_base &rhs) = default; constexpr option_base(string_view name_long, string_view name_short, std::string desc, bool hidden) : m_name(name_long.begin(), name_long.end()) , m_desc(std::move(desc)) , m_short_name(name_short.size() > 0 ? name_short.front() : 0) , m_hidden(hidden) { } virtual ~option_base() = default; virtual void set_value(std::string_view /*value*/, std::error_code & /*ec*/) = 0; template T get_value(std::error_code &ec) const { T result{}; if (m_value.empty()) { if (m_default_value) { if constexpr (is_container_type_v) result.emplace_back(option_traits::set_value( *m_default_value, ec)); else result = option_traits::set_value(*m_default_value, ec); } else if constexpr (not is_container_type_v) // Return an empty list if not specified ec = make_error_code(config_error::option_not_specified); } else { if constexpr (is_container_type_v) { for (auto &a : m_value) { result.emplace_back( option_traits::set_value(a, ec)); if (ec) { result.clear(); break; } } } else result = option_traits::set_value(m_value.front(), ec); } return result; } [[nodiscard]] size_t width(std::string_view section_name) const; void write(std::ostream &os, std::string_view section_name, size_t indent, size_t output_width) const; }; template struct option : public option_base { using traits_type = option_traits; using value_type = typename option_traits::value_type; option(const option &rhs) = default; option(string_view name_long, string_view name_short, std::string desc, bool hidden) : option_base(name_long, name_short, std::move(desc), hidden) { m_is_flag = false; } option(string_view name_long, string_view name_short, const value_type &default_value, std::string desc, bool hidden) : option(name_long, name_short, std::move(desc), hidden) { if constexpr (std::is_same_v) m_default_value = default_value; else m_default_value = traits_type::to_string(default_value); } void set_value(std::string_view argument, std::error_code &ec) override { traits_type::set_value(argument, ec); if (not ec) { m_value.clear(); m_value.emplace_back(argument); } } }; template struct multiple_option : public option_base { using value_type = typename T::value_type; using traits_type = option_traits; multiple_option(const multiple_option &rhs) = default; multiple_option(string_view name_long, string_view name_short, std::string desc, bool hidden) : option_base(name_long, name_short, std::move(desc), hidden) { m_is_flag = false; m_multi = true; } void set_value(std::string_view argument, std::error_code &ec) override { traits_type::set_value(argument, ec); if (not ec) m_value.emplace_back(argument); } }; template <> struct option : public option_base { option(const option &rhs) = default; option(string_view name_long, string_view name_short, std::string desc, bool hidden) : option_base(name_long, name_short, std::move(desc), hidden) { } void set_value(std::string_view value, std::error_code &ec) override { if (value == "true") m_seen = 1; else if (value == "false") m_seen = 0; else if (auto [ptr, ec2] = mcfp::from_chars( value.data(), value.data() + value.length(), m_seen); ec2 != std::errc{} or ptr != value.data() + value.length()) ec = make_error_code(config_error::wrong_type_cast_flag); } }; } // namespace mcfp