From 674737120e96be8e6aa2974c49596a77bc52ea8e Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 12 Apr 2024 08:57:50 -0500 Subject: [PATCH 1/7] ENH: Removing CLI --- src/CLI/CMakeLists.txt | 14 - src/CLI/README.md | 168 -- src/CLI/include/FierroParallelBackends.hpp | 80 - src/CLI/include/MeshBuilderBackend.hpp | 83 - src/CLI/include/VoxelizerBackend.hpp | 84 - src/CLI/include/argparse/argparse.hpp | 1698 -------------------- src/CLI/include/argument_exception.hpp | 15 - src/CLI/include/backend.hpp | 73 - src/CLI/src/main.cpp | 100 -- src/CMakeLists.txt | 2 +- 10 files changed, 1 insertion(+), 2316 deletions(-) delete mode 100644 src/CLI/CMakeLists.txt delete mode 100644 src/CLI/README.md delete mode 100644 src/CLI/include/FierroParallelBackends.hpp delete mode 100644 src/CLI/include/MeshBuilderBackend.hpp delete mode 100644 src/CLI/include/VoxelizerBackend.hpp delete mode 100644 src/CLI/include/argparse/argparse.hpp delete mode 100644 src/CLI/include/argument_exception.hpp delete mode 100644 src/CLI/include/backend.hpp delete mode 100644 src/CLI/src/main.cpp diff --git a/src/CLI/CMakeLists.txt b/src/CLI/CMakeLists.txt deleted file mode 100644 index 70e0fefa3..000000000 --- a/src/CLI/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.17) -project(fierro-cli) - -set(CMAKE_CXX_STANDARD 17) -add_executable(fierro src/main.cpp) -target_include_directories(fierro PUBLIC include) - -# HIPCC is a driver for Clang, and Clang wants you to -# link against lstdc++fs for version 15. -if (NOT APPLE) - target_link_options(fierro PRIVATE -lstdc++fs) -endif() - -install(TARGETS fierro) diff --git a/src/CLI/README.md b/src/CLI/README.md deleted file mode 100644 index 9fb134b7e..000000000 --- a/src/CLI/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# Fiero Command Line Interface -This CLI package is a Fierro command line interface that is intended to act as a consolidated front-end for our Fierro backend tools. It provides a consistent interface and framework for exposing tools to the user. - -## Front-End -The front end here is built off of [argparse](https://github.com/p-ranav/argparse), which implements a language agnostic standard for simple command line interface design. It defines a syntax for programs from the command line with suitable helptext/defaults/post-processing. - -The Fierro-CLI implements two things: -1. Output directory -- A directory to run the program in. Outputs of the program will by default be put here if some other backend specific option is not specified. -2. Subcommands -- a list of argparse "subcommands" that each represent an available backend tool. - -Example: -``` -$ fierro -h - -Usage: fierro [--help] [--version] [--output VAR] {mesh-builder,parallel-explicit,parallel-implicit,voxelizer} - -Optional arguments: - -h, --help shows help message and exits - -v, --version prints version information and exits - -o, --output output directory for the program [default: "."] - -Subcommands: - mesh-builder Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems. - parallel-explicit Use an explicit solution scheme to step the system. - parallel-implicit Use an implicit solution scheme to step the system. - voxelizer Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. Note: Only supports binary STL mesh inputs -``` - -## Backends -This Fierro-CLI package is designed to operate decoupled from backends. Each registered backend is an executable that may or may not be present in the system. - -### Registering Backends -To add a new backend, you only need to do a couple of steps. -#### 1. Implementing NewBackend.hpp -You should start by copying one of the existing backend.hpp files and implementing your new backend. We will use `VoxelizerBackend.hpp` as an example. - -You should derive from `FierroBackend` and tell the base class which executable name to look for -```c++ -#include "backend.hpp" -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - ... - } - ... -}; -``` - -Then, in the constructor, setup your argparse command - -```c++ - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") // <--- This is the name that shows up under "Subcommands:" in the CLI - ); - - // Here you can add your arguments with their helptext. - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); // <-- You can also add a post-processing command. In this case we convert the input filepath to an absolute one. - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - - // Add a description for your subcommand - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - } - ... -}; -``` - -Lastly you tell the CLI framework how to actually invoke the backend command. This is mostly just building a system command string and invoking it, but I suppose you could do whatever you want. -```c++ - -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; -``` - -You probably also want to add some validation to your input arguments. The voxelizer does this with a serparate function: - -```c++ -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - // Here we just check if the input file is an actual file. - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - ... -}; -``` - -Now you have a well defined backend. - - -#### 2. Plug it into the CLI framework -Head over to main.cpp and add your backend to the list of backends: -```c++ -std::vector> BACKENDS { - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - //std::shared_ptr() <-- Add yours here -}; - -``` - -Now you have everything set up, and it will automatically be picked up if its compiled and in the system. \ No newline at end of file diff --git a/src/CLI/include/FierroParallelBackends.hpp b/src/CLI/include/FierroParallelBackends.hpp deleted file mode 100644 index 0926f7b0e..000000000 --- a/src/CLI/include/FierroParallelBackends.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef FIERRO_PARALLEL_BACKENDS -#define FIERRO_PARALLEL_BACKENDS -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct ParallelBackend: public FierroBackend { - ParallelBackend(std::string name) - : FierroBackend(name) { } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return std::nullopt; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string input_file = this->command->get("config"); - std::string sys_command = this->exec_path.value().string() + " " + input_file; - - return system(sys_command.c_str()); - } - - void add_common_options() { - this->command->add_argument("config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - } -}; - -struct ParallelExplicit: public ParallelBackend { - ParallelExplicit() : ParallelBackend("fierro-parallel-explicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-explicit") - ); - this->add_common_options(); - this->command->add_description("Use an explicit solution scheme to step the system."); - } -}; - -struct ParallelImplicit: public ParallelBackend { - ParallelImplicit() : ParallelBackend("fierro-parallel-implicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-implicit") - ); - this->add_common_options(); - this->command->add_description("Use an implicit solution scheme to step the system."); - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/MeshBuilderBackend.hpp b/src/CLI/include/MeshBuilderBackend.hpp deleted file mode 100644 index 22f2e217d..000000000 --- a/src/CLI/include/MeshBuilderBackend.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef MESH_BUILDER_BACKENDS_H -#define MESH_BUILDER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct MeshBuilderBackend: public FierroBackend { - MeshBuilderBackend() : FierroBackend("fierro-mesh-builder") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("mesh-builder") - ); - - this->command->add_argument("-c", "config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - this->command->add_argument("--box-help") - .help("Set this flag to print out an example box mesh configuration file.") - .default_value(false) - .implicit_value(true); - this->command->add_argument("--cylinder-help") - .help("Set this flag to print out an example cylinder mesh configuration file.") - .default_value(false) - .implicit_value(true);; - this->command->add_description("Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - if (this->command->is_used("box-help") || this->command->is_used("cylinder-help")) - return {}; - - if (!parser->is_used("config")) - msg = "A value for config is required"; - else if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - if (this->command->get("box-help")) - sys_command = this->exec_path.value().string() + " Box"; - else if (this->command->get("cylinder-help")) - sys_command = this->exec_path.value().string() + " Cylinder"; - else { - std::string input_file = this->command->get("config"); - sys_command = this->exec_path.value().string() + " " + input_file; - } - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/VoxelizerBackend.hpp b/src/CLI/include/VoxelizerBackend.hpp deleted file mode 100644 index 63ca7c769..000000000 --- a/src/CLI/include/VoxelizerBackend.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef VOXELIZER_BACKENDS_H -#define VOXELIZER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") - ); - - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/argparse/argparse.hpp b/src/CLI/include/argparse/argparse.hpp deleted file mode 100644 index 633f7820d..000000000 --- a/src/CLI/include/argparse/argparse.hpp +++ /dev/null @@ -1,1698 +0,0 @@ -/* - __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ - / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ -| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse - \__,_|_| \__, | .__/ \__,_|_| |___/\___| - |___/|_| - -Licensed under the MIT License . -SPDX-License-Identifier: MIT -Copyright (c) 2019-2022 Pranav Srinivas Kumar -and other contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace argparse { - -namespace details { // namespace for helper methods - -template -struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template -struct HasContainerTraits< - T, std::void_t().begin()), - decltype(std::declval().end()), - decltype(std::declval().size())>> : std::true_type {}; - -template -static constexpr bool IsContainer = HasContainerTraits::value; - -template -struct HasStreamableTraits : std::false_type {}; - -template -struct HasStreamableTraits< - T, - std::void_t() << std::declval())>> - : std::true_type {}; - -template -static constexpr bool IsStreamable = HasStreamableTraits::value; - -constexpr std::size_t repr_max_container_size = 5; - -template std::string repr(T const &val) { - if constexpr (std::is_same_v) { - return val ? "true" : "false"; - } else if constexpr (std::is_convertible_v) { - return '"' + std::string{std::string_view{val}} + '"'; - } else if constexpr (IsContainer) { - std::stringstream out; - out << "{"; - const auto size = val.size(); - if (size > 1) { - out << repr(*val.begin()); - std::for_each( - std::next(val.begin()), - std::next( - val.begin(), - static_cast( - std::min(size, repr_max_container_size) - 1)), - [&out](const auto &v) { out << " " << repr(v); }); - if (size <= repr_max_container_size) { - out << " "; - } else { - out << "..."; - } - } - if (size > 0) { - out << repr(*std::prev(val.end())); - } - out << "}"; - return out.str(); - } else if constexpr (IsStreamable) { - std::stringstream out; - out << val; - return out.str(); - } else { - return ""; - } -} - -namespace { - -template constexpr bool standard_signed_integer = false; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; - -template constexpr bool standard_unsigned_integer = false; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> -constexpr bool standard_unsigned_integer = true; - -} // namespace - -constexpr int radix_8 = 8; -constexpr int radix_10 = 10; -constexpr int radix_16 = 16; - -template -constexpr bool standard_integer = - standard_signed_integer || standard_unsigned_integer; - -template -constexpr decltype(auto) -apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, - std::index_sequence /*unused*/) { - return std::invoke(std::forward(f), std::get(std::forward(t))..., - std::forward(x)); -} - -template -constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { - return details::apply_plus_one_impl( - std::forward(f), std::forward(t), std::forward(x), - std::make_index_sequence< - std::tuple_size_v>>{}); -} - -constexpr auto pointer_range(std::string_view s) noexcept { - return std::tuple(s.data(), s.data() + s.size()); -} - -template -constexpr bool starts_with(std::basic_string_view prefix, - std::basic_string_view s) noexcept { - return s.substr(0, prefix.size()) == prefix; -} - -enum class chars_format { - scientific = 0x1, - fixed = 0x2, - hex = 0x4, - general = fixed | scientific -}; - -struct ConsumeHexPrefixResult { - bool is_hexadecimal; - std::string_view rest; -}; - -using namespace std::literals; - -constexpr auto consume_hex_prefix(std::string_view s) - -> ConsumeHexPrefixResult { - if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { - s.remove_prefix(2); - return {true, s}; - } - return {false, s}; -} - -template -inline auto do_from_chars(std::string_view s) -> T { - T x; - auto [first, last] = pointer_range(s); - auto [ptr, ec] = std::from_chars(first, last, x, Param); - if (ec == std::errc()) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (ec == std::errc::invalid_argument) { - throw std::invalid_argument{"pattern not found"}; - } - if (ec == std::errc::result_out_of_range) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string_view s) -> T { - return do_from_chars(s); - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); - } - throw std::invalid_argument{"pattern not found"}; - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - auto [ok, rest] = consume_hex_prefix(s); - if (ok) { - return do_from_chars(rest); - } - if (starts_with("0"sv, s)) { - return do_from_chars(rest); - } - return do_from_chars(rest); - } -}; - -namespace { - -template inline const auto generic_strtod = nullptr; -template <> inline const auto generic_strtod = strtof; -template <> inline const auto generic_strtod = strtod; -template <> inline const auto generic_strtod = strtold; - -} // namespace - -template inline auto do_strtod(std::string const &s) -> T { - if (isspace(static_cast(s[0])) || s[0] == '+') { - throw std::invalid_argument{"pattern not found"}; - } - - auto [first, last] = pointer_range(s); - char *ptr; - - errno = 0; - auto x = generic_strtod(first, &ptr); - if (errno == 0) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (errno == ERANGE) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::general does not parse hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { - throw std::invalid_argument{"chars_format::hex parses hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::scientific does not parse hexfloat"}; - } - if (s.find_first_of("eE") == std::string::npos) { - throw std::invalid_argument{ - "chars_format::scientific requires exponent part"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::fixed does not parse hexfloat"}; - } - if (s.find_first_of("eE") != std::string::npos) { - throw std::invalid_argument{ - "chars_format::fixed does not parse exponent part"}; - } - - return do_strtod(s); - } -}; - -template -std::string join(StrIt first, StrIt last, const std::string &separator) { - if (first == last) { - return ""; - } - std::stringstream value; - value << *first; - ++first; - while (first != last) { - value << separator << *first; - ++first; - } - return value.str(); -} - -} // namespace details - -enum class nargs_pattern { optional, any, at_least_one }; - -enum class default_arguments : unsigned int { - none = 0, - help = 1, - version = 2, - all = help | version, -}; - -inline default_arguments operator&(const default_arguments &a, - const default_arguments &b) { - return static_cast( - static_cast::type>(a) & - static_cast::type>(b)); -} - -class ArgumentParser; - -class Argument { - friend class ArgumentParser; - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream &; - - template - explicit Argument(std::string_view prefix_chars, - std::array &&a, - std::index_sequence /*unused*/) - : m_accepts_optional_like_value(false), - m_is_optional((is_optional(a[I], prefix_chars) || ...)), - m_is_required(false), m_is_repeatable(false), m_is_used(false), - m_prefix_chars(prefix_chars) { - ((void)m_names.emplace_back(a[I]), ...); - std::sort( - m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { - return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); - }); - } - -public: - template - explicit Argument(std::string_view prefix_chars, - std::array &&a) - : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} - - Argument &help(std::string help_text) { - m_help = std::move(help_text); - return *this; - } - - Argument &metavar(std::string metavar) { - m_metavar = std::move(metavar); - return *this; - } - - template Argument &default_value(T &&value) { - m_default_value_repr = details::repr(value); - m_default_value = std::forward(value); - return *this; - } - - Argument &default_value(const char *value) { - return default_value(std::string(value)); - } - - Argument &required() { - m_is_required = true; - return *this; - } - - Argument &implicit_value(std::any value) { - m_implicit_value = std::move(value); - m_num_args_range = NArgsRange{0, 0}; - return *this; - } - - template - auto action(F &&callable, Args &&... bound_args) - -> std::enable_if_t, - Argument &> { - using action_type = std::conditional_t< - std::is_void_v>, - void_action, valued_action>; - if constexpr (sizeof...(Args) == 0) { - m_action.emplace(std::forward(callable)); - } else { - m_action.emplace( - [f = std::forward(callable), - tup = std::make_tuple(std::forward(bound_args)...)]( - std::string const &opt) mutable { - return details::apply_plus_one(f, tup, opt); - }); - } - return *this; - } - - auto &append() { - m_is_repeatable = true; - return *this; - } - - template - auto scan() -> std::enable_if_t, Argument &> { - static_assert(!(std::is_const_v || std::is_volatile_v), - "T should not be cv-qualified"); - auto is_one_of = [](char c, auto... x) constexpr { - return ((c == x) || ...); - }; - - if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'i') && - details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'u') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'o') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'x', 'X') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'a', 'A') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'e', 'E') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'f', 'F') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'g', 'G') && - std::is_floating_point_v) { - action(details::parse_number()); - } else { - static_assert(alignof(T) == 0, "No scan specification for T"); - } - - return *this; - } - - Argument &nargs(std::size_t num_args) { - m_num_args_range = NArgsRange{num_args, num_args}; - return *this; - } - - Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { - m_num_args_range = NArgsRange{num_args_min, num_args_max}; - return *this; - } - - Argument &nargs(nargs_pattern pattern) { - switch (pattern) { - case nargs_pattern::optional: - m_num_args_range = NArgsRange{0, 1}; - break; - case nargs_pattern::any: - m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; - break; - case nargs_pattern::at_least_one: - m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; - break; - } - return *this; - } - - Argument &remaining() { - m_accepts_optional_like_value = true; - return nargs(nargs_pattern::any); - } - - template - Iterator consume(Iterator start, Iterator end, - std::string_view used_name = {}) { - if (!m_is_repeatable && m_is_used) { - throw std::runtime_error("Duplicate argument"); - } - m_is_used = true; - m_used_name = used_name; - - const auto num_args_max = m_num_args_range.get_max(); - const auto num_args_min = m_num_args_range.get_min(); - std::size_t dist = 0; - if (num_args_max == 0) { - m_values.emplace_back(m_implicit_value); - std::visit([](const auto &f) { f({}); }, m_action); - return start; - } - if ((dist = static_cast(std::distance(start, end))) >= - num_args_min) { - if (num_args_max < dist) { - end = std::next(start, static_cast( - num_args_max)); - } - if (!m_accepts_optional_like_value) { - end = std::find_if( - start, end, - std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); - dist = static_cast(std::distance(start, end)); - if (dist < num_args_min) { - throw std::runtime_error("Too few arguments"); - } - } - - struct ActionApply { - void operator()(valued_action &f) { - std::transform(first, last, std::back_inserter(self.m_values), f); - } - - void operator()(void_action &f) { - std::for_each(first, last, f); - if (!self.m_default_value.has_value()) { - if (!self.m_accepts_optional_like_value) { - self.m_values.resize( - static_cast(std::distance(first, last))); - } - } - } - - Iterator first, last; - Argument &self; - }; - std::visit(ActionApply{start, end, *this}, m_action); - return end; - } - if (m_default_value.has_value()) { - return start; - } - throw std::runtime_error("Too few arguments for '" + - std::string(m_used_name) + "'."); - } - - /* - * @throws std::runtime_error if argument values are not valid - */ - void validate() const { - if (m_is_optional) { - // TODO: check if an implicit value was programmed for this argument - if (!m_is_used && !m_default_value.has_value() && m_is_required) { - throw_required_arg_not_used_error(); - } - if (m_is_used && m_is_required && m_values.empty()) { - throw_required_arg_no_value_provided_error(); - } - } else { - if (!m_num_args_range.contains(m_values.size()) && - !m_default_value.has_value()) { - throw_nargs_range_validation_error(); - } - } - } - - std::string get_inline_usage() const { - std::stringstream usage; - // Find the longest variant to show in the usage string - std::string longest_name = m_names.front(); - for (const auto &s : m_names) { - if (s.size() > longest_name.size()) { - longest_name = s; - } - } - if (!m_is_required) { - usage << "["; - } - usage << longest_name; - const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; - if (m_num_args_range.get_max() > 0) { - usage << " " << metavar; - if (m_num_args_range.get_max() > 1) { - usage << "..."; - } - } - if (!m_is_required) { - usage << "]"; - } - return usage.str(); - } - - std::size_t get_arguments_length() const { - - std::size_t names_size = std::accumulate( - std::begin(m_names), std::end(m_names), std::size_t(0), - [](const auto &sum, const auto &s) { return sum + s.size(); }); - - if (is_positional(m_names.front(), m_prefix_chars)) { - // A set metavar means this replaces the names - if (!m_metavar.empty()) { - // Indent and metavar - return 2 + m_metavar.size(); - } - - // Indent and space-separated - return 2 + names_size + (m_names.size() - 1); - } - // Is an option - include both names _and_ metavar - // size = text + (", " between names) - std::size_t size = names_size + 2 * (m_names.size() - 1); - if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { - size += m_metavar.size() + 1; - } - return size + 2; // indent - } - - friend std::ostream &operator<<(std::ostream &stream, - const Argument &argument) { - std::stringstream name_stream; - name_stream << " "; // indent - if (argument.is_positional(argument.m_names.front(), - argument.m_prefix_chars)) { - if (!argument.m_metavar.empty()) { - name_stream << argument.m_metavar; - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), " "); - } - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), ", "); - // If we have a metavar, and one narg - print the metavar - if (!argument.m_metavar.empty() && - argument.m_num_args_range == NArgsRange{1, 1}) { - name_stream << " " << argument.m_metavar; - } - } - stream << name_stream.str() << "\t" << argument.m_help; - - // print nargs spec - if (!argument.m_help.empty()) { - stream << " "; - } - stream << argument.m_num_args_range; - - if (argument.m_default_value.has_value() && - argument.m_num_args_range != NArgsRange{0, 0}) { - stream << "[default: " << argument.m_default_value_repr << "]"; - } else if (argument.m_is_required) { - stream << "[required]"; - } - stream << "\n"; - return stream; - } - - template bool operator!=(const T &rhs) const { - return !(*this == rhs); - } - - /* - * Compare to an argument value of known type - * @throws std::logic_error in case of incompatible types - */ - template bool operator==(const T &rhs) const { - if constexpr (!details::IsContainer) { - return get() == rhs; - } else { - using ValueType = typename T::value_type; - auto lhs = get(); - return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), - std::end(rhs), - [](const auto &a, const auto &b) { - return std::any_cast(a) == b; - }); - } - } - -private: - class NArgsRange { - std::size_t m_min; - std::size_t m_max; - - public: - NArgsRange(std::size_t minimum, std::size_t maximum) - : m_min(minimum), m_max(maximum) { - if (minimum > maximum) { - throw std::logic_error("Range of number of arguments is invalid"); - } - } - - bool contains(std::size_t value) const { - return value >= m_min && value <= m_max; - } - - bool is_exact() const { return m_min == m_max; } - - bool is_right_bounded() const { - return m_max < (std::numeric_limits::max)(); - } - - std::size_t get_min() const { return m_min; } - - std::size_t get_max() const { return m_max; } - - // Print help message - friend auto operator<<(std::ostream &stream, const NArgsRange &range) - -> std::ostream & { - if (range.m_min == range.m_max) { - if (range.m_min != 0 && range.m_min != 1) { - stream << "[nargs: " << range.m_min << "] "; - } - } else { - if (range.m_max == (std::numeric_limits::max)()) { - stream << "[nargs: " << range.m_min << " or more] "; - } else { - stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; - } - } - return stream; - } - - bool operator==(const NArgsRange &rhs) const { - return rhs.m_min == m_min && rhs.m_max == m_max; - } - - bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } - }; - - void throw_nargs_range_validation_error() const { - std::stringstream stream; - if (!m_used_name.empty()) { - stream << m_used_name << ": "; - } else { - stream << m_names.front() << ": "; - } - if (m_num_args_range.is_exact()) { - stream << m_num_args_range.get_min(); - } else if (m_num_args_range.is_right_bounded()) { - stream << m_num_args_range.get_min() << " to " - << m_num_args_range.get_max(); - } else { - stream << m_num_args_range.get_min() << " or more"; - } - stream << " argument(s) expected. " << m_values.size() << " provided."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_not_used_error() const { - std::stringstream stream; - stream << m_names.front() << ": required."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_no_value_provided_error() const { - std::stringstream stream; - stream << m_used_name << ": no value provided."; - throw std::runtime_error(stream.str()); - } - - static constexpr int eof = std::char_traits::eof(); - - static auto lookahead(std::string_view s) -> int { - if (s.empty()) { - return eof; - } - return static_cast(static_cast(s[0])); - } - - /* - * decimal-literal: - * '0' - * nonzero-digit digit-sequence_opt - * integer-part fractional-part - * fractional-part - * integer-part '.' exponent-part_opt - * integer-part exponent-part - * - * integer-part: - * digit-sequence - * - * fractional-part: - * '.' post-decimal-point - * - * post-decimal-point: - * digit-sequence exponent-part_opt - * - * exponent-part: - * 'e' post-e - * 'E' post-e - * - * post-e: - * sign_opt digit-sequence - * - * sign: one of - * '+' '-' - */ - static bool is_decimal_literal(std::string_view s) { - auto is_digit = [](auto c) constexpr { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; - } - }; - - // precondition: we have consumed or will consume at least one digit - auto consume_digits = [=](std::string_view sd) { - // NOLINTNEXTLINE(readability-qualified-auto) - auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); - return sd.substr(static_cast(it - std::begin(sd))); - }; - - switch (lookahead(s)) { - case '0': { - s.remove_prefix(1); - if (s.empty()) { - return true; - } - goto integer_part; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - s = consume_digits(s); - if (s.empty()) { - return true; - } - goto integer_part_consumed; - } - case '.': { - s.remove_prefix(1); - goto post_decimal_point; - } - default: - return false; - } - - integer_part: - s = consume_digits(s); - integer_part_consumed: - switch (lookahead(s)) { - case '.': { - s.remove_prefix(1); - if (is_digit(lookahead(s))) { - goto post_decimal_point; - } else { - goto exponent_part_opt; - } - } - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_decimal_point: - if (is_digit(lookahead(s))) { - s = consume_digits(s); - goto exponent_part_opt; - } - return false; - - exponent_part_opt: - switch (lookahead(s)) { - case eof: - return true; - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_e: - switch (lookahead(s)) { - case '-': - case '+': - s.remove_prefix(1); - } - if (is_digit(lookahead(s))) { - s = consume_digits(s); - return s.empty(); - } - return false; - } - - static bool is_optional(std::string_view name, - std::string_view prefix_chars) { - return !is_positional(name, prefix_chars); - } - - /* - * positional: - * _empty_ - * '-' - * '-' decimal-literal - * !'-' anything - */ - static bool is_positional(std::string_view name, - std::string_view prefix_chars) { - auto first = lookahead(name); - - if (first == eof) { - return true; - } else if (prefix_chars.find(static_cast(first)) != - std::string_view::npos) { - name.remove_prefix(1); - if (name.empty()) { - return true; - } - return is_decimal_literal(name); - } - return true; - } - - /* - * Get argument value given a type - * @throws std::logic_error in case of incompatible types - */ - template T get() const { - if (!m_values.empty()) { - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } else { - return std::any_cast(m_values.front()); - } - } - if (m_default_value.has_value()) { - return std::any_cast(m_default_value); - } - if constexpr (details::IsContainer) { - if (!m_accepts_optional_like_value) { - return any_cast_container(m_values); - } - } - - throw std::logic_error("No value provided for '" + m_names.back() + "'."); - } - - /* - * Get argument value given a type. - * @pre The object has no default value. - * @returns The stored value if any, std::nullopt otherwise. - */ - template auto present() const -> std::optional { - if (m_default_value.has_value()) { - throw std::logic_error("Argument with default value always presents"); - } - if (m_values.empty()) { - return std::nullopt; - } - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } - return std::any_cast(m_values.front()); - } - - template - static auto any_cast_container(const std::vector &operand) -> T { - using ValueType = typename T::value_type; - - T result; - std::transform( - std::begin(operand), std::end(operand), std::back_inserter(result), - [](const auto &value) { return std::any_cast(value); }); - return result; - } - - std::vector m_names; - std::string_view m_used_name; - std::string m_help; - std::string m_metavar; - std::any m_default_value; - std::string m_default_value_repr; - std::any m_implicit_value; - using valued_action = std::function; - using void_action = std::function; - std::variant m_action{ - std::in_place_type, - [](const std::string &value) { return value; }}; - std::vector m_values; - NArgsRange m_num_args_range{1, 1}; - // Bit field of bool values. Set default value in ctor. - bool m_accepts_optional_like_value : 1; - bool m_is_optional : 1; - bool m_is_required : 1; - bool m_is_repeatable : 1; - bool m_is_used : 1; - std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars -}; - -class ArgumentParser { -public: - explicit ArgumentParser(std::string program_name = {}, - std::string version = "1.0", - default_arguments add_args = default_arguments::all, - bool exit_on_default_arguments = true) - : m_program_name(std::move(program_name)), m_version(std::move(version)), - m_exit_on_default_arguments(exit_on_default_arguments), - m_parser_path(m_program_name) { - if ((add_args & default_arguments::help) == default_arguments::help) { - add_argument("-h", "--help") - .action([&](const auto & /*unused*/) { - std::cout << help().str(); - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("shows help message and exits") - .implicit_value(true) - .nargs(0); - } - if ((add_args & default_arguments::version) == default_arguments::version) { - add_argument("-v", "--version") - .action([&](const auto & /*unused*/) { - std::cout << m_version << std::endl; - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("prints version information and exits") - .implicit_value(true) - .nargs(0); - } - } - - ArgumentParser(ArgumentParser &&) noexcept = default; - ArgumentParser &operator=(ArgumentParser &&) = default; - - ArgumentParser(const ArgumentParser &other) - : m_program_name(other.m_program_name), m_version(other.m_version), - m_description(other.m_description), m_epilog(other.m_epilog), - m_prefix_chars(other.m_prefix_chars), - m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed), - m_positional_arguments(other.m_positional_arguments), - m_optional_arguments(other.m_optional_arguments), - m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) { - for (auto it = std::begin(m_positional_arguments); - it != std::end(m_positional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_optional_arguments); - it != std::end(m_optional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); - ++it) { - m_subparser_map.insert_or_assign(it->get().m_program_name, it); - m_subparser_used.insert_or_assign(it->get().m_program_name, false); - } - } - - ~ArgumentParser() = default; - - ArgumentParser &operator=(const ArgumentParser &other) { - auto tmp = other; - std::swap(*this, tmp); - return *this; - } - - explicit operator bool() const { - auto arg_used = std::any_of(m_argument_map.cbegin(), - m_argument_map.cend(), - [](auto &it) { - return it.second->m_is_used; - }); - auto subparser_used = std::any_of(m_subparser_used.cbegin(), - m_subparser_used.cend(), - [](auto &it) { - return it.second; - }); - - return m_is_parsed && (arg_used || subparser_used); - } - - // Parameter packing - // Call add_argument with variadic number of string arguments - template Argument &add_argument(Targs... f_args) { - using array_of_sv = std::array; - auto argument = - m_optional_arguments.emplace(std::cend(m_optional_arguments), - m_prefix_chars, array_of_sv{f_args...}); - - if (!argument->m_is_optional) { - m_positional_arguments.splice(std::cend(m_positional_arguments), - m_optional_arguments, argument); - } - - index_argument(argument); - return *argument; - } - - // Parameter packed add_parents method - // Accepts a variadic number of ArgumentParser objects - template - ArgumentParser &add_parents(const Targs &... f_args) { - for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { - for (const auto &argument : parent_parser.m_positional_arguments) { - auto it = m_positional_arguments.insert( - std::cend(m_positional_arguments), argument); - index_argument(it); - } - for (const auto &argument : parent_parser.m_optional_arguments) { - auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), - argument); - index_argument(it); - } - } - return *this; - } - - ArgumentParser &add_description(std::string description) { - m_description = std::move(description); - return *this; - } - - ArgumentParser &add_epilog(std::string epilog) { - m_epilog = std::move(epilog); - return *this; - } - - /* Getter for arguments and subparsers. - * @throws std::logic_error in case of an invalid argument or subparser name - */ - template - T& at(std::string_view name) { - if constexpr (std::is_same_v) { - return (*this)[name]; - } else { - auto subparser_it = m_subparser_map.find(name); - if (subparser_it != m_subparser_map.end()) { - return subparser_it->second->get(); - } - throw std::logic_error("No such subparser: " + std::string(name)); - } - } - - ArgumentParser &set_prefix_chars(std::string prefix_chars) { - m_prefix_chars = std::move(prefix_chars); - return *this; - } - - ArgumentParser &set_assign_chars(std::string assign_chars) { - m_assign_chars = std::move(assign_chars); - return *this; - } - - /* Call parse_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args(const std::vector &arguments) { - parse_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - } - - /* Call parse_known_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - std::vector - parse_known_args(const std::vector &arguments) { - auto unknown_arguments = parse_known_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - return unknown_arguments; - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - void parse_args(int argc, const char *const argv[]) { - parse_args({argv, argv + argc}); - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - auto parse_known_args(int argc, const char *const argv[]) { - return parse_known_args({argv, argv + argc}); - } - - /* Getter for options with default values. - * @throws std::logic_error if parse_args() has not been previously called - * @throws std::logic_error if there is no such option - * @throws std::logic_error if the option has no value - * @throws std::bad_any_cast if the option is not of type T - */ - template T get(std::string_view arg_name) const { - if (!m_is_parsed) { - throw std::logic_error("Nothing parsed, no arguments are available."); - } - return (*this)[arg_name].get(); - } - - /* Getter for options without default values. - * @pre The option has no default value. - * @throws std::logic_error if there is no such option - * @throws std::bad_any_cast if the option is not of type T - */ - template - auto present(std::string_view arg_name) const -> std::optional { - return (*this)[arg_name].present(); - } - - /* Getter that returns true for user-supplied options. Returns false if not - * user-supplied, even with a default value. - */ - auto is_used(std::string_view arg_name) const { - return (*this)[arg_name].m_is_used; - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(std::string_view subcommand_name) const { - return m_subparser_used.at(subcommand_name); - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(const ArgumentParser &subparser) const { - return is_subcommand_used(subparser.m_program_name); - } - - /* Indexing operator. Return a reference to an Argument object - * Used in conjuction with Argument.operator== e.g., parser["foo"] == true - * @throws std::logic_error in case of an invalid argument name - */ - Argument &operator[](std::string_view arg_name) const { - auto it = m_argument_map.find(arg_name); - if (it != m_argument_map.end()) { - return *(it->second); - } - if (!is_valid_prefix_char(arg_name.front())) { - std::string name(arg_name); - const auto legal_prefix_char = get_any_valid_prefix_char(); - const auto prefix = std::string(1, legal_prefix_char); - - // "-" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - // "--" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - } - throw std::logic_error("No such argument: " + std::string(arg_name)); - } - - // Print help message - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream & { - stream.setf(std::ios_base::left); - - auto longest_arg_length = parser.get_length_of_longest_argument(); - - stream << parser.usage() << "\n\n"; - - if (!parser.m_description.empty()) { - stream << parser.m_description << "\n\n"; - } - - if (!parser.m_positional_arguments.empty()) { - stream << "Positional arguments:\n"; - } - - for (const auto &argument : parser.m_positional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_optional_arguments.empty()) { - stream << (parser.m_positional_arguments.empty() ? "" : "\n") - << "Optional arguments:\n"; - } - - for (const auto &argument : parser.m_optional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_subparser_map.empty()) { - stream << (parser.m_positional_arguments.empty() - ? (parser.m_optional_arguments.empty() ? "" : "\n") - : "\n") - << "Subcommands:\n"; - for (const auto &[command, subparser] : parser.m_subparser_map) { - stream << std::setw(2) << " "; - stream << std::setw(static_cast(longest_arg_length - 2)) - << command; - stream << " " << subparser->get().m_description << "\n"; - } - } - - if (!parser.m_epilog.empty()) { - stream << '\n'; - stream << parser.m_epilog << "\n\n"; - } - - return stream; - } - - // Format help message - auto help() const -> std::stringstream { - std::stringstream out; - out << *this; - return out; - } - - // Format usage part of help only - auto usage() const -> std::string { - std::stringstream stream; - - stream << "Usage: " << this->m_program_name; - - // Add any options inline here - for (const auto &argument : this->m_optional_arguments) { - stream << " " << argument.get_inline_usage(); - } - // Put positional arguments after the optionals - for (const auto &argument : this->m_positional_arguments) { - if (!argument.m_metavar.empty()) { - stream << " " << argument.m_metavar; - } else { - stream << " " << argument.m_names.front(); - } - } - // Put subcommands after positional arguments - if (!m_subparser_map.empty()) { - stream << " {"; - std::size_t i{0}; - for (const auto &[command, unused] : m_subparser_map) { - if (i == 0) { - stream << command; - } else { - stream << "," << command; - } - ++i; - } - stream << "}"; - } - - return stream.str(); - } - - // Printing the one and only help message - // I've stuck with a simple message format, nothing fancy. - [[deprecated("Use cout << program; instead. See also help().")]] std::string - print_help() const { - auto out = help(); - std::cout << out.rdbuf(); - return out.str(); - } - - void add_subparser(ArgumentParser &parser) { - parser.m_parser_path = m_program_name + " " + parser.m_program_name; - auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); - m_subparser_map.insert_or_assign(parser.m_program_name, it); - m_subparser_used.insert_or_assign(parser.m_program_name, false); - } - -private: - bool is_valid_prefix_char(char c) const { - return m_prefix_chars.find(c) != std::string::npos; - } - - char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } - - /* - * Pre-process this argument list. Anything starting with "--", that - * contains an =, where the prefix before the = has an entry in the - * options table, should be split. - */ - std::vector - preprocess_arguments(const std::vector &raw_arguments) const { - std::vector arguments{}; - for (const auto &arg : raw_arguments) { - - const auto argument_starts_with_prefix_chars = - [this](const std::string &a) -> bool { - if (!a.empty()) { - - const auto legal_prefix = [this](char c) -> bool { - return m_prefix_chars.find(c) != std::string::npos; - }; - - // Windows-style - // if '/' is a legal prefix char - // then allow single '/' followed by argument name, followed by an - // assign char, e.g., ':' e.g., 'test.exe /A:Foo' - const auto windows_style = legal_prefix('/'); - - if (windows_style) { - if (legal_prefix(a[0])) { - return true; - } - } else { - // Slash '/' is not a legal prefix char - // For all other characters, only support long arguments - // i.e., the argument must start with 2 prefix chars, e.g, - // '--foo' e,g, './test --foo=Bar -DARG=yes' - if (a.size() > 1) { - return (legal_prefix(a[0]) && legal_prefix(a[1])); - } - } - } - return false; - }; - - // Check that: - // - We don't have an argument named exactly this - // - The argument starts with a prefix char, e.g., "--" - // - The argument contains an assign char, e.g., "=" - auto assign_char_pos = arg.find_first_of(m_assign_chars); - - if (m_argument_map.find(arg) == m_argument_map.end() && - argument_starts_with_prefix_chars(arg) && - assign_char_pos != std::string::npos) { - // Get the name of the potential option, and check it exists - std::string opt_name = arg.substr(0, assign_char_pos); - if (m_argument_map.find(opt_name) != m_argument_map.end()) { - // This is the name of an option! Split it into two parts - arguments.push_back(std::move(opt_name)); - arguments.push_back(arg.substr(assign_char_pos + 1)); - continue; - } - } - // If we've fallen through to here, then it's a standard argument - arguments.push_back(arg); - } - return arguments; - } - - /* - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_args( - unprocessed_arguments); - } - - throw std::runtime_error( - "Maximum number of positional arguments exceeded"); - } - auto argument = positional_argument_it++; - it = argument->consume(it, end); - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - m_is_parsed = true; - } - - /* - * Like parse_args_internal but collects unused args into a vector - */ - std::vector - parse_known_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - - std::vector unknown_arguments{}; - - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_known_args_internal( - unprocessed_arguments); - } - - // save current argument as unknown and go to next argument - unknown_arguments.push_back(current_argument); - ++it; - } else { - // current argument is the value of a positional argument - // consume it - auto argument = positional_argument_it++; - it = argument->consume(it, end); - } - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - unknown_arguments.push_back(current_argument); - break; - } - } - } else { - // current argument is an optional-like argument that is unknown - // save it and move to next argument - unknown_arguments.push_back(current_argument); - ++it; - } - } - m_is_parsed = true; - return unknown_arguments; - } - - // Used by print_help. - std::size_t get_length_of_longest_argument() const { - if (m_argument_map.empty()) { - return 0; - } - std::size_t max_size = 0; - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - max_size = std::max(max_size, argument->get_arguments_length()); - } - for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { - max_size = std::max(max_size, command.size()); - } - return max_size; - } - - using argument_it = std::list::iterator; - using argument_parser_it = - std::list>::iterator; - - void index_argument(argument_it it) { - for (const auto &name : std::as_const(it->m_names)) { - m_argument_map.insert_or_assign(name, it); - } - } - - std::string m_program_name; - std::string m_version; - std::string m_description; - std::string m_epilog; - bool m_exit_on_default_arguments = true; - std::string m_prefix_chars{"-"}; - std::string m_assign_chars{"="}; - bool m_is_parsed = false; - std::list m_positional_arguments; - std::list m_optional_arguments; - std::map m_argument_map; - std::string m_parser_path; - std::list> m_subparsers; - std::map m_subparser_map; - std::map m_subparser_used; -}; - -} // namespace argparse diff --git a/src/CLI/include/argument_exception.hpp b/src/CLI/include/argument_exception.hpp deleted file mode 100644 index 4627b0b11..000000000 --- a/src/CLI/include/argument_exception.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ARGUMENT_EXCEPTION -#define ARGUMENT_EXCEPTION - -#include -#include - -class ArgumentException: public std::exception { -private: -public: - std::string message; - ArgumentException(std::string msg) : message(msg) { - this->message = msg; - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/backend.hpp b/src/CLI/include/backend.hpp deleted file mode 100644 index 4e755cb65..000000000 --- a/src/CLI/include/backend.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef BACKEND_GUARD -#define BACKEND_GUARD - -#include -#include -#include -#include -#include -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" - - -/** - * Check to see if a file exists on the system. - * Technically this will return false if it exists but you don't have permission. -*/ -inline bool file_exists(std::string fname) { - std::fstream f(fname.c_str()); - return f.good(); -} - -inline std::string absolute_fp(std::string fname) { - return std::string(std::filesystem::absolute(fname)); -} - -inline bool can_exec(const std::filesystem::path& fp) { - auto entry = std::filesystem::directory_entry(fp); - return ( - entry.exists() - && - std::filesystem::perms::none != (entry.status().permissions() & std::filesystem::perms::owner_exec) - ); -} - -static std::vector& get_paths() { - std::string path = std::getenv("PATH"); - static std::vector result; - if (result.size() > 0) return result; - result.push_back(std::filesystem::canonical("/proc/self/exe").parent_path()); - result.push_back(std::filesystem::path(".")); - - size_t i = 0; - // Windows uses ";" as a delimeter, but we don't support windows yet. - while ((i = path.find(":")) != std::string::npos) { - result.push_back(std::filesystem::path(path.substr(0, i))); - path.erase(0, i + 1); - } - return result; -} - -struct FierroBackend { - std::string name = ""; - std::shared_ptr command; - std::optional exec_path{}; - FierroBackend(std::string name): name(name) { - exec_path = find(); - } - - std::optional find() { - return this->find(get_paths()); - } - - std::optional find(const std::vector& paths) { - for(const auto & path : paths) { - auto potential_path = path / this->name; - if (can_exec(potential_path)) return {potential_path}; - } - return {}; - } - - virtual int invoke() = 0; -}; -#endif \ No newline at end of file diff --git a/src/CLI/src/main.cpp b/src/CLI/src/main.cpp deleted file mode 100644 index c7cd3d018..000000000 --- a/src/CLI/src/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "backend.hpp" -#include "FierroParallelBackends.hpp" -#include "MeshBuilderBackend.hpp" -#include "VoxelizerBackend.hpp" -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" -#include -#include -#include -#include -#include - -std::vector> BACKENDS { - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared(), -}; - -std::vector> find_fierro_backends() { - auto found = std::vector>(); - for(auto backend : BACKENDS) { - if(backend->exec_path.has_value()) found.push_back(backend); - } - - return found; -} - -/** - * Execute a function with a particular working directory. - * - * @param dir The working directory to execute the function in. - * If the directoy doesn't exist, it will be created. - * Will also create parent directories if necessary. - * @param f The function to execute. - * - * @return The return value of `f` if there is one. Else void. -*/ -template -T with_curdir(std::string dir, std::function f) { - T val; - with_curdir(dir, [&]() -> void { val = f(); }); - return val; -} - -/** - * `with_curdir` template specialization for void functions. -*/ -template<> -void with_curdir(std::string dir, std::function f) { - auto old_path = std::filesystem::current_path(); - - dir = std::filesystem::absolute(dir); - if (!std::filesystem::exists(dir)) std::filesystem::create_directories(dir); - - std::filesystem::current_path(dir); - f(); - std::filesystem::current_path(old_path); -} - -int main(int argc, char** argv) { - argparse::ArgumentParser parser("fierro"); - parser.add_description(""); - parser.add_argument("-o", "--output") - .help("output directory for the program") - .default_value("."); - - auto backends = find_fierro_backends(); - for(auto backend : backends) { - parser.add_subparser(*backend->command); - } - - try { - parser.parse_args(argc, argv); - } catch(const std::runtime_error& e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - std::exit(1); - } - - for(auto backend : backends) { - if (parser.is_subcommand_used(*backend->command)) { - return with_curdir( - parser.get("output"), - [&]() { - try { - return backend->invoke(); - } catch (ArgumentException e) { - std::cerr << e.message << std::endl; - std::cerr << parser; - std::exit(1); - } - } - ); - } - } - - // If they didn't select anything, give them some help. - std::cout << parser << std::endl; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8758ebda9..78e716178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_subdirectory(Yaml-Serializable) add_subdirectory(Mesh-Builder) add_subdirectory(Voxelizer) -add_subdirectory(CLI) + if(BUILD_IMPLICIT_SOLVER OR BUILD_PARALLEL_EXPLICIT_SOLVER) add_subdirectory(Parallel-Solvers) From 50d64b6f60dbeff19efd422f1fda3c4be6ba4564 Mon Sep 17 00:00:00 2001 From: Jacob Moore Date: Fri, 12 Apr 2024 09:16:20 -0500 Subject: [PATCH 2/7] ENH: Removing CLI --- src/CLI/CMakeLists.txt | 14 - src/CLI/README.md | 168 -- src/CLI/include/FierroParallelBackends.hpp | 80 - src/CLI/include/MeshBuilderBackend.hpp | 83 - src/CLI/include/VoxelizerBackend.hpp | 84 - src/CLI/include/argparse/argparse.hpp | 1698 -------------------- src/CLI/include/argument_exception.hpp | 15 - src/CLI/include/backend.hpp | 73 - src/CLI/src/main.cpp | 100 -- src/CMakeLists.txt | 2 +- 10 files changed, 1 insertion(+), 2316 deletions(-) delete mode 100644 src/CLI/CMakeLists.txt delete mode 100644 src/CLI/README.md delete mode 100644 src/CLI/include/FierroParallelBackends.hpp delete mode 100644 src/CLI/include/MeshBuilderBackend.hpp delete mode 100644 src/CLI/include/VoxelizerBackend.hpp delete mode 100644 src/CLI/include/argparse/argparse.hpp delete mode 100644 src/CLI/include/argument_exception.hpp delete mode 100644 src/CLI/include/backend.hpp delete mode 100644 src/CLI/src/main.cpp diff --git a/src/CLI/CMakeLists.txt b/src/CLI/CMakeLists.txt deleted file mode 100644 index 70e0fefa3..000000000 --- a/src/CLI/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.17) -project(fierro-cli) - -set(CMAKE_CXX_STANDARD 17) -add_executable(fierro src/main.cpp) -target_include_directories(fierro PUBLIC include) - -# HIPCC is a driver for Clang, and Clang wants you to -# link against lstdc++fs for version 15. -if (NOT APPLE) - target_link_options(fierro PRIVATE -lstdc++fs) -endif() - -install(TARGETS fierro) diff --git a/src/CLI/README.md b/src/CLI/README.md deleted file mode 100644 index 9fb134b7e..000000000 --- a/src/CLI/README.md +++ /dev/null @@ -1,168 +0,0 @@ -# Fiero Command Line Interface -This CLI package is a Fierro command line interface that is intended to act as a consolidated front-end for our Fierro backend tools. It provides a consistent interface and framework for exposing tools to the user. - -## Front-End -The front end here is built off of [argparse](https://github.com/p-ranav/argparse), which implements a language agnostic standard for simple command line interface design. It defines a syntax for programs from the command line with suitable helptext/defaults/post-processing. - -The Fierro-CLI implements two things: -1. Output directory -- A directory to run the program in. Outputs of the program will by default be put here if some other backend specific option is not specified. -2. Subcommands -- a list of argparse "subcommands" that each represent an available backend tool. - -Example: -``` -$ fierro -h - -Usage: fierro [--help] [--version] [--output VAR] {mesh-builder,parallel-explicit,parallel-implicit,voxelizer} - -Optional arguments: - -h, --help shows help message and exits - -v, --version prints version information and exits - -o, --output output directory for the program [default: "."] - -Subcommands: - mesh-builder Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems. - parallel-explicit Use an explicit solution scheme to step the system. - parallel-implicit Use an implicit solution scheme to step the system. - voxelizer Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. Note: Only supports binary STL mesh inputs -``` - -## Backends -This Fierro-CLI package is designed to operate decoupled from backends. Each registered backend is an executable that may or may not be present in the system. - -### Registering Backends -To add a new backend, you only need to do a couple of steps. -#### 1. Implementing NewBackend.hpp -You should start by copying one of the existing backend.hpp files and implementing your new backend. We will use `VoxelizerBackend.hpp` as an example. - -You should derive from `FierroBackend` and tell the base class which executable name to look for -```c++ -#include "backend.hpp" -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - ... - } - ... -}; -``` - -Then, in the constructor, setup your argparse command - -```c++ - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") // <--- This is the name that shows up under "Subcommands:" in the CLI - ); - - // Here you can add your arguments with their helptext. - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); // <-- You can also add a post-processing command. In this case we convert the input filepath to an absolute one. - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - - // Add a description for your subcommand - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - } - ... -}; -``` - -Lastly you tell the CLI framework how to actually invoke the backend command. This is mostly just building a system command string and invoking it, but I suppose you could do whatever you want. -```c++ - -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; -``` - -You probably also want to add some validation to your input arguments. The voxelizer does this with a serparate function: - -```c++ -struct VoxelizerBackend: public FierroBackend { - ... - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - // Here we just check if the input file is an actual file. - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - ... -}; -``` - -Now you have a well defined backend. - - -#### 2. Plug it into the CLI framework -Head over to main.cpp and add your backend to the list of backends: -```c++ -std::vector> BACKENDS { - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - std::shared_ptr(), - //std::shared_ptr() <-- Add yours here -}; - -``` - -Now you have everything set up, and it will automatically be picked up if its compiled and in the system. \ No newline at end of file diff --git a/src/CLI/include/FierroParallelBackends.hpp b/src/CLI/include/FierroParallelBackends.hpp deleted file mode 100644 index 0926f7b0e..000000000 --- a/src/CLI/include/FierroParallelBackends.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef FIERRO_PARALLEL_BACKENDS -#define FIERRO_PARALLEL_BACKENDS -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct ParallelBackend: public FierroBackend { - ParallelBackend(std::string name) - : FierroBackend(name) { } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return std::nullopt; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string input_file = this->command->get("config"); - std::string sys_command = this->exec_path.value().string() + " " + input_file; - - return system(sys_command.c_str()); - } - - void add_common_options() { - this->command->add_argument("config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - } -}; - -struct ParallelExplicit: public ParallelBackend { - ParallelExplicit() : ParallelBackend("fierro-parallel-explicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-explicit") - ); - this->add_common_options(); - this->command->add_description("Use an explicit solution scheme to step the system."); - } -}; - -struct ParallelImplicit: public ParallelBackend { - ParallelImplicit() : ParallelBackend("fierro-parallel-implicit") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("parallel-implicit") - ); - this->add_common_options(); - this->command->add_description("Use an implicit solution scheme to step the system."); - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/MeshBuilderBackend.hpp b/src/CLI/include/MeshBuilderBackend.hpp deleted file mode 100644 index 22f2e217d..000000000 --- a/src/CLI/include/MeshBuilderBackend.hpp +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef MESH_BUILDER_BACKENDS_H -#define MESH_BUILDER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct MeshBuilderBackend: public FierroBackend { - MeshBuilderBackend() : FierroBackend("fierro-mesh-builder") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("mesh-builder") - ); - - this->command->add_argument("-c", "config") - .help("The `.yaml` configuration to run fierro with.`") - .action(absolute_fp); - this->command->add_argument("--box-help") - .help("Set this flag to print out an example box mesh configuration file.") - .default_value(false) - .implicit_value(true); - this->command->add_argument("--cylinder-help") - .help("Set this flag to print out an example cylinder mesh configuration file.") - .default_value(false) - .implicit_value(true);; - this->command->add_description("Build rectangular or cylindrical mesh in 2 or 3 dimensions. Useful for setting up test problems."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - if (this->command->is_used("box-help") || this->command->is_used("cylinder-help")) - return {}; - - if (!parser->is_used("config")) - msg = "A value for config is required"; - else if (!file_exists(parser->get("config"))) { - msg = "Unable to find configuration: " + parser->get("config"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - if (this->command->get("box-help")) - sys_command = this->exec_path.value().string() + " Box"; - else if (this->command->get("cylinder-help")) - sys_command = this->exec_path.value().string() + " Cylinder"; - else { - std::string input_file = this->command->get("config"); - sys_command = this->exec_path.value().string() + " " + input_file; - } - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/VoxelizerBackend.hpp b/src/CLI/include/VoxelizerBackend.hpp deleted file mode 100644 index 63ca7c769..000000000 --- a/src/CLI/include/VoxelizerBackend.hpp +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef VOXELIZER_BACKENDS_H -#define VOXELIZER_BACKENDS_H - -#include "backend.hpp" -#include "argparse/argparse.hpp" -#include -#include - -struct VoxelizerBackend: public FierroBackend { - VoxelizerBackend() : FierroBackend("fierro-voxelizer") { - this->command = std::shared_ptr( - new argparse::ArgumentParser("voxelizer") - ); - - this->command->add_argument("input-file") - .help("The binary STL file to voxelize.") - .action(absolute_fp); - this->command->add_argument("output-name") - .help("The name of the VTK structured mesh output file."); - this->command->add_argument("x") - .help("The number of voxels along the X direction (int)."); - this->command->add_argument("y") - .help("The number of voxels along the Y direction (int)."); - this->command->add_argument("z") - .help("The number of voxels along the Z direction (int)."); - this->command->add_description( - "Take a mesh defined by an STL and turn it into a dense voxel grid represented by a VTK structured mesh. " - "The extent of the grid is selected by taking the minimum and maximum extent of the mesh along each dimenison. " - "Note: Only supports binary STL mesh inputs." - ); - this->command->add_argument("use-index-space") - .help("Wether or not to output the VTK in index-space coordinates."); - } - - /** - * Check that the arguments of the CLI are valid and make sense. - * - * Checks for explicit/implicit logical consistency. - * Checks that the mesh file is real and readable. - * Does not check that it is correctly formatted. - * - * @return An optional error. If the empty, the arguments are valid. - * - */ - std::optional validate_arguments() { - std::string msg = ""; - auto parser = this->command; - - if (!file_exists(parser->get("input-file"))) { - msg = "Unable to find input file: " + parser->get("input-file"); - } - - if (msg.length() > 0) { - return std::optional(msg); - } - return {}; - } - - /** - * Invoke the backend executable. - * Will throw an ArgumentException if the arguments aren't valid. - * - * @return Return code of the executable. - */ - int invoke() { - if (!exec_path.has_value()) - throw std::runtime_error("Cannot invoke executable without absolute path."); - auto err = this->validate_arguments(); - if (err.has_value()) throw ArgumentException(err.value()); - - std::string sys_command; - sys_command = this->exec_path.value().string() - + " " + this->command->get("input-file") - + " " + this->command->get("output-name") - + " " + this->command->get("x") - + " " + this->command->get("y") - + " " + this->command->get("z") - + " " + this->command->get("use-index-space"); - - return system(sys_command.c_str()); - } -}; - -#endif \ No newline at end of file diff --git a/src/CLI/include/argparse/argparse.hpp b/src/CLI/include/argparse/argparse.hpp deleted file mode 100644 index 633f7820d..000000000 --- a/src/CLI/include/argparse/argparse.hpp +++ /dev/null @@ -1,1698 +0,0 @@ -/* - __ _ _ __ __ _ _ __ __ _ _ __ ___ ___ - / _` | '__/ _` | '_ \ / _` | '__/ __|/ _ \ Argument Parser for Modern C++ -| (_| | | | (_| | |_) | (_| | | \__ \ __/ http://github.com/p-ranav/argparse - \__,_|_| \__, | .__/ \__,_|_| |___/\___| - |___/|_| - -Licensed under the MIT License . -SPDX-License-Identifier: MIT -Copyright (c) 2019-2022 Pranav Srinivas Kumar -and other contributors. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace argparse { - -namespace details { // namespace for helper methods - -template -struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template <> struct HasContainerTraits : std::false_type {}; - -template -struct HasContainerTraits< - T, std::void_t().begin()), - decltype(std::declval().end()), - decltype(std::declval().size())>> : std::true_type {}; - -template -static constexpr bool IsContainer = HasContainerTraits::value; - -template -struct HasStreamableTraits : std::false_type {}; - -template -struct HasStreamableTraits< - T, - std::void_t() << std::declval())>> - : std::true_type {}; - -template -static constexpr bool IsStreamable = HasStreamableTraits::value; - -constexpr std::size_t repr_max_container_size = 5; - -template std::string repr(T const &val) { - if constexpr (std::is_same_v) { - return val ? "true" : "false"; - } else if constexpr (std::is_convertible_v) { - return '"' + std::string{std::string_view{val}} + '"'; - } else if constexpr (IsContainer) { - std::stringstream out; - out << "{"; - const auto size = val.size(); - if (size > 1) { - out << repr(*val.begin()); - std::for_each( - std::next(val.begin()), - std::next( - val.begin(), - static_cast( - std::min(size, repr_max_container_size) - 1)), - [&out](const auto &v) { out << " " << repr(v); }); - if (size <= repr_max_container_size) { - out << " "; - } else { - out << "..."; - } - } - if (size > 0) { - out << repr(*std::prev(val.end())); - } - out << "}"; - return out.str(); - } else if constexpr (IsStreamable) { - std::stringstream out; - out << val; - return out.str(); - } else { - return ""; - } -} - -namespace { - -template constexpr bool standard_signed_integer = false; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; -template <> constexpr bool standard_signed_integer = true; - -template constexpr bool standard_unsigned_integer = false; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> constexpr bool standard_unsigned_integer = true; -template <> -constexpr bool standard_unsigned_integer = true; - -} // namespace - -constexpr int radix_8 = 8; -constexpr int radix_10 = 10; -constexpr int radix_16 = 16; - -template -constexpr bool standard_integer = - standard_signed_integer || standard_unsigned_integer; - -template -constexpr decltype(auto) -apply_plus_one_impl(F &&f, Tuple &&t, Extra &&x, - std::index_sequence /*unused*/) { - return std::invoke(std::forward(f), std::get(std::forward(t))..., - std::forward(x)); -} - -template -constexpr decltype(auto) apply_plus_one(F &&f, Tuple &&t, Extra &&x) { - return details::apply_plus_one_impl( - std::forward(f), std::forward(t), std::forward(x), - std::make_index_sequence< - std::tuple_size_v>>{}); -} - -constexpr auto pointer_range(std::string_view s) noexcept { - return std::tuple(s.data(), s.data() + s.size()); -} - -template -constexpr bool starts_with(std::basic_string_view prefix, - std::basic_string_view s) noexcept { - return s.substr(0, prefix.size()) == prefix; -} - -enum class chars_format { - scientific = 0x1, - fixed = 0x2, - hex = 0x4, - general = fixed | scientific -}; - -struct ConsumeHexPrefixResult { - bool is_hexadecimal; - std::string_view rest; -}; - -using namespace std::literals; - -constexpr auto consume_hex_prefix(std::string_view s) - -> ConsumeHexPrefixResult { - if (starts_with("0x"sv, s) || starts_with("0X"sv, s)) { - s.remove_prefix(2); - return {true, s}; - } - return {false, s}; -} - -template -inline auto do_from_chars(std::string_view s) -> T { - T x; - auto [first, last] = pointer_range(s); - auto [ptr, ec] = std::from_chars(first, last, x, Param); - if (ec == std::errc()) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (ec == std::errc::invalid_argument) { - throw std::invalid_argument{"pattern not found"}; - } - if (ec == std::errc::result_out_of_range) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string_view s) -> T { - return do_from_chars(s); - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - if (auto [ok, rest] = consume_hex_prefix(s); ok) { - return do_from_chars(rest); - } - throw std::invalid_argument{"pattern not found"}; - } -}; - -template struct parse_number { - auto operator()(std::string_view s) -> T { - auto [ok, rest] = consume_hex_prefix(s); - if (ok) { - return do_from_chars(rest); - } - if (starts_with("0"sv, s)) { - return do_from_chars(rest); - } - return do_from_chars(rest); - } -}; - -namespace { - -template inline const auto generic_strtod = nullptr; -template <> inline const auto generic_strtod = strtof; -template <> inline const auto generic_strtod = strtod; -template <> inline const auto generic_strtod = strtold; - -} // namespace - -template inline auto do_strtod(std::string const &s) -> T { - if (isspace(static_cast(s[0])) || s[0] == '+') { - throw std::invalid_argument{"pattern not found"}; - } - - auto [first, last] = pointer_range(s); - char *ptr; - - errno = 0; - auto x = generic_strtod(first, &ptr); - if (errno == 0) { - if (ptr == last) { - return x; - } - throw std::invalid_argument{"pattern does not match to the end"}; - } - if (errno == ERANGE) { - throw std::range_error{"not representable"}; - } - return x; // unreachable -} - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::general does not parse hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); !r.is_hexadecimal) { - throw std::invalid_argument{"chars_format::hex parses hexfloat"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::scientific does not parse hexfloat"}; - } - if (s.find_first_of("eE") == std::string::npos) { - throw std::invalid_argument{ - "chars_format::scientific requires exponent part"}; - } - - return do_strtod(s); - } -}; - -template struct parse_number { - auto operator()(std::string const &s) -> T { - if (auto r = consume_hex_prefix(s); r.is_hexadecimal) { - throw std::invalid_argument{ - "chars_format::fixed does not parse hexfloat"}; - } - if (s.find_first_of("eE") != std::string::npos) { - throw std::invalid_argument{ - "chars_format::fixed does not parse exponent part"}; - } - - return do_strtod(s); - } -}; - -template -std::string join(StrIt first, StrIt last, const std::string &separator) { - if (first == last) { - return ""; - } - std::stringstream value; - value << *first; - ++first; - while (first != last) { - value << separator << *first; - ++first; - } - return value.str(); -} - -} // namespace details - -enum class nargs_pattern { optional, any, at_least_one }; - -enum class default_arguments : unsigned int { - none = 0, - help = 1, - version = 2, - all = help | version, -}; - -inline default_arguments operator&(const default_arguments &a, - const default_arguments &b) { - return static_cast( - static_cast::type>(a) & - static_cast::type>(b)); -} - -class ArgumentParser; - -class Argument { - friend class ArgumentParser; - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream &; - - template - explicit Argument(std::string_view prefix_chars, - std::array &&a, - std::index_sequence /*unused*/) - : m_accepts_optional_like_value(false), - m_is_optional((is_optional(a[I], prefix_chars) || ...)), - m_is_required(false), m_is_repeatable(false), m_is_used(false), - m_prefix_chars(prefix_chars) { - ((void)m_names.emplace_back(a[I]), ...); - std::sort( - m_names.begin(), m_names.end(), [](const auto &lhs, const auto &rhs) { - return lhs.size() == rhs.size() ? lhs < rhs : lhs.size() < rhs.size(); - }); - } - -public: - template - explicit Argument(std::string_view prefix_chars, - std::array &&a) - : Argument(prefix_chars, std::move(a), std::make_index_sequence{}) {} - - Argument &help(std::string help_text) { - m_help = std::move(help_text); - return *this; - } - - Argument &metavar(std::string metavar) { - m_metavar = std::move(metavar); - return *this; - } - - template Argument &default_value(T &&value) { - m_default_value_repr = details::repr(value); - m_default_value = std::forward(value); - return *this; - } - - Argument &default_value(const char *value) { - return default_value(std::string(value)); - } - - Argument &required() { - m_is_required = true; - return *this; - } - - Argument &implicit_value(std::any value) { - m_implicit_value = std::move(value); - m_num_args_range = NArgsRange{0, 0}; - return *this; - } - - template - auto action(F &&callable, Args &&... bound_args) - -> std::enable_if_t, - Argument &> { - using action_type = std::conditional_t< - std::is_void_v>, - void_action, valued_action>; - if constexpr (sizeof...(Args) == 0) { - m_action.emplace(std::forward(callable)); - } else { - m_action.emplace( - [f = std::forward(callable), - tup = std::make_tuple(std::forward(bound_args)...)]( - std::string const &opt) mutable { - return details::apply_plus_one(f, tup, opt); - }); - } - return *this; - } - - auto &append() { - m_is_repeatable = true; - return *this; - } - - template - auto scan() -> std::enable_if_t, Argument &> { - static_assert(!(std::is_const_v || std::is_volatile_v), - "T should not be cv-qualified"); - auto is_one_of = [](char c, auto... x) constexpr { - return ((c == x) || ...); - }; - - if constexpr (is_one_of(Shape, 'd') && details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'i') && - details::standard_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'u') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'o') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'x', 'X') && - details::standard_unsigned_integer) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'a', 'A') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'e', 'E') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'f', 'F') && - std::is_floating_point_v) { - action(details::parse_number()); - } else if constexpr (is_one_of(Shape, 'g', 'G') && - std::is_floating_point_v) { - action(details::parse_number()); - } else { - static_assert(alignof(T) == 0, "No scan specification for T"); - } - - return *this; - } - - Argument &nargs(std::size_t num_args) { - m_num_args_range = NArgsRange{num_args, num_args}; - return *this; - } - - Argument &nargs(std::size_t num_args_min, std::size_t num_args_max) { - m_num_args_range = NArgsRange{num_args_min, num_args_max}; - return *this; - } - - Argument &nargs(nargs_pattern pattern) { - switch (pattern) { - case nargs_pattern::optional: - m_num_args_range = NArgsRange{0, 1}; - break; - case nargs_pattern::any: - m_num_args_range = NArgsRange{0, (std::numeric_limits::max)()}; - break; - case nargs_pattern::at_least_one: - m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()}; - break; - } - return *this; - } - - Argument &remaining() { - m_accepts_optional_like_value = true; - return nargs(nargs_pattern::any); - } - - template - Iterator consume(Iterator start, Iterator end, - std::string_view used_name = {}) { - if (!m_is_repeatable && m_is_used) { - throw std::runtime_error("Duplicate argument"); - } - m_is_used = true; - m_used_name = used_name; - - const auto num_args_max = m_num_args_range.get_max(); - const auto num_args_min = m_num_args_range.get_min(); - std::size_t dist = 0; - if (num_args_max == 0) { - m_values.emplace_back(m_implicit_value); - std::visit([](const auto &f) { f({}); }, m_action); - return start; - } - if ((dist = static_cast(std::distance(start, end))) >= - num_args_min) { - if (num_args_max < dist) { - end = std::next(start, static_cast( - num_args_max)); - } - if (!m_accepts_optional_like_value) { - end = std::find_if( - start, end, - std::bind(is_optional, std::placeholders::_1, m_prefix_chars)); - dist = static_cast(std::distance(start, end)); - if (dist < num_args_min) { - throw std::runtime_error("Too few arguments"); - } - } - - struct ActionApply { - void operator()(valued_action &f) { - std::transform(first, last, std::back_inserter(self.m_values), f); - } - - void operator()(void_action &f) { - std::for_each(first, last, f); - if (!self.m_default_value.has_value()) { - if (!self.m_accepts_optional_like_value) { - self.m_values.resize( - static_cast(std::distance(first, last))); - } - } - } - - Iterator first, last; - Argument &self; - }; - std::visit(ActionApply{start, end, *this}, m_action); - return end; - } - if (m_default_value.has_value()) { - return start; - } - throw std::runtime_error("Too few arguments for '" + - std::string(m_used_name) + "'."); - } - - /* - * @throws std::runtime_error if argument values are not valid - */ - void validate() const { - if (m_is_optional) { - // TODO: check if an implicit value was programmed for this argument - if (!m_is_used && !m_default_value.has_value() && m_is_required) { - throw_required_arg_not_used_error(); - } - if (m_is_used && m_is_required && m_values.empty()) { - throw_required_arg_no_value_provided_error(); - } - } else { - if (!m_num_args_range.contains(m_values.size()) && - !m_default_value.has_value()) { - throw_nargs_range_validation_error(); - } - } - } - - std::string get_inline_usage() const { - std::stringstream usage; - // Find the longest variant to show in the usage string - std::string longest_name = m_names.front(); - for (const auto &s : m_names) { - if (s.size() > longest_name.size()) { - longest_name = s; - } - } - if (!m_is_required) { - usage << "["; - } - usage << longest_name; - const std::string metavar = !m_metavar.empty() ? m_metavar : "VAR"; - if (m_num_args_range.get_max() > 0) { - usage << " " << metavar; - if (m_num_args_range.get_max() > 1) { - usage << "..."; - } - } - if (!m_is_required) { - usage << "]"; - } - return usage.str(); - } - - std::size_t get_arguments_length() const { - - std::size_t names_size = std::accumulate( - std::begin(m_names), std::end(m_names), std::size_t(0), - [](const auto &sum, const auto &s) { return sum + s.size(); }); - - if (is_positional(m_names.front(), m_prefix_chars)) { - // A set metavar means this replaces the names - if (!m_metavar.empty()) { - // Indent and metavar - return 2 + m_metavar.size(); - } - - // Indent and space-separated - return 2 + names_size + (m_names.size() - 1); - } - // Is an option - include both names _and_ metavar - // size = text + (", " between names) - std::size_t size = names_size + 2 * (m_names.size() - 1); - if (!m_metavar.empty() && m_num_args_range == NArgsRange{1, 1}) { - size += m_metavar.size() + 1; - } - return size + 2; // indent - } - - friend std::ostream &operator<<(std::ostream &stream, - const Argument &argument) { - std::stringstream name_stream; - name_stream << " "; // indent - if (argument.is_positional(argument.m_names.front(), - argument.m_prefix_chars)) { - if (!argument.m_metavar.empty()) { - name_stream << argument.m_metavar; - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), " "); - } - } else { - name_stream << details::join(argument.m_names.begin(), - argument.m_names.end(), ", "); - // If we have a metavar, and one narg - print the metavar - if (!argument.m_metavar.empty() && - argument.m_num_args_range == NArgsRange{1, 1}) { - name_stream << " " << argument.m_metavar; - } - } - stream << name_stream.str() << "\t" << argument.m_help; - - // print nargs spec - if (!argument.m_help.empty()) { - stream << " "; - } - stream << argument.m_num_args_range; - - if (argument.m_default_value.has_value() && - argument.m_num_args_range != NArgsRange{0, 0}) { - stream << "[default: " << argument.m_default_value_repr << "]"; - } else if (argument.m_is_required) { - stream << "[required]"; - } - stream << "\n"; - return stream; - } - - template bool operator!=(const T &rhs) const { - return !(*this == rhs); - } - - /* - * Compare to an argument value of known type - * @throws std::logic_error in case of incompatible types - */ - template bool operator==(const T &rhs) const { - if constexpr (!details::IsContainer) { - return get() == rhs; - } else { - using ValueType = typename T::value_type; - auto lhs = get(); - return std::equal(std::begin(lhs), std::end(lhs), std::begin(rhs), - std::end(rhs), - [](const auto &a, const auto &b) { - return std::any_cast(a) == b; - }); - } - } - -private: - class NArgsRange { - std::size_t m_min; - std::size_t m_max; - - public: - NArgsRange(std::size_t minimum, std::size_t maximum) - : m_min(minimum), m_max(maximum) { - if (minimum > maximum) { - throw std::logic_error("Range of number of arguments is invalid"); - } - } - - bool contains(std::size_t value) const { - return value >= m_min && value <= m_max; - } - - bool is_exact() const { return m_min == m_max; } - - bool is_right_bounded() const { - return m_max < (std::numeric_limits::max)(); - } - - std::size_t get_min() const { return m_min; } - - std::size_t get_max() const { return m_max; } - - // Print help message - friend auto operator<<(std::ostream &stream, const NArgsRange &range) - -> std::ostream & { - if (range.m_min == range.m_max) { - if (range.m_min != 0 && range.m_min != 1) { - stream << "[nargs: " << range.m_min << "] "; - } - } else { - if (range.m_max == (std::numeric_limits::max)()) { - stream << "[nargs: " << range.m_min << " or more] "; - } else { - stream << "[nargs=" << range.m_min << ".." << range.m_max << "] "; - } - } - return stream; - } - - bool operator==(const NArgsRange &rhs) const { - return rhs.m_min == m_min && rhs.m_max == m_max; - } - - bool operator!=(const NArgsRange &rhs) const { return !(*this == rhs); } - }; - - void throw_nargs_range_validation_error() const { - std::stringstream stream; - if (!m_used_name.empty()) { - stream << m_used_name << ": "; - } else { - stream << m_names.front() << ": "; - } - if (m_num_args_range.is_exact()) { - stream << m_num_args_range.get_min(); - } else if (m_num_args_range.is_right_bounded()) { - stream << m_num_args_range.get_min() << " to " - << m_num_args_range.get_max(); - } else { - stream << m_num_args_range.get_min() << " or more"; - } - stream << " argument(s) expected. " << m_values.size() << " provided."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_not_used_error() const { - std::stringstream stream; - stream << m_names.front() << ": required."; - throw std::runtime_error(stream.str()); - } - - void throw_required_arg_no_value_provided_error() const { - std::stringstream stream; - stream << m_used_name << ": no value provided."; - throw std::runtime_error(stream.str()); - } - - static constexpr int eof = std::char_traits::eof(); - - static auto lookahead(std::string_view s) -> int { - if (s.empty()) { - return eof; - } - return static_cast(static_cast(s[0])); - } - - /* - * decimal-literal: - * '0' - * nonzero-digit digit-sequence_opt - * integer-part fractional-part - * fractional-part - * integer-part '.' exponent-part_opt - * integer-part exponent-part - * - * integer-part: - * digit-sequence - * - * fractional-part: - * '.' post-decimal-point - * - * post-decimal-point: - * digit-sequence exponent-part_opt - * - * exponent-part: - * 'e' post-e - * 'E' post-e - * - * post-e: - * sign_opt digit-sequence - * - * sign: one of - * '+' '-' - */ - static bool is_decimal_literal(std::string_view s) { - auto is_digit = [](auto c) constexpr { - switch (c) { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return true; - default: - return false; - } - }; - - // precondition: we have consumed or will consume at least one digit - auto consume_digits = [=](std::string_view sd) { - // NOLINTNEXTLINE(readability-qualified-auto) - auto it = std::find_if_not(std::begin(sd), std::end(sd), is_digit); - return sd.substr(static_cast(it - std::begin(sd))); - }; - - switch (lookahead(s)) { - case '0': { - s.remove_prefix(1); - if (s.empty()) { - return true; - } - goto integer_part; - } - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': { - s = consume_digits(s); - if (s.empty()) { - return true; - } - goto integer_part_consumed; - } - case '.': { - s.remove_prefix(1); - goto post_decimal_point; - } - default: - return false; - } - - integer_part: - s = consume_digits(s); - integer_part_consumed: - switch (lookahead(s)) { - case '.': { - s.remove_prefix(1); - if (is_digit(lookahead(s))) { - goto post_decimal_point; - } else { - goto exponent_part_opt; - } - } - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_decimal_point: - if (is_digit(lookahead(s))) { - s = consume_digits(s); - goto exponent_part_opt; - } - return false; - - exponent_part_opt: - switch (lookahead(s)) { - case eof: - return true; - case 'e': - case 'E': { - s.remove_prefix(1); - goto post_e; - } - default: - return false; - } - - post_e: - switch (lookahead(s)) { - case '-': - case '+': - s.remove_prefix(1); - } - if (is_digit(lookahead(s))) { - s = consume_digits(s); - return s.empty(); - } - return false; - } - - static bool is_optional(std::string_view name, - std::string_view prefix_chars) { - return !is_positional(name, prefix_chars); - } - - /* - * positional: - * _empty_ - * '-' - * '-' decimal-literal - * !'-' anything - */ - static bool is_positional(std::string_view name, - std::string_view prefix_chars) { - auto first = lookahead(name); - - if (first == eof) { - return true; - } else if (prefix_chars.find(static_cast(first)) != - std::string_view::npos) { - name.remove_prefix(1); - if (name.empty()) { - return true; - } - return is_decimal_literal(name); - } - return true; - } - - /* - * Get argument value given a type - * @throws std::logic_error in case of incompatible types - */ - template T get() const { - if (!m_values.empty()) { - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } else { - return std::any_cast(m_values.front()); - } - } - if (m_default_value.has_value()) { - return std::any_cast(m_default_value); - } - if constexpr (details::IsContainer) { - if (!m_accepts_optional_like_value) { - return any_cast_container(m_values); - } - } - - throw std::logic_error("No value provided for '" + m_names.back() + "'."); - } - - /* - * Get argument value given a type. - * @pre The object has no default value. - * @returns The stored value if any, std::nullopt otherwise. - */ - template auto present() const -> std::optional { - if (m_default_value.has_value()) { - throw std::logic_error("Argument with default value always presents"); - } - if (m_values.empty()) { - return std::nullopt; - } - if constexpr (details::IsContainer) { - return any_cast_container(m_values); - } - return std::any_cast(m_values.front()); - } - - template - static auto any_cast_container(const std::vector &operand) -> T { - using ValueType = typename T::value_type; - - T result; - std::transform( - std::begin(operand), std::end(operand), std::back_inserter(result), - [](const auto &value) { return std::any_cast(value); }); - return result; - } - - std::vector m_names; - std::string_view m_used_name; - std::string m_help; - std::string m_metavar; - std::any m_default_value; - std::string m_default_value_repr; - std::any m_implicit_value; - using valued_action = std::function; - using void_action = std::function; - std::variant m_action{ - std::in_place_type, - [](const std::string &value) { return value; }}; - std::vector m_values; - NArgsRange m_num_args_range{1, 1}; - // Bit field of bool values. Set default value in ctor. - bool m_accepts_optional_like_value : 1; - bool m_is_optional : 1; - bool m_is_required : 1; - bool m_is_repeatable : 1; - bool m_is_used : 1; - std::string_view m_prefix_chars; // ArgumentParser has the prefix_chars -}; - -class ArgumentParser { -public: - explicit ArgumentParser(std::string program_name = {}, - std::string version = "1.0", - default_arguments add_args = default_arguments::all, - bool exit_on_default_arguments = true) - : m_program_name(std::move(program_name)), m_version(std::move(version)), - m_exit_on_default_arguments(exit_on_default_arguments), - m_parser_path(m_program_name) { - if ((add_args & default_arguments::help) == default_arguments::help) { - add_argument("-h", "--help") - .action([&](const auto & /*unused*/) { - std::cout << help().str(); - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("shows help message and exits") - .implicit_value(true) - .nargs(0); - } - if ((add_args & default_arguments::version) == default_arguments::version) { - add_argument("-v", "--version") - .action([&](const auto & /*unused*/) { - std::cout << m_version << std::endl; - if (m_exit_on_default_arguments) { - std::exit(0); - } - }) - .default_value(false) - .help("prints version information and exits") - .implicit_value(true) - .nargs(0); - } - } - - ArgumentParser(ArgumentParser &&) noexcept = default; - ArgumentParser &operator=(ArgumentParser &&) = default; - - ArgumentParser(const ArgumentParser &other) - : m_program_name(other.m_program_name), m_version(other.m_version), - m_description(other.m_description), m_epilog(other.m_epilog), - m_prefix_chars(other.m_prefix_chars), - m_assign_chars(other.m_assign_chars), m_is_parsed(other.m_is_parsed), - m_positional_arguments(other.m_positional_arguments), - m_optional_arguments(other.m_optional_arguments), - m_parser_path(other.m_parser_path), m_subparsers(other.m_subparsers) { - for (auto it = std::begin(m_positional_arguments); - it != std::end(m_positional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_optional_arguments); - it != std::end(m_optional_arguments); ++it) { - index_argument(it); - } - for (auto it = std::begin(m_subparsers); it != std::end(m_subparsers); - ++it) { - m_subparser_map.insert_or_assign(it->get().m_program_name, it); - m_subparser_used.insert_or_assign(it->get().m_program_name, false); - } - } - - ~ArgumentParser() = default; - - ArgumentParser &operator=(const ArgumentParser &other) { - auto tmp = other; - std::swap(*this, tmp); - return *this; - } - - explicit operator bool() const { - auto arg_used = std::any_of(m_argument_map.cbegin(), - m_argument_map.cend(), - [](auto &it) { - return it.second->m_is_used; - }); - auto subparser_used = std::any_of(m_subparser_used.cbegin(), - m_subparser_used.cend(), - [](auto &it) { - return it.second; - }); - - return m_is_parsed && (arg_used || subparser_used); - } - - // Parameter packing - // Call add_argument with variadic number of string arguments - template Argument &add_argument(Targs... f_args) { - using array_of_sv = std::array; - auto argument = - m_optional_arguments.emplace(std::cend(m_optional_arguments), - m_prefix_chars, array_of_sv{f_args...}); - - if (!argument->m_is_optional) { - m_positional_arguments.splice(std::cend(m_positional_arguments), - m_optional_arguments, argument); - } - - index_argument(argument); - return *argument; - } - - // Parameter packed add_parents method - // Accepts a variadic number of ArgumentParser objects - template - ArgumentParser &add_parents(const Targs &... f_args) { - for (const ArgumentParser &parent_parser : {std::ref(f_args)...}) { - for (const auto &argument : parent_parser.m_positional_arguments) { - auto it = m_positional_arguments.insert( - std::cend(m_positional_arguments), argument); - index_argument(it); - } - for (const auto &argument : parent_parser.m_optional_arguments) { - auto it = m_optional_arguments.insert(std::cend(m_optional_arguments), - argument); - index_argument(it); - } - } - return *this; - } - - ArgumentParser &add_description(std::string description) { - m_description = std::move(description); - return *this; - } - - ArgumentParser &add_epilog(std::string epilog) { - m_epilog = std::move(epilog); - return *this; - } - - /* Getter for arguments and subparsers. - * @throws std::logic_error in case of an invalid argument or subparser name - */ - template - T& at(std::string_view name) { - if constexpr (std::is_same_v) { - return (*this)[name]; - } else { - auto subparser_it = m_subparser_map.find(name); - if (subparser_it != m_subparser_map.end()) { - return subparser_it->second->get(); - } - throw std::logic_error("No such subparser: " + std::string(name)); - } - } - - ArgumentParser &set_prefix_chars(std::string prefix_chars) { - m_prefix_chars = std::move(prefix_chars); - return *this; - } - - ArgumentParser &set_assign_chars(std::string assign_chars) { - m_assign_chars = std::move(assign_chars); - return *this; - } - - /* Call parse_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args(const std::vector &arguments) { - parse_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - } - - /* Call parse_known_args_internal - which does all the work - * Then, validate the parsed arguments - * This variant is used mainly for testing - * @throws std::runtime_error in case of any invalid argument - */ - std::vector - parse_known_args(const std::vector &arguments) { - auto unknown_arguments = parse_known_args_internal(arguments); - // Check if all arguments are parsed - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - argument->validate(); - } - return unknown_arguments; - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - void parse_args(int argc, const char *const argv[]) { - parse_args({argv, argv + argc}); - } - - /* Main entry point for parsing command-line arguments using this - * ArgumentParser - * @throws std::runtime_error in case of any invalid argument - */ - // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays) - auto parse_known_args(int argc, const char *const argv[]) { - return parse_known_args({argv, argv + argc}); - } - - /* Getter for options with default values. - * @throws std::logic_error if parse_args() has not been previously called - * @throws std::logic_error if there is no such option - * @throws std::logic_error if the option has no value - * @throws std::bad_any_cast if the option is not of type T - */ - template T get(std::string_view arg_name) const { - if (!m_is_parsed) { - throw std::logic_error("Nothing parsed, no arguments are available."); - } - return (*this)[arg_name].get(); - } - - /* Getter for options without default values. - * @pre The option has no default value. - * @throws std::logic_error if there is no such option - * @throws std::bad_any_cast if the option is not of type T - */ - template - auto present(std::string_view arg_name) const -> std::optional { - return (*this)[arg_name].present(); - } - - /* Getter that returns true for user-supplied options. Returns false if not - * user-supplied, even with a default value. - */ - auto is_used(std::string_view arg_name) const { - return (*this)[arg_name].m_is_used; - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(std::string_view subcommand_name) const { - return m_subparser_used.at(subcommand_name); - } - - /* Getter that returns true if a subcommand is used. - */ - auto is_subcommand_used(const ArgumentParser &subparser) const { - return is_subcommand_used(subparser.m_program_name); - } - - /* Indexing operator. Return a reference to an Argument object - * Used in conjuction with Argument.operator== e.g., parser["foo"] == true - * @throws std::logic_error in case of an invalid argument name - */ - Argument &operator[](std::string_view arg_name) const { - auto it = m_argument_map.find(arg_name); - if (it != m_argument_map.end()) { - return *(it->second); - } - if (!is_valid_prefix_char(arg_name.front())) { - std::string name(arg_name); - const auto legal_prefix_char = get_any_valid_prefix_char(); - const auto prefix = std::string(1, legal_prefix_char); - - // "-" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - // "--" + arg_name - name = prefix + name; - it = m_argument_map.find(name); - if (it != m_argument_map.end()) { - return *(it->second); - } - } - throw std::logic_error("No such argument: " + std::string(arg_name)); - } - - // Print help message - friend auto operator<<(std::ostream &stream, const ArgumentParser &parser) - -> std::ostream & { - stream.setf(std::ios_base::left); - - auto longest_arg_length = parser.get_length_of_longest_argument(); - - stream << parser.usage() << "\n\n"; - - if (!parser.m_description.empty()) { - stream << parser.m_description << "\n\n"; - } - - if (!parser.m_positional_arguments.empty()) { - stream << "Positional arguments:\n"; - } - - for (const auto &argument : parser.m_positional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_optional_arguments.empty()) { - stream << (parser.m_positional_arguments.empty() ? "" : "\n") - << "Optional arguments:\n"; - } - - for (const auto &argument : parser.m_optional_arguments) { - stream.width(static_cast(longest_arg_length)); - stream << argument; - } - - if (!parser.m_subparser_map.empty()) { - stream << (parser.m_positional_arguments.empty() - ? (parser.m_optional_arguments.empty() ? "" : "\n") - : "\n") - << "Subcommands:\n"; - for (const auto &[command, subparser] : parser.m_subparser_map) { - stream << std::setw(2) << " "; - stream << std::setw(static_cast(longest_arg_length - 2)) - << command; - stream << " " << subparser->get().m_description << "\n"; - } - } - - if (!parser.m_epilog.empty()) { - stream << '\n'; - stream << parser.m_epilog << "\n\n"; - } - - return stream; - } - - // Format help message - auto help() const -> std::stringstream { - std::stringstream out; - out << *this; - return out; - } - - // Format usage part of help only - auto usage() const -> std::string { - std::stringstream stream; - - stream << "Usage: " << this->m_program_name; - - // Add any options inline here - for (const auto &argument : this->m_optional_arguments) { - stream << " " << argument.get_inline_usage(); - } - // Put positional arguments after the optionals - for (const auto &argument : this->m_positional_arguments) { - if (!argument.m_metavar.empty()) { - stream << " " << argument.m_metavar; - } else { - stream << " " << argument.m_names.front(); - } - } - // Put subcommands after positional arguments - if (!m_subparser_map.empty()) { - stream << " {"; - std::size_t i{0}; - for (const auto &[command, unused] : m_subparser_map) { - if (i == 0) { - stream << command; - } else { - stream << "," << command; - } - ++i; - } - stream << "}"; - } - - return stream.str(); - } - - // Printing the one and only help message - // I've stuck with a simple message format, nothing fancy. - [[deprecated("Use cout << program; instead. See also help().")]] std::string - print_help() const { - auto out = help(); - std::cout << out.rdbuf(); - return out.str(); - } - - void add_subparser(ArgumentParser &parser) { - parser.m_parser_path = m_program_name + " " + parser.m_program_name; - auto it = m_subparsers.emplace(std::cend(m_subparsers), parser); - m_subparser_map.insert_or_assign(parser.m_program_name, it); - m_subparser_used.insert_or_assign(parser.m_program_name, false); - } - -private: - bool is_valid_prefix_char(char c) const { - return m_prefix_chars.find(c) != std::string::npos; - } - - char get_any_valid_prefix_char() const { return m_prefix_chars[0]; } - - /* - * Pre-process this argument list. Anything starting with "--", that - * contains an =, where the prefix before the = has an entry in the - * options table, should be split. - */ - std::vector - preprocess_arguments(const std::vector &raw_arguments) const { - std::vector arguments{}; - for (const auto &arg : raw_arguments) { - - const auto argument_starts_with_prefix_chars = - [this](const std::string &a) -> bool { - if (!a.empty()) { - - const auto legal_prefix = [this](char c) -> bool { - return m_prefix_chars.find(c) != std::string::npos; - }; - - // Windows-style - // if '/' is a legal prefix char - // then allow single '/' followed by argument name, followed by an - // assign char, e.g., ':' e.g., 'test.exe /A:Foo' - const auto windows_style = legal_prefix('/'); - - if (windows_style) { - if (legal_prefix(a[0])) { - return true; - } - } else { - // Slash '/' is not a legal prefix char - // For all other characters, only support long arguments - // i.e., the argument must start with 2 prefix chars, e.g, - // '--foo' e,g, './test --foo=Bar -DARG=yes' - if (a.size() > 1) { - return (legal_prefix(a[0]) && legal_prefix(a[1])); - } - } - } - return false; - }; - - // Check that: - // - We don't have an argument named exactly this - // - The argument starts with a prefix char, e.g., "--" - // - The argument contains an assign char, e.g., "=" - auto assign_char_pos = arg.find_first_of(m_assign_chars); - - if (m_argument_map.find(arg) == m_argument_map.end() && - argument_starts_with_prefix_chars(arg) && - assign_char_pos != std::string::npos) { - // Get the name of the potential option, and check it exists - std::string opt_name = arg.substr(0, assign_char_pos); - if (m_argument_map.find(opt_name) != m_argument_map.end()) { - // This is the name of an option! Split it into two parts - arguments.push_back(std::move(opt_name)); - arguments.push_back(arg.substr(assign_char_pos + 1)); - continue; - } - } - // If we've fallen through to here, then it's a standard argument - arguments.push_back(arg); - } - return arguments; - } - - /* - * @throws std::runtime_error in case of any invalid argument - */ - void parse_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_args( - unprocessed_arguments); - } - - throw std::runtime_error( - "Maximum number of positional arguments exceeded"); - } - auto argument = positional_argument_it++; - it = argument->consume(it, end); - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - } else { - throw std::runtime_error("Unknown argument: " + current_argument); - } - } - m_is_parsed = true; - } - - /* - * Like parse_args_internal but collects unused args into a vector - */ - std::vector - parse_known_args_internal(const std::vector &raw_arguments) { - auto arguments = preprocess_arguments(raw_arguments); - - std::vector unknown_arguments{}; - - if (m_program_name.empty() && !arguments.empty()) { - m_program_name = arguments.front(); - } - auto end = std::end(arguments); - auto positional_argument_it = std::begin(m_positional_arguments); - for (auto it = std::next(std::begin(arguments)); it != end;) { - const auto ¤t_argument = *it; - if (Argument::is_positional(current_argument, m_prefix_chars)) { - if (positional_argument_it == std::end(m_positional_arguments)) { - - std::string_view maybe_command = current_argument; - - // Check sub-parsers - auto subparser_it = m_subparser_map.find(maybe_command); - if (subparser_it != m_subparser_map.end()) { - - // build list of remaining args - const auto unprocessed_arguments = - std::vector(it, end); - - // invoke subparser - m_is_parsed = true; - m_subparser_used[maybe_command] = true; - return subparser_it->second->get().parse_known_args_internal( - unprocessed_arguments); - } - - // save current argument as unknown and go to next argument - unknown_arguments.push_back(current_argument); - ++it; - } else { - // current argument is the value of a positional argument - // consume it - auto argument = positional_argument_it++; - it = argument->consume(it, end); - } - continue; - } - - auto arg_map_it = m_argument_map.find(current_argument); - if (arg_map_it != m_argument_map.end()) { - auto argument = arg_map_it->second; - it = argument->consume(std::next(it), end, arg_map_it->first); - } else if (const auto &compound_arg = current_argument; - compound_arg.size() > 1 && - is_valid_prefix_char(compound_arg[0]) && - !is_valid_prefix_char(compound_arg[1])) { - ++it; - for (std::size_t j = 1; j < compound_arg.size(); j++) { - auto hypothetical_arg = std::string{'-', compound_arg[j]}; - auto arg_map_it2 = m_argument_map.find(hypothetical_arg); - if (arg_map_it2 != m_argument_map.end()) { - auto argument = arg_map_it2->second; - it = argument->consume(it, end, arg_map_it2->first); - } else { - unknown_arguments.push_back(current_argument); - break; - } - } - } else { - // current argument is an optional-like argument that is unknown - // save it and move to next argument - unknown_arguments.push_back(current_argument); - ++it; - } - } - m_is_parsed = true; - return unknown_arguments; - } - - // Used by print_help. - std::size_t get_length_of_longest_argument() const { - if (m_argument_map.empty()) { - return 0; - } - std::size_t max_size = 0; - for ([[maybe_unused]] const auto &[unused, argument] : m_argument_map) { - max_size = std::max(max_size, argument->get_arguments_length()); - } - for ([[maybe_unused]] const auto &[command, unused] : m_subparser_map) { - max_size = std::max(max_size, command.size()); - } - return max_size; - } - - using argument_it = std::list::iterator; - using argument_parser_it = - std::list>::iterator; - - void index_argument(argument_it it) { - for (const auto &name : std::as_const(it->m_names)) { - m_argument_map.insert_or_assign(name, it); - } - } - - std::string m_program_name; - std::string m_version; - std::string m_description; - std::string m_epilog; - bool m_exit_on_default_arguments = true; - std::string m_prefix_chars{"-"}; - std::string m_assign_chars{"="}; - bool m_is_parsed = false; - std::list m_positional_arguments; - std::list m_optional_arguments; - std::map m_argument_map; - std::string m_parser_path; - std::list> m_subparsers; - std::map m_subparser_map; - std::map m_subparser_used; -}; - -} // namespace argparse diff --git a/src/CLI/include/argument_exception.hpp b/src/CLI/include/argument_exception.hpp deleted file mode 100644 index 4627b0b11..000000000 --- a/src/CLI/include/argument_exception.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ARGUMENT_EXCEPTION -#define ARGUMENT_EXCEPTION - -#include -#include - -class ArgumentException: public std::exception { -private: -public: - std::string message; - ArgumentException(std::string msg) : message(msg) { - this->message = msg; - } -}; -#endif \ No newline at end of file diff --git a/src/CLI/include/backend.hpp b/src/CLI/include/backend.hpp deleted file mode 100644 index 4e755cb65..000000000 --- a/src/CLI/include/backend.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef BACKEND_GUARD -#define BACKEND_GUARD - -#include -#include -#include -#include -#include -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" - - -/** - * Check to see if a file exists on the system. - * Technically this will return false if it exists but you don't have permission. -*/ -inline bool file_exists(std::string fname) { - std::fstream f(fname.c_str()); - return f.good(); -} - -inline std::string absolute_fp(std::string fname) { - return std::string(std::filesystem::absolute(fname)); -} - -inline bool can_exec(const std::filesystem::path& fp) { - auto entry = std::filesystem::directory_entry(fp); - return ( - entry.exists() - && - std::filesystem::perms::none != (entry.status().permissions() & std::filesystem::perms::owner_exec) - ); -} - -static std::vector& get_paths() { - std::string path = std::getenv("PATH"); - static std::vector result; - if (result.size() > 0) return result; - result.push_back(std::filesystem::canonical("/proc/self/exe").parent_path()); - result.push_back(std::filesystem::path(".")); - - size_t i = 0; - // Windows uses ";" as a delimeter, but we don't support windows yet. - while ((i = path.find(":")) != std::string::npos) { - result.push_back(std::filesystem::path(path.substr(0, i))); - path.erase(0, i + 1); - } - return result; -} - -struct FierroBackend { - std::string name = ""; - std::shared_ptr command; - std::optional exec_path{}; - FierroBackend(std::string name): name(name) { - exec_path = find(); - } - - std::optional find() { - return this->find(get_paths()); - } - - std::optional find(const std::vector& paths) { - for(const auto & path : paths) { - auto potential_path = path / this->name; - if (can_exec(potential_path)) return {potential_path}; - } - return {}; - } - - virtual int invoke() = 0; -}; -#endif \ No newline at end of file diff --git a/src/CLI/src/main.cpp b/src/CLI/src/main.cpp deleted file mode 100644 index c7cd3d018..000000000 --- a/src/CLI/src/main.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "backend.hpp" -#include "FierroParallelBackends.hpp" -#include "MeshBuilderBackend.hpp" -#include "VoxelizerBackend.hpp" -#include "argparse/argparse.hpp" -#include "argument_exception.hpp" -#include -#include -#include -#include -#include - -std::vector> BACKENDS { - std::make_shared(), - std::make_shared(), - std::make_shared(), - std::make_shared(), -}; - -std::vector> find_fierro_backends() { - auto found = std::vector>(); - for(auto backend : BACKENDS) { - if(backend->exec_path.has_value()) found.push_back(backend); - } - - return found; -} - -/** - * Execute a function with a particular working directory. - * - * @param dir The working directory to execute the function in. - * If the directoy doesn't exist, it will be created. - * Will also create parent directories if necessary. - * @param f The function to execute. - * - * @return The return value of `f` if there is one. Else void. -*/ -template -T with_curdir(std::string dir, std::function f) { - T val; - with_curdir(dir, [&]() -> void { val = f(); }); - return val; -} - -/** - * `with_curdir` template specialization for void functions. -*/ -template<> -void with_curdir(std::string dir, std::function f) { - auto old_path = std::filesystem::current_path(); - - dir = std::filesystem::absolute(dir); - if (!std::filesystem::exists(dir)) std::filesystem::create_directories(dir); - - std::filesystem::current_path(dir); - f(); - std::filesystem::current_path(old_path); -} - -int main(int argc, char** argv) { - argparse::ArgumentParser parser("fierro"); - parser.add_description(""); - parser.add_argument("-o", "--output") - .help("output directory for the program") - .default_value("."); - - auto backends = find_fierro_backends(); - for(auto backend : backends) { - parser.add_subparser(*backend->command); - } - - try { - parser.parse_args(argc, argv); - } catch(const std::runtime_error& e) { - std::cerr << e.what() << std::endl; - std::cerr << parser; - std::exit(1); - } - - for(auto backend : backends) { - if (parser.is_subcommand_used(*backend->command)) { - return with_curdir( - parser.get("output"), - [&]() { - try { - return backend->invoke(); - } catch (ArgumentException e) { - std::cerr << e.message << std::endl; - std::cerr << parser; - std::exit(1); - } - } - ); - } - } - - // If they didn't select anything, give them some help. - std::cout << parser << std::endl; -} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8758ebda9..78e716178 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_subdirectory(Yaml-Serializable) add_subdirectory(Mesh-Builder) add_subdirectory(Voxelizer) -add_subdirectory(CLI) + if(BUILD_IMPLICIT_SOLVER OR BUILD_PARALLEL_EXPLICIT_SOLVER) add_subdirectory(Parallel-Solvers) From 0a7118b41a6f7a34e283dd3d47ba7105c7f522f3 Mon Sep 17 00:00:00 2001 From: Jacob Moore Date: Fri, 12 Apr 2024 11:35:08 -0500 Subject: [PATCH 3/7] DOC: Update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a5a598e3c..adc01ebc1 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ # Usage ## Anaconda -The recommended way to use **Fierro** is through the provided Anaconda package and command line utility. To use the anaconda package, follow the steps for your platform to install [anaconda](https://docs.anaconda.com/free/anaconda/install/index.html)/[miniconda](https://docs.conda.io/en/latest/miniconda.html)/[mamba](https://mamba.readthedocs.io/en/latest/installation.html). Then run the following command in your desired Anaconda environment: +The recommended way to use **Fierro** is through the provided Anaconda package. To use the anaconda package, follow the steps for your platform to install [anaconda](https://docs.anaconda.com/free/anaconda/install/index.html)/[miniconda](https://docs.conda.io/en/latest/miniconda.html)/[mamba](https://mamba.readthedocs.io/en/latest/installation.html). Then run the following command in your desired Anaconda environment: ``` conda install fierro-cpu -c fierromechanics -c conda-forge ``` -This will give you access to the **Fierro** command line interface. You can run the following to check that the package was installed correctly: +This will give you access to the **Fierro** tool suite. This includes the `fierro-mesh-builder`,`fierro-parallel-explicit`,`fierro-parallel-implicit`, and the `fierro-voxelizer` executables. These can be ran by calling the appropriate executable with the desired input. For example, to call the parallel explicit hydrodynamics solver, use the following command: ``` -fierro -h +./fierro-parallel-explicit input.yaml ``` ## Material models From 5614981d4d54e59d4035fd3ca9891dad573cc468 Mon Sep 17 00:00:00 2001 From: Jacob Moore Date: Fri, 12 Apr 2024 11:36:46 -0500 Subject: [PATCH 4/7] DOC: Update readme, remove CLI --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index adc01ebc1..68c295c4a 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ This will give you access to the **Fierro** tool suite. This includes the `fierr ./fierro-parallel-explicit input.yaml ``` -## Material models +## Material models The classical ideal gas model is the only material model implemented in the code, and it is useful for verification tests of the software and simple gas dynamic simulations. The linear Lagrangian finite element methods for explicit material dynamics have an interface to user developed material models. The interface is to enable **Fierro** to be used for model research and development that has historically been done with commercial explicit finite element codes. To include your own custom material models, you need to implement them under `Fierro/Parallel-Solvers/User-Material-Interface` and re-build the project. @@ -51,7 +51,7 @@ Building the code from source allows you to compile with more targeted hardware To build it yourself, run the following from the root directory. The native CPU architecture will automatically be taken into account. GPU hardware will be leveraged according to the distribution of Trilinos that **Fierro** is built against. -You are welcome to only compile one solver or the other, and the one(s) that you did compile will be available through the CLI. +You are welcome to only compile the desired solver, or all of the currently available ones. ## Building dependencies **Fierro** depends on both ELEMENTS and Trilinos. If you are building **Fierro** for hardware optimizations, you should also build ELEMENTS from source. ELEMENTS is included in the **Fierro** source distribution and building ELEMENTS is enabled by default when building **Fierro**. From 5c73365051f790586b045277ff23d34cefdcbf05 Mon Sep 17 00:00:00 2001 From: Nathaniel Morgan <47866137+nathanielmorgan@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:33:30 -0600 Subject: [PATCH 5/7] Update README.md Updated the documentation on installing anaconda distributions and running those distributions --- README.md | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 68c295c4a..bd0d6efe4 100644 --- a/README.md +++ b/README.md @@ -13,16 +13,44 @@ # Usage ## Anaconda -The recommended way to use **Fierro** is through the provided Anaconda package. To use the anaconda package, follow the steps for your platform to install [anaconda](https://docs.anaconda.com/free/anaconda/install/index.html)/[miniconda](https://docs.conda.io/en/latest/miniconda.html)/[mamba](https://mamba.readthedocs.io/en/latest/installation.html). Then run the following command in your desired Anaconda environment: +The recommended way to use **Fierro** is through the provided Anaconda package. To use the anaconda package, follow the steps for your platform to install [anaconda](https://docs.anaconda.com/free/anaconda/install/index.html)/[miniconda](https://docs.conda.io/en/latest/miniconda.html)/[mamba](https://mamba.readthedocs.io/en/latest/installation.html). +Open a terminal on your machine and go to a folder where you want to run the **Fierro** code. Then create and activate an Anaconda environment by typing: ``` -conda install fierro-cpu -c fierromechanics -c conda-forge +conda create -n FierroCode +source activate FierroCode ``` +In this example, the enviroment is called FierroCode, but any name can be used. On some distributions of Anaconda, the text to activate an enviroment is `conda activate FierroCode`. Likewise, if an enviroment already exists, then just activate the desired environment. -This will give you access to the **Fierro** tool suite. This includes the `fierro-mesh-builder`,`fierro-parallel-explicit`,`fierro-parallel-implicit`, and the `fierro-voxelizer` executables. These can be ran by calling the appropriate executable with the desired input. For example, to call the parallel explicit hydrodynamics solver, use the following command: +To install the finite element physics solvers in **Fierro**, please type within the activated Anaconda environment: ``` -./fierro-parallel-explicit input.yaml +conda install -c conda-forge -c fierromechanics fierro-cpu ``` +The EVPFFT physics solver in **Fierro** can be installed by typing: +``` +conda install -c conda-forge -c fierromechanics evpfft +``` +A GUI is offered, it can be installed by typing: +``` +conda install -c conda-forge -c fierromechanics evpfft_gui +``` + +After installing the finite element solvers, it gives you access to `fierro-mesh-builder`,`fierro-parallel-explicit`,`fierro-parallel-implicit`, and the `fierro-voxelizer` executables. These can be run by calling the appropriate executable with the desired input. For example, to call the parallel explicit hydrodynamics solver, use the following command: +``` +fierro-parallel-explicit input.yaml +``` +A sample input file for the explicit finite element solver is here: +`./src/Parallel-Solvers/Parallel-Explicit/example_simple.yaml` + +The GUI can be run in the anaconda enviroment by typing: +``` +evpfft-gui +``` +The anaconda distributions of Fierro are located in +``` +https://anaconda.org/FierroMechanics +``` + ## Material models The classical ideal gas model is the only material model implemented in the code, and it is useful for verification tests of the software and simple gas dynamic simulations. The linear Lagrangian finite element methods for explicit material dynamics have an interface to user developed material models. The interface is to enable **Fierro** to be used for model research and development that has historically been done with commercial explicit finite element codes. From 453e7f08e9ce41b8130edc2cb351f871e4cfae12 Mon Sep 17 00:00:00 2001 From: Nathaniel Morgan <47866137+nathanielmorgan@users.noreply.github.com> Date: Fri, 12 Apr 2024 11:37:00 -0600 Subject: [PATCH 6/7] Update README.md Updated the documentation on installing anaconda distributions and running those distributions --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bd0d6efe4..fa205d75c 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,8 @@ The GUI can be run in the anaconda enviroment by typing: ``` evpfft-gui ``` -The anaconda distributions of Fierro are located in -``` -https://anaconda.org/FierroMechanics -``` +The anaconda distributions of Fierro are located [here](https://anaconda.org/FierroMechanics). + ## Material models From 3cd0b6a384348aa6eb8bccf65f2c501f000f919b Mon Sep 17 00:00:00 2001 From: Nathaniel Morgan <47866137+nathanielmorgan@users.noreply.github.com> Date: Fri, 12 Apr 2024 15:54:09 -0600 Subject: [PATCH 7/7] Update README.md Updated documentation for Anaconda --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fa205d75c..373200185 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,9 @@ The recommended way to use **Fierro** is through the provided Anaconda package. Open a terminal on your machine and go to a folder where you want to run the **Fierro** code. Then create and activate an Anaconda environment by typing: ``` conda create -n FierroCode -source activate FierroCode +conda activate FierroCode ``` -In this example, the enviroment is called FierroCode, but any name can be used. On some distributions of Anaconda, the text to activate an enviroment is `conda activate FierroCode`. Likewise, if an enviroment already exists, then just activate the desired environment. +In this example, the enviroment is called FierroCode, but any name can be used. In some cases, the text to activate an enviroment is `source activate FierroCode`. Likewise, if an enviroment already exists, then just activate the desired environment. To install the finite element physics solvers in **Fierro**, please type within the activated Anaconda environment: ```