libmcfp-1.2.4/0000775000175000017500000000000014364034504012777 5ustar maartenmaartenlibmcfp-1.2.4/.gitignore0000664000175000017500000000005014364034504014762 0ustar maartenmaartenbuild .vscode cmake/libmcfpConfig.cmake libmcfp-1.2.4/CMakeLists.txt0000664000175000017500000001125714364034504015545 0ustar maartenmaarten# 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. cmake_minimum_required(VERSION 3.16) # set the project name project(libmcfp VERSION 1.2.4 LANGUAGES CXX) include(GNUInstallDirs) include(CMakePackageConfigHelpers) include(Dart) set(CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 17 CACHE STRING "The minimum version of C++ required for this library") set(CMAKE_CXX_STANDARD_REQUIRED ON) option(ENABLE_TESTING "Build the unit test applications" OFF) if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers") elseif(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") endif() if(MSVC) # make msvc standards compliant... add_compile_options(/permissive-) if(${CMAKE_SYSTEM_VERSION} GREATER_EQUAL 10) # Windows 10 add_definitions(-D _WIN32_WINNT=0x0A00) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.3) # Windows 8.1 add_definitions(-D _WIN32_WINNT=0x0603) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.2) # Windows 8 add_definitions(-D _WIN32_WINNT=0x0602) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.1) # Windows 7 add_definitions(-D _WIN32_WINNT=0x0601) elseif(${CMAKE_SYSTEM_VERSION} EQUAL 6.0) # Windows Vista add_definitions(-D _WIN32_WINNT=0x0600) else() # Windows XP (5.1) add_definitions(-D _WIN32_WINNT=0x0501) endif() add_definitions(-DNOMINMAX) endif() add_library(libmcfp INTERFACE) add_library(libmcfp::libmcfp ALIAS libmcfp) target_include_directories(libmcfp INTERFACE $ $ ) # adding header sources just helps IDEs target_sources(libmcfp INTERFACE $$/mcfp/mcfp.hpp ) set_target_properties(libmcfp PROPERTIES PUBLIC_HEADER include/mcfp/mcfp.hpp) # installation set(version_config "${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfigVersion.cmake") set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}) write_basic_package_version_file("${version_config}" VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) install(TARGETS libmcfp EXPORT libmcfpConfig PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) install( DIRECTORY include/mcfp DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} COMPONENT Devel ) export(TARGETS libmcfp NAMESPACE libmcfp:: FILE libmcfpTargets.cmake) if(WIN32 AND NOT CYGWIN) set(CONFIG_LOC CMake) else() set(CONFIG_LOC "${CMAKE_INSTALL_LIBDIR}/cmake/libmcfp") endif() configure_package_config_file( ${PROJECT_SOURCE_DIR}/cmake/libmcfpConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libmcfp PATH_VARS INCLUDE_INSTALL_DIR ) install(EXPORT libmcfpConfig FILE libmcfpTargets.cmake NAMESPACE libmcfp:: DESTINATION ${CONFIG_LOC}) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/libmcfpConfig.cmake "${version_config}" DESTINATION ${CONFIG_LOC}) if(ENABLE_TESTING) enable_testing() find_package(Boost REQUIRED) add_executable(libmcfp-unit-test ${PROJECT_SOURCE_DIR}/test/unit-test.cpp) target_link_libraries(libmcfp-unit-test libmcfp::libmcfp Boost::boost) if(MSVC) # Specify unwind semantics so that MSVC knowns how to handle exceptions target_compile_options(libmcfp-unit-test PRIVATE /EHsc) endif() add_test(NAME libmcfp-unit-test COMMAND $ -- ${PROJECT_SOURCE_DIR}/test) endif() libmcfp-1.2.4/LICENSE0000664000175000017500000000245614364034504014013 0ustar maartenmaartenBSD-2-Clause License Copyright (c) 2022 Maarten L. Hekkelman All rights reserved. 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. libmcfp-1.2.4/README.md0000664000175000017500000000742314364034504014264 0ustar maartenmaarten# libmcfp A library for parsing command line arguments and configuration files and making them available throughout a program. There's a config file parser as well. ## Synopsis ```c++ // Example of using libmcfp #include #include #include int main(int argc, char * const argv[]) { // config is a singleton auto &config = mcfp::config::instance(); // Initialise the config object. This can be done more than once, // e.g. when you have different sets of options depending on the // first operand. config.init( // The first parameter is the 'usage' line, used when printing out the options "usage: example [options] file", // Flag options (not taking a parameter) mcfp::make_option("help,h", "Print this help text"), mcfp::make_option("verbose,v", "Verbose level, can be specified more than once to increase level"), // A couple of options with parameter mcfp::make_option("config", "Config file to use"), mcfp::make_option("text", "The text string to echo"), // And options with a default parameter mcfp::make_option("a", 1, "first parameter for multiplication"), mcfp::make_option("b", 2.0f, "second parameter for multiplication"), // You can also allow multiple values mcfp::make_option>("c", "Option c, can be specified more than once"), // This option is not shown when printing out the options mcfp::make_hidden_option("d", "Debug mode") ); // There are two flavors of calls, ones that take an error_code // and return the error in that code in case something is wrong. // The alternative is calling without an error_code, in which // case an exception is thrown when appropriate // Parse the command line arguments here std::error_code ec; config.parse(argc, argv, ec); if (ec) { std::cerr << "Error parsing arguments: " << ec.message() << std::endl; exit(1); } // First check, to see if we need to stop early on if (config.has("help") or config.operands().size() != 1) { // This will print out the 'usage' message with all the visible options std::cerr << config << std::endl; exit(config.has("help") ? 0 : 1); } // Configuration files, read it if it exists. If the users // specifies an alternative config file, it is an error if that // file cannot be found. config.parse_config_file("config", "example.conf", { "." }, ec); if (ec) { std::cerr << "Error parsing config file: " << ec.message() << std::endl; exit(1); } // If options are specified more than once, you can get the count int VERBOSE = config.count("verbose"); // Operands are arguments that are not options, e.g. files to act upon std::cout << "The first operand is " << config.operands().front() << std::endl; // Getting the value of a string option auto text = config.get("text", ec); if (ec) { std::cerr << "Error getting option text: " << ec.message() << std::endl; exit(1); } std::cout << "Text option is " << text << std::endl; // Likewise for numeric options int a = config.get("a"); float b = config.get("b"); std::cout << "a (" << a << ") * b (" << b << ") = " << a * b << std::endl; // And multiple strings for (std::string s : config.get>("c")) std::cout << "c: " << s << std::endl; return 0; } ``` ## Installation Use [cmake](https://cmake.org/) to install _libmcfp_. ```bash git clone https://github.com/mhekkel/libmcfp.git cd libmcfp mkdir build cd build cmake .. cmake --build . cmake --install . ``` libmcfp-1.2.4/changelog0000664000175000017500000000113114364034504014645 0ustar maartenmaartenVersion 1.2.4 - Simpler get (added a version without template arguments) Version 1.2.3 - MSVC compatibility Version 1.2.2 - Added a non-throwing get - Added extended example code Version 1.2.1 - Renamed to libmcfp and namespace mcfp Version 1.2.0 - Fix in word wrapping code Version 1.1.1 - Better support for older GCC compilers Version 1.2.0 - New, tuples base implementation - Add 'usage' parameter to init Version 1.1.0 - Parse config files Version 1.0.2 - Fix get_terminal_width. Should be inline of course. Version 1.0.1 - printing options, word-wrapped Version 1.0.0 - initial releaselibmcfp-1.2.4/cmake/0000775000175000017500000000000014364034504014057 5ustar maartenmaartenlibmcfp-1.2.4/cmake/libmcfpConfig.cmake.in0000664000175000017500000000015614364034504020232 0ustar maartenmaarten@PACKAGE_INIT@ INCLUDE("${CMAKE_CURRENT_LIST_DIR}/libmcfpTargets.cmake") check_required_components(libmcfp) libmcfp-1.2.4/examples/0000775000175000017500000000000014364034504014615 5ustar maartenmaartenlibmcfp-1.2.4/examples/CMakeLists.txt0000664000175000017500000000053114364034504017354 0ustar maartenmaartencmake_minimum_required(VERSION 3.16) project(mcfp-usage LANGUAGES CXX) set(CXX_EXTENSIONS OFF) set(CMAKE_CXX_STANDARD 17 CACHE STRING "The minimum version of C++ required for this library") set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(libmcfp REQUIRED) add_executable(example example.cpp) target_link_libraries(example libmcfp::libmcfp) libmcfp-1.2.4/examples/example.conf0000664000175000017500000000001114364034504017107 0ustar maartenmaartentext=foo libmcfp-1.2.4/examples/example.cpp0000664000175000017500000000611114364034504016753 0ustar maartenmaarten// Example of using libmcfp #include #include #include int main(int argc, char * const argv[]) { auto &config = mcfp::config::instance(); // Initialise the config object. This can be done more than once, // e.g. when you have different sets of options depending on the // first operand. config.init( // The first parameter is the 'usage' line, used when printing out the options "usage: example [options] file", // Flag options (not taking a parameter) mcfp::make_option("help,h", "Print this help text"), mcfp::make_option("verbose,v", "Verbose level, can be specified more than once to increase level"), // A couple of options with parameter mcfp::make_option("config", "Config file to use"), mcfp::make_option("text", "The text string to echo"), // And options with a default parameter mcfp::make_option("a", 1, "first parameter for multiplication"), mcfp::make_option("b", 2.0f, "second parameter for multiplication"), // You can also allow multiple values mcfp::make_option>("c", "Option c, can be specified more than once"), // This option is not shown when printing out the options mcfp::make_hidden_option("d", "Debug mode") ); // There are two flavors of calls, ones that take an error_code // and return the error in that code in case something is wrong. // The alternative is calling without an error_code, in which // case an exception is thrown when appropriate // Parse the command line arguments here std::error_code ec; config.parse(argc, argv, ec); if (ec) { std::cerr << "Error parsing arguments: " << ec.message() << std::endl; exit(1); } // First check, to see if we need to stop early on if (config.has("help") or config.operands().size() != 1) { // This will print out the 'usage' message with all the visible options std::cerr << config << std::endl; exit(config.has("help") ? 0 : 1); } // Configuration files, read it if it exists. If the users // specifies an alternative config file, it is an error if that // file cannot be found. config.parse_config_file("config", "example.conf", { "." }, ec); if (ec) { std::cerr << "Error parsing config file: " << ec.message() << std::endl; exit(1); } // If options are specified more than once, you can get the count int VERBOSE = config.count("verbose"); // Operands are arguments that are not options, e.g. files to act upon std::cout << "The first operand is " << config.operands().front() << std::endl; // Getting the value of a string option auto text = config.get("text", ec); if (ec) { std::cerr << "Error getting option text: " << ec.message() << std::endl; exit(1); } std::cout << "Text option is " << text << std::endl; // Likewise for numeric options int a = config.get("a"); float b = config.get("b"); std::cout << "a (" << a << ") * b (" << b << ") = " << a * b << std::endl; // And multiple strings for (std::string s : config.get>("c")) std::cout << "c: " << s << std::endl; return 0; }libmcfp-1.2.4/include/0000775000175000017500000000000014364034504014422 5ustar maartenmaartenlibmcfp-1.2.4/include/mcfp/0000775000175000017500000000000014364034504015347 5ustar maartenmaartenlibmcfp-1.2.4/include/mcfp/mcfp.hpp0000664000175000017500000005640114364034504017013 0ustar maartenmaarten/*- * 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 /// \file mcfp.hpp /// This header-only library contains code to parse argc/argv and store the /// values contained therein into a singleton object. #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mcfp { // we use the new system_error stuff. enum class config_error { unknown_option = 1, option_does_not_accept_argument, missing_argument_for_option, option_not_specified, invalid_config_file, wrong_type_cast, config_file_not_found }; class config_category_impl : public std::error_category { public: const char *name() const noexcept override { return "configuration"; } std::string message(int ev) const override { switch (static_cast(ev)) { case config_error::unknown_option: return "unknown option"; case config_error::option_does_not_accept_argument: return "option does not accept argument"; case config_error::missing_argument_for_option: return "missing argument for option"; case config_error::option_not_specified: return "option was not specified"; case config_error::invalid_config_file: return "config file contains a syntax error"; case config_error::wrong_type_cast: return "the implementation contains a type cast error"; case config_error::config_file_not_found: return "the specified config file was not found"; default: assert(false); return "unknown error code"; } } bool equivalent(const std::error_code &/*code*/, int /*condition*/) const noexcept override { return false; } }; inline std::error_category &config_category() { static config_category_impl instance; return instance; } inline std::error_code make_error_code(config_error e) { return std::error_code(static_cast(e), config_category()); } inline std::error_condition make_error_condition(config_error e) { return std::error_condition(static_cast(e), config_category()); } // -------------------------------------------------------------------- // 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 struct is_container_type and is_detected_v and not is_detected_v>> : std::true_type { }; template inline constexpr bool is_container_type_v = is_container_type::value; // -------------------------------------------------------------------- // The options classes namespace detail { // 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 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 = charconv::from_chars(argument.data(), argument.data() + argument.length(), value); if (r.ec != std::errc()) ec = std::make_error_code(r.ec); return value; } static std::string to_string(const T &value) { char b[32]; auto r = charconv::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 struct option_traits and std::is_assignable_v>> { 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; ///< The long argument name std::string m_desc; ///< The description of the argument char m_short_name; ///< The single character name of the argument, can be zero bool m_is_flag = true, ///< When true, this option does not allow arguments m_has_default = false, ///< When true, this option has a default value. m_multi = false, ///< When true, this option allows mulitple values. m_hidden; ///< When true, this option is hidden from the help text int m_seen = 0; ///< How often the option was seen on the command line option_base(const option_base &rhs) = default; option_base(std::string_view name, std::string_view desc, bool hidden) : m_name(name) , m_desc(desc) , m_short_name(0) , m_hidden(hidden) { if (m_name.length() == 1) m_short_name = m_name.front(); else if (m_name.length() > 2 and m_name[m_name.length() - 2] == ',') { m_short_name = m_name.back(); m_name.erase(m_name.end() - 2, m_name.end()); } } virtual ~option_base() = default; virtual void set_value(std::string_view /*value*/, std::error_code &/*ec*/) { assert(false); } virtual std::any get_value() const { return {}; } virtual std::string get_default_value() const { return {}; } size_t width() const { size_t result = m_name.length(); if (result <= 1) result = 2; else if (m_short_name != 0) result += 7; if (not m_is_flag) { result += 4; if (m_has_default) result += 4 + get_default_value().length(); } return result + 6; } void write(std::ostream &os, size_t width) const { if (m_hidden) // quick exit return; size_t w2 = 2; os << " "; if (m_short_name) { os << '-' << m_short_name; w2 += 2; if (m_name.length() > 1) { os << " [ --" << m_name << " ]"; w2 += 7 + m_name.length(); } } else { os << "--" << m_name; w2 += 2 + m_name.length(); } if (not m_is_flag) { os << " arg"; w2 += 4; if (m_has_default) { auto default_value = get_default_value(); os << " (=" << default_value << ')'; w2 += 4 + default_value.length(); } } auto leading_spaces = width; if (w2 + 2 > width) os << std::endl; else leading_spaces = width - w2; word_wrapper ww(m_desc, get_terminal_width() - width); for (auto line : ww) { os << std::string(leading_spaces, ' ') << line << std::endl; leading_spaces = width; } } }; template struct option : public option_base { using traits_type = option_traits; using value_type = typename option_traits::value_type; std::optional m_value; option(const option &rhs) = default; option(std::string_view name, std::string_view desc, bool hidden) : option_base(name, desc, hidden) { m_is_flag = false; } option(std::string_view name, const value_type &default_value, std::string_view desc, bool hidden) : option(name, desc, hidden) { m_has_default = true; m_value = default_value; } void set_value(std::string_view argument, std::error_code &ec) override { m_value = traits_type::set_value(argument, ec); } std::any get_value() const override { std::any result; if (m_value) result = *m_value; return result; } std::string get_default_value() const override { if constexpr (std::is_same_v) return *m_value; else return traits_type::to_string(*m_value); } }; template struct multiple_option : public option_base { using value_type = typename T::value_type; using traits_type = option_traits; std::vector m_values; multiple_option(const multiple_option &rhs) = default; multiple_option(std::string_view name, std::string_view desc, bool hidden) : option_base(name, desc, hidden) { m_is_flag = false; m_multi = true; } void set_value(std::string_view argument, std::error_code &ec) override { m_values.emplace_back(traits_type::set_value(argument, ec)); } std::any get_value() const override { return { m_values }; } }; template <> struct option : public option_base { option(const option &rhs) = default; option(std::string_view name, std::string_view desc, bool hidden) : option_base(name, desc, hidden) { } }; } // namespace detail // -------------------------------------------------------------------- /// \brief A singleton class. Use config::instance to create an instance class config { public: using option_base = detail::option_base; void set_usage(std::string_view usage) { m_usage = usage; } /// \brief Initialise a config instance with a \a usage message and a set of \a options template void init(std::string_view usage, Options... options) { m_usage = usage; 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); } 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(); } 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 == '#') 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_hidden_option(std::string_view name, std::string_view description) { return detail::option(name, description, true); } 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, const T &v, std::string_view description) { return detail::option(name, v, description, true); } 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_hidden_option(std::string_view name, std::string_view description) { return detail::option(name, description, true); } } // namespace mcfp namespace std { template <> struct is_error_condition_enum : public true_type { }; } // namespace stdlibmcfp-1.2.4/include/mcfp/text.hpp0000664000175000017500000003077114364034504017054 0ustar maartenmaarten/*- * 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 #if __has_include() #include #else #include #endif namespace mcfp { #if (not defined(__cpp_lib_experimental_detect) or (__cpp_lib_experimental_detect < 201505)) and (not defined(_LIBCPP_VERSION) or _LIBCPP_VERSION < 5000) // This code is copied from: // https://ld2015.scusa.lsu.edu/cppreference/en/cpp/experimental/is_detected.html template< class... > using void_t = void; namespace detail { template class Op, class... Args> struct detector { using value_t = std::false_type; using type = Default; }; template class Op, class... Args> struct detector>, Op, Args...> { // Note that std::void_t is a c++17 feature using value_t = std::true_type; using type = Op; }; } // namespace detail struct nonesuch { nonesuch() = delete; ~nonesuch() = delete; nonesuch(nonesuch const&) = delete; void operator=(nonesuch const&) = delete; }; template class Op, class... Args> using is_detected = typename detail::detector::value_t; template class Op, class... Args> constexpr inline bool is_detected_v = is_detected::value; template class Op, class... Args> using detected_t = typename detail::detector::type; template class Op, class... Args> using detected_or = detail::detector; template class Op, class... Args> using is_detected_exact = std::is_same>; template class Op, class... Args> constexpr inline bool is_detected_exact_v = is_detected_exact::value; #else template class Op, class... Args> constexpr inline bool is_detected_v = std::experimental::is_detected::value; #endif template struct my_charconv { using value_type = T; static std::from_chars_result from_chars(const char *first, const char *last, value_type &value) { std::from_chars_result result{ first, {} }; enum State { IntegerSign, Integer, Fraction, ExponentSign, Exponent } state = IntegerSign; int sign = 1; unsigned long long vi = 0; long double f = 1; int exponent_sign = 1; int exponent = 0; bool done = false; while (not done and result.ec == std::errc()) { char ch = result.ptr != last ? *result.ptr : 0; ++result.ptr; switch (state) { case IntegerSign: if (ch == '-') { sign = -1; state = Integer; } else if (ch == '+') state = Integer; else if (ch >= '0' and ch <= '9') { vi = ch - '0'; state = Integer; } else if (ch == '.') state = Fraction; else result.ec = std::errc::invalid_argument; break; case Integer: if (ch >= '0' and ch <= '9') vi = 10 * vi + (ch - '0'); else if (ch == 'e' or ch == 'E') state = ExponentSign; else if (ch == '.') state = Fraction; else { done = true; --result.ptr; } break; case Fraction: if (ch >= '0' and ch <= '9') { vi = 10 * vi + (ch - '0'); f /= 10; } else if (ch == 'e' or ch == 'E') state = ExponentSign; else { done = true; --result.ptr; } break; case ExponentSign: if (ch == '-') { exponent_sign = -1; state = Exponent; } else if (ch == '+') state = Exponent; else if (ch >= '0' and ch <= '9') { exponent = ch - '0'; state = Exponent; } else result.ec = std::errc::invalid_argument; break; case Exponent: if (ch >= '0' and ch <= '9') exponent = 10 * exponent + (ch - '0'); else { done = true; --result.ptr; } break; } } if (result.ec == std::errc()) { long double v = f * vi * sign; if (exponent != 0) v *= std::pow(10, exponent * exponent_sign); if (std::isnan(v)) result.ec = std::errc::invalid_argument; else if (std::abs(v) > std::numeric_limits::max()) result.ec = std::errc::result_out_of_range; value = static_cast(v); } return result; } template , int> = 0> static std::to_chars_result to_chars(Iterator first, Iterator last, const T &value) { int size = last - first; int r; if constexpr (std::is_same_v) r = snprintf(first, last - first, "%lg", value); else r = snprintf(first, last - first, "%g", value); std::to_chars_result result; if (r < 0 or r >= size) result = { first, std::errc::value_too_large }; else result = { first + r, std::errc() }; return result; } }; template struct std_charconv { static std::from_chars_result from_chars(const char *a, const char *b, T &d) { return std::from_chars(a, b, d); } template static std::to_chars_result to_chars(Iterator a, Iterator b, const T &value) { return std::to_chars(a, b, value); } }; template using from_chars_function = decltype(std::from_chars(std::declval(), std::declval(), std::declval())); template using charconv = typename std::conditional_t, std_charconv, my_charconv>; // -------------------------------------------------------------------- /// Simplified line breaking code taken from a decent text editor. /// In this case, simplified means it only supports ASCII. /// The algorithm uses dynamic programming to find the optimal /// separation in lines. class word_wrapper : public std::vector { public: word_wrapper(std::string_view text, size_t width) : m_width(width) { std::string_view::size_type line_start = 0, line_end = text.find('\n'); for (;;) { auto line = text.substr(line_start, line_end - line_start); if (line.empty()) this->push_back(line); else { auto lines = wrap_line(line); this->insert(this->end(), lines.begin(), lines.end()); } if (line_end == std::string_view::npos) break; line_start = line_end + 1; line_end = text.find('\n', line_start); } } private: std::vector wrap_line(std::string_view line) { std::vector result; std::vector offsets = { 0 }; auto b = line.begin(); while (b != line.end()) { auto e = next_line_break(b, line.end()); offsets.push_back(e - line.begin()); b = e; } size_t count = offsets.size() - 1; std::vector minima(count + 1, std::numeric_limits::max()); minima[0] = 0; std::vector breaks(count + 1, 0); for (size_t i = 0; i < count; ++i) { size_t j = i + 1; while (j <= count) { size_t w = offsets[j] - offsets[i]; if (w > m_width) break; while (w > 0 and std::isspace(line[offsets[i] + w - 1])) --w; size_t cost = minima[i]; if (j < count) // last line may be shorter cost += (m_width - w) * (m_width - w); if (cost < minima[j]) { minima[j] = cost; breaks[j] = i; } ++j; } } size_t j = count; while (j > 0) { size_t i = breaks[j]; result.push_back(line.substr(offsets[i], offsets[j] - offsets[i])); j = i; } reverse(result.begin(), result.end()); return result; } std::string_view::const_iterator next_line_break(std::string_view::const_iterator text, std::string_view::const_iterator end) { if (text == end) return text; enum LineBreakClass { OP, // OpenPunctuation, CL, // ClosePunctuation, CP, // CloseParenthesis, QU, // Quotation, EX, // Exlamation, SY, // SymbolAllowingBreakAfter, IS, // InfixNumericSeparator, PR, // PrefixNumeric, PO, // PostfixNumeric, NU, // Numeric, AL, // Alphabetic, HY, // Hyphen, BA, // BreakAfter, CM, // CombiningMark, WJ, // WordJoiner, MB, // MandatoryBreak, SP, // Space, }; static const LineBreakClass kASCII_LineBreakTable[128] = { CM, CM, CM, CM, CM, CM, CM, CM, CM, BA, MB, MB, MB, SP, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, CM, SP, EX, QU, AL, PR, PO, AL, QU, OP, CP, AL, PR, IS, HY, IS, SY, NU, NU, NU, NU, NU, NU, NU, NU, NU, NU, IS, IS, AL, AL, AL, EX, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, PR, CP, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, AL, OP, BA, CL, AL, CM }; enum BreakAction { DBK = 0, // direct break (blank in table) IBK, // indirect break (% in table) PBK, // prohibited break (^ in table) CIB, // combining indirect break CPB // combining prohibited break }; static const BreakAction brkTable[15][15] = { // OP CL CP QU EX SY IS PR PO NU AL HY BA CM WJ /* OP */ { PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, PBK, CPB, PBK }, /* CL */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* CP */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* QU */ { PBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* EX */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* SY */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, DBK, IBK, IBK, CIB, PBK }, /* IS */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* PR */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* PO */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* NU */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* AL */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* HY */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, DBK, IBK, IBK, CIB, PBK }, /* BA */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, DBK, DBK, IBK, IBK, CIB, PBK }, /* CM */ { DBK, PBK, PBK, IBK, PBK, PBK, PBK, DBK, DBK, IBK, IBK, IBK, IBK, CIB, PBK }, /* WJ */ { IBK, PBK, PBK, IBK, PBK, PBK, PBK, IBK, IBK, IBK, IBK, IBK, IBK, CIB, PBK }, }; uint8_t ch = *text; LineBreakClass cls; if (ch < 128) cls = kASCII_LineBreakTable[ch]; else cls = AL; if (cls == SP) cls = WJ; LineBreakClass ncls = cls; while (++text != end and cls != MB) { ch = *text; LineBreakClass lcls = ncls; if (ch < 128) ncls = kASCII_LineBreakTable[ch]; else ncls = AL; if (ncls == MB) { ++text; break; } if (ncls == SP) continue; BreakAction brk = brkTable[cls][ncls]; if (brk == DBK or (brk == IBK and lcls == SP)) break; cls = ncls; } return text; } size_t m_width; }; } // namespace mcfp libmcfp-1.2.4/include/mcfp/utilities.hpp0000664000175000017500000000461014364034504020074 0ustar maartenmaarten/*- * 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 #if __has_include() #include #include #include #elif defined(_MSC_VER) #include #endif namespace mcfp { #if defined(_MSC_VER) /// @brief Get the width in columns of the current terminal /// @return number of columns of the terminal inline uint32_t get_terminal_width() { CONSOLE_SCREEN_BUFFER_INFO csbi; ::GetConsoleScreenBufferInfo(::GetStdHandle(STD_OUTPUT_HANDLE), &csbi); return csbi.srWindow.Right - csbi.srWindow.Left + 1; } #elif __has_include() /// @brief Get the width in columns of the current terminal /// @return number of columns of the terminal inline uint32_t get_terminal_width() { uint32_t result = 80; if (::isatty(STDOUT_FILENO)) { struct winsize w; ::ioctl(0, TIOCGWINSZ, &w); result = w.ws_col; } return result; } #else #warning "Could not find the terminal width, falling back to default" inline uint32_t get_terminal_width() { return 80; } #endif } // namespace mcfplibmcfp-1.2.4/test/0000775000175000017500000000000014364034504013756 5ustar maartenmaartenlibmcfp-1.2.4/test/unit-test.conf0000664000175000017500000000005414364034504016560 0ustar maartenmaarten# A simple test config file aap = 2 noot = 3libmcfp-1.2.4/test/unit-test.cpp0000664000175000017500000003650514364034504016427 0ustar maartenmaarten/*- * 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. */ #define BOOST_TEST_ALTERNATIVE_INIT_API #include #include #include namespace tt = boost::test_tools; namespace utf = boost::unit_test; namespace fs = std::filesystem; fs::path gTestDir = fs::current_path(); // -------------------------------------------------------------------- bool init_unit_test() { // not a test, just initialize test dir if (boost::unit_test::framework::master_test_suite().argc == 2) gTestDir = boost::unit_test::framework::master_test_suite().argv[1]; return true; } // -------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(t_1, * utf::tolerance(0.001)) { int argc = 3; const char *const argv[] = { "test", "--flag", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("flag", ""), mcfp::make_option("param_int", ""), mcfp::make_option("param_int_2", 1, ""), mcfp::make_option("param_float", ""), mcfp::make_option("param_float_2", 3.14f, "")); config.parse(argc, argv); BOOST_CHECK(config.has("flag")); BOOST_CHECK(not config.has("flag2")); BOOST_CHECK_EQUAL(config.get("param_int_2"), 1); BOOST_CHECK_THROW(config.get("param_int_2"), std::system_error); BOOST_CHECK_THROW(config.get("param_int"), std::system_error); BOOST_TEST(config.get("param_float_2") == 3.14); BOOST_CHECK_THROW(config.get("param_float_2"), std::system_error); BOOST_CHECK_THROW(config.get("param_float"), std::system_error); } BOOST_AUTO_TEST_CASE(t_2) { int argc = 3; const char *const argv[] = { "test", "-vvvv", "--verbose", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("verbose,v", "")); config.parse(argc, argv); BOOST_CHECK_EQUAL(config.count("verbose"), 5); } BOOST_AUTO_TEST_CASE(t_3) { int argc = 2; const char *const argv[] = { "test", "--param_int=42", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("param_int", "")); config.parse(argc, argv); BOOST_CHECK(config.has("param_int")); BOOST_CHECK_EQUAL(config.get("param_int"), 42); } BOOST_AUTO_TEST_CASE(t_4) { int argc = 3; const char *const argv[] = { "test", "--param_int", "42", nullptr }; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("param_int", "")); config.parse(argc, argv); BOOST_CHECK(config.has("param_int")); BOOST_CHECK_EQUAL(config.get("param_int"), 42); } BOOST_AUTO_TEST_CASE(t_5) { const char *const argv[] = { "test", "-i", "42", "-j43", nullptr }; int argc = sizeof(argv) / sizeof(char*); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); BOOST_CHECK(config.has("nr1")); BOOST_CHECK(config.has("nr2")); BOOST_CHECK_EQUAL(config.get("nr1"), 42); BOOST_CHECK_EQUAL(config.get("nr2"), 43); } BOOST_AUTO_TEST_CASE(t_6) { const char *const argv[] = { "test", "-i", "42", "-j43", "foo", "bar", nullptr }; int argc = sizeof(argv) / sizeof(char*); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); BOOST_CHECK(config.has("nr1")); BOOST_CHECK(config.has("nr2")); BOOST_CHECK_EQUAL(config.get("nr1"), 42); BOOST_CHECK_EQUAL(config.get("nr2"), 43); BOOST_CHECK_EQUAL(config.operands().size(), 2); BOOST_CHECK_EQUAL(config.operands().front(), "foo"); BOOST_CHECK_EQUAL(config.operands().back(), "bar"); } BOOST_AUTO_TEST_CASE(t_7) { const char *const argv[] = { "test", "--", "-i", "42", "-j43", "foo", "bar", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("nr1,i", ""), mcfp::make_option("nr2,j", "")); config.parse(argc, argv); BOOST_CHECK(not config.has("nr1")); BOOST_CHECK(not config.has("nr2")); BOOST_CHECK_EQUAL(config.operands().size(), 5); auto compare = std::vector{ argv[2], argv[3], argv[4], argv[5], argv[6] }; BOOST_CHECK(config.operands() == compare); } BOOST_AUTO_TEST_CASE(t_8) { const char *const argv[] = { "test", "-i", "foo", "-jbar", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("i", ""), mcfp::make_option("j", ""), mcfp::make_option("k", "baz", "")); config.parse(argc, argv); BOOST_CHECK(config.has("i")); BOOST_CHECK_EQUAL(config.get("i"), "foo"); BOOST_CHECK(config.has("j")); BOOST_CHECK_EQUAL(config.get("j"), "bar"); BOOST_CHECK(config.has("k")); BOOST_CHECK_EQUAL(config.get("k"), "baz"); } BOOST_AUTO_TEST_CASE(t_9) { auto &config = mcfp::config::instance(); config.set_usage("usage: test [options]"); config.init( "test [options]", mcfp::make_option("i", "First option"), mcfp::make_option("j", "This is the second option"), mcfp::make_option("a-very-long-option-name,k", "baz", "And, you guessed it, this must be option three.")); // std::stringstream ss; // int fd = open("/dev/null", O_RDWR); // dup2(fd, STDOUT_FILENO); // ss << config << std::endl; // const char kExpected[] = R"(usage: test [options] // -i arg First option // -j arg This is the second option // -k [ --a-very-long-option-name ] arg (=baz) // And, you guessed it, this must be // option three. // )"; // std::cerr << '>' << kExpected << '<' << std::endl; // std::cerr << '>' << ss.str() << '<' << std::endl; // BOOST_CHECK_EQUAL(ss.str(), kExpected); } BOOST_AUTO_TEST_CASE(t_10) { std::string s1 = R"(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. )"; mcfp::word_wrapper ww(s1, 80); std::ostringstream os; for (auto line : ww) os << line << std::endl; BOOST_CHECK_EQUAL(os.str(), R"(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. )"); } BOOST_AUTO_TEST_CASE(t_11) { const char *const argv[] = { "test", "-faap", "-fnoot", "-fmies", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option>("file,f", "")); config.parse(argc, argv); BOOST_CHECK_EQUAL(config.count("file"), 3); std::vector files = config.get>("file"); BOOST_CHECK_EQUAL(files.size(), 3); BOOST_CHECK_EQUAL(files[0], "aap"); BOOST_CHECK_EQUAL(files[1], "noot"); BOOST_CHECK_EQUAL(files[2], "mies"); } BOOST_AUTO_TEST_CASE(t_12) { const char *const argv[] = { "test", "--aap", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option>("file,f", "")); std::error_code ec; config.parse(argc, argv, ec); BOOST_CHECK(ec == mcfp::config_error::unknown_option); config.set_ignore_unknown(true); ec = {}; config.parse(argc, argv, ec); BOOST_CHECK(not ec); } BOOST_AUTO_TEST_CASE(t_13) { const char *const argv[] = { "test", "--test=bla", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("test", "")); BOOST_CHECK_NO_THROW(config.parse(argc, argv)); BOOST_TEST(config.has("test")); BOOST_TEST(config.get("test") == "bla"); } // -------------------------------------------------------------------- BOOST_AUTO_TEST_CASE(file_1, * utf::tolerance(0.001)) { const std::string_view config_file{ R"( # This is a test configuration aap=1 noot = 2 mies = pi = 3.14 s = hello, world! verbose )" }; struct membuf : public std::streambuf { membuf(char * text, size_t length) { this->setg(text, text, text + length); } } buffer(const_cast(config_file.data()), config_file.length()); std::istream is(&buffer); auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("mies", ""), mcfp::make_option("pi", ""), mcfp::make_option("s", ""), mcfp::make_option("verbose,v", "")); std::error_code ec; config.parse_config_file(is, ec); BOOST_CHECK(not ec); BOOST_CHECK(config.has("aap")); BOOST_CHECK_EQUAL(config.get("aap"), "1"); BOOST_CHECK(config.has("noot")); BOOST_CHECK_EQUAL(config.get("noot"), 2); BOOST_CHECK(config.has("pi")); BOOST_TEST(config.get("pi") == 3.14); BOOST_CHECK(config.has("s")); BOOST_CHECK_EQUAL(config.get("s"), "hello, world!"); BOOST_CHECK(config.has("verbose")); } BOOST_AUTO_TEST_CASE(file_2) { auto &config = mcfp::config::instance(); std::tuple tests[] = { { "aap !", "aap", make_error_code(mcfp::config_error::invalid_config_file) }, { "aap=aap", "aap", {} }, { "aap", "aap", make_error_code(mcfp::config_error::missing_argument_for_option) }, { "verbose=1", "verbose", make_error_code(mcfp::config_error::option_does_not_accept_argument) }, }; for (const auto &[config_file, option, err] : tests) { struct membuf : public std::streambuf { membuf(char * text, size_t length) { this->setg(text, text, text + length); } } buffer(const_cast(config_file.data()), config_file.length()); std::istream is(&buffer); std::error_code ec; config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("pi", ""), mcfp::make_option("s", ""), mcfp::make_option("verbose,v", "")); config.parse_config_file(is, ec); BOOST_CHECK(ec == err); if (ec == std::errc()) BOOST_CHECK(config.has(option)); } } BOOST_AUTO_TEST_CASE(file_3) { auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("config", "")); std::error_code ec; const char *const argv[] = { "test", "--aap=aap", "--noot=42", "--config=unit-test.conf", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; config.parse(argc, argv); config.parse_config_file("config", "bla-bla.conf", { gTestDir.string() }, ec); BOOST_CHECK(not ec); BOOST_CHECK(config.has("aap")); BOOST_CHECK_EQUAL(config.get("aap"), "aap"); BOOST_CHECK(config.has("noot")); BOOST_CHECK_EQUAL(config.get("noot"), 42); } BOOST_AUTO_TEST_CASE(file_4) { auto &config = mcfp::config::instance(); config.init( "test [options]", mcfp::make_option("aap", ""), mcfp::make_option("noot", ""), mcfp::make_option("config", "")); std::error_code ec; const char *const argv[] = { "test", "--aap=aap", nullptr }; int argc = sizeof(argv) / sizeof(char*) - 1; config.parse(argc, argv); config.parse_config_file("config", "unit-test.conf", { gTestDir.string() }, ec); BOOST_CHECK(not ec); BOOST_CHECK(config.has("aap")); BOOST_CHECK_EQUAL(config.get("aap"), "aap"); BOOST_CHECK(config.has("noot")); BOOST_CHECK_EQUAL(config.get("noot"), 3); }