From 2fb95d72fcef0ebd1183c2ade398a481e8f8d2da Mon Sep 17 00:00:00 2001 From: reneSchm <49305466+reneSchm@users.noreply.github.com> Date: Tue, 16 Sep 2025 17:32:20 +0200 Subject: [PATCH 01/10] manually rebase cli rework branch on new main --- cpp/examples/cli.cpp | 110 ++- cpp/memilio/CMakeLists.txt | 1 + cpp/memilio/io/cli.cpp | 225 ++++++ cpp/memilio/io/cli.h | 1026 +++++++++++++++------------ cpp/memilio/utils/string_literal.h | 19 + cpp/tests/distributions_helpers.cpp | 16 +- cpp/tests/distributions_helpers.h | 3 + cpp/tests/test_io_cli.cpp | 409 +++++++---- 8 files changed, 1165 insertions(+), 644 deletions(-) create mode 100644 cpp/memilio/io/cli.cpp diff --git a/cpp/examples/cli.cpp b/cpp/examples/cli.cpp index 6dcfe55bcd..5df8ada063 100644 --- a/cpp/examples/cli.cpp +++ b/cpp/examples/cli.cpp @@ -19,85 +19,57 @@ */ #include "memilio/io/cli.h" -#include - -struct Name { - using Type = std::vector; - - static Type get_default() - { - return Type{"FirstName", "LastName"}; - } - const static std::string name() - { - return "Name"; - } - const static std::string alias() - { - return "n"; - } - const static std::string description() - { - return "Enter your name as list of strings."; - } -}; - -struct Age { - using Type = int; - const static std::string name() - { - return "Age"; - } - const static std::string alias() - { - return "a"; - } - const static std::string description() - { - return "Enter your age."; - } -}; - -struct Greeting { - using Type = std::string; - - static Type get_default() - { - return Type{"Hello World!"}; - } - const static std::string name() - { - return "Greeting"; - } - const static std::string description() - { - return "Enter a custom greeting."; - } -}; - int main(int argc, char** argv) { - if (argc == 1) { // Print this if no arguments were given + // Print a message if no arguments were given. + if (argc == 1) { std::cout << "This is a small example on how to use the command line interface. " "Use \"-h\" to show the help dialogue.\n"; } - // create parameter set - auto parameters = mio::ParameterSet{}; - // get command line options - auto result = mio::command_line_interface("cli_example", argc, argv, parameters); - // catch errors + // Create a parameter set for the CLI using the builder. This defines the (parameter) options that the user can set + // through the command line. + // + // To add a parameter, you need to specify the name and type and pass an initial value. The type can sometimes be + // deduced from the initial value, so it can sometimes be omitted. + // After the initial value you can set some optional fields of the parameter: + // - alias, which allows you to add a shorthand for setting values + // - description, which contains details on, e.g., what the parameter does and what values are accepted. + // - is_required, which makes the CLI check whether the parameter was set. If not, it exits with an error. + // + // As a general rule, use simple types! The more complicated the type, the more complex is the Json representation + // that the user has to input. + // + // Instead of using the builder, you can also define and pass a mio::ParameterSet as paraneters. + // The main difference (for the CLI) is that the mio::ParameterSet uses struct names to "get" parameters, while + // the mio::cli::ParameterSet uses StringLiteral%s. + auto parameters = mio::cli::ParameterSetBuilder() + .add<"Name", std::vector>({"FirstName", "LastName"}, + {"n", "Enter your name as list of strings.", true}) + .add<"Age">(0, {"a", "Enter your age."}) + .add<"Greeting">(std::string("Hello World!"), + {.description = "Enter a custom greeting.", .is_required = false}) + .build(); + // Define some default options. This is an optional feature, that allows users to set some options in the given + // order as the first arguments, without specifying their name or alias. + auto default_options = std::vector{"Name", "Age"}; + // Parse command line arguments and/or set parameters. This next line as well as the following check on its result + // are required to use the CLI. + auto result = mio::command_line_interface(argv[0], argc, argv, parameters, default_options); + // Catch and print help output, printed options, and errors. if (!result) { - std::cout << result.error().formatted_message(); - return result.error().code().value(); + std::cout << result.error().message(); // Do not use formatted_message(). + return result.error().code().value(); // Use exit here when not used in main(). } - // do something with the parameters - std::cout << parameters.get() << "\n" + // Now, do something with the parameters! + // Note that the CLI only verifies that the user input is parsable, not plausible. If a parameter has certain value + // requirements, like "Age > 0", you must check this + std::cout << parameters.get<"Greeting">() << "\n" << "Name: "; - for (auto& name : parameters.get()) { + for (auto& name : parameters.get<"Name">()) { std::cout << name << " "; } std::cout << "\n"; - if (parameters.get() > 0) { - std::cout << "Age: " << parameters.get() << "\n"; + if (parameters.get<"Age">() > 0) { + std::cout << "Age: " << parameters.get<"Age">() << "\n"; } } diff --git a/cpp/memilio/CMakeLists.txt b/cpp/memilio/CMakeLists.txt index 5079672695..ed21478da6 100644 --- a/cpp/memilio/CMakeLists.txt +++ b/cpp/memilio/CMakeLists.txt @@ -50,6 +50,7 @@ add_library(memilio io/result_io.cpp io/epi_data.h io/epi_data.cpp + io/cli.cpp io/cli.h math/euler.cpp math/euler.h diff --git a/cpp/memilio/io/cli.cpp b/cpp/memilio/io/cli.cpp new file mode 100644 index 0000000000..82fb63c36b --- /dev/null +++ b/cpp/memilio/io/cli.cpp @@ -0,0 +1,225 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: René Schmieding +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#include "memilio/io/cli.h" + +#include + +/// @brief Part of write_help implementation. Writes the header with usage information and default options. +void write_help_preamble(const std::string& executable_name, const std::vector& default_options, + std::ostream& os) +{ + os << "Usage: " << executable_name; + if (default_options.size() > 0) { + os << " ["; + size_t i = 0; + for (; i < default_options.size() - 1; i++) { + os << default_options[i] << " "; + } + os << default_options[i] << "]"; + } + os << "