diff --git a/thirdparty/argparse/.clang-tidy b/thirdparty/argparse/.clang-tidy
index d0b28f4f79..7d0c827054 100644
--- a/thirdparty/argparse/.clang-tidy
+++ b/thirdparty/argparse/.clang-tidy
@@ -18,4 +18,4 @@ CheckOptions:
- { key: readability-identifier-naming.StructIgnoredRegexp, value: "parse_number" }
- { key: readability-identifier-naming.VariableCase, value: lower_case }
-HeaderFilterRegex: '.*'
+HeaderFilterRegex: 'argparse/.+\.hpp'
diff --git a/thirdparty/argparse/.github/workflows/tidy-analysis-stage-01.yml b/thirdparty/argparse/.github/workflows/tidy-analysis-stage-01.yml
new file mode 100644
index 0000000000..66ca0a1de3
--- /dev/null
+++ b/thirdparty/argparse/.github/workflows/tidy-analysis-stage-01.yml
@@ -0,0 +1,42 @@
+# Insecure workflow with limited permissions that should provide analysis
+# results through an artifact.
+name: Tidy analysis
+
+on: pull_request
+
+jobs:
+
+ clang-tidy:
+
+ runs-on: ubuntu-20.04
+
+ steps:
+
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 2
+
+ - name: Install clang-tidy
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y clang-tidy-12
+
+ - name: Prepare compile_commands.json
+ run: cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
+
+ - name: Create results directory
+ run: mkdir clang-tidy-result
+
+ - name: Analyze
+ run: git diff -U0 HEAD^ | clang-tidy-diff-12.py -p1 -regex ".+hpp" -extra-arg=-Iinclude -extra-arg=-std=c++17 -export-fixes clang-tidy-result/fixes.yml
+
+ - name: Save PR metadata
+ run: |
+ echo ${{ github.event.number }} > clang-tidy-result/pr-id.txt
+ echo ${{ github.event.pull_request.head.repo.full_name }} > clang-tidy-result/pr-head-repo.txt
+ echo ${{ github.event.pull_request.head.ref }} > clang-tidy-result/pr-head-ref.txt
+
+ - uses: actions/upload-artifact@v2
+ with:
+ name: clang-tidy-result
+ path: clang-tidy-result/
diff --git a/thirdparty/argparse/.github/workflows/tidy-analysis-stage-02.yml b/thirdparty/argparse/.github/workflows/tidy-analysis-stage-02.yml
new file mode 100644
index 0000000000..61a60de5d0
--- /dev/null
+++ b/thirdparty/argparse/.github/workflows/tidy-analysis-stage-02.yml
@@ -0,0 +1,86 @@
+# Secure workflow with access to repository secrets and GitHub token
+# for posting analysis results.
+name: Post the Tidy analysis results
+
+on:
+ workflow_run:
+ workflows: [ "Tidy analysis" ]
+ types: [ completed ]
+
+jobs:
+
+ clang-tidy-results:
+
+ # Trigger the job only if the previous (insecure) workflow completed successfully
+ if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
+ runs-on: ubuntu-20.04
+
+ steps:
+
+ - name: Download analysis results
+ uses: actions/github-script@v3.1.0
+ with:
+ script: |
+ let artifacts = await github.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{github.event.workflow_run.id }},
+ });
+ let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "clang-tidy-result"
+ })[0];
+ let download = await github.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: "zip",
+ });
+ let fs = require("fs");
+ fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
+
+ - name: Set environment variables
+ run: |
+ mkdir clang-tidy-result
+ unzip clang-tidy-result.zip -d clang-tidy-result
+ echo "pr_id=$(cat clang-tidy-result/pr-id.txt)" >> $GITHUB_ENV
+ echo "pr_head_repo=$(cat clang-tidy-result/pr-head-repo.txt)" >> $GITHUB_ENV
+ echo "pr_head_ref=$(cat clang-tidy-result/pr-head-ref.txt)" >> $GITHUB_ENV
+
+ - uses: actions/checkout@v3
+ with:
+ repository: ${{ env.pr_head_repo }}
+ ref: ${{ env.pr_head_ref }}
+ persist-credentials: false
+
+ - name: Redownload analysis results
+ uses: actions/github-script@v3.1.0
+ with:
+ script: |
+ let artifacts = await github.actions.listWorkflowRunArtifacts({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ run_id: ${{github.event.workflow_run.id }},
+ });
+ let matchArtifact = artifacts.data.artifacts.filter((artifact) => {
+ return artifact.name == "clang-tidy-result"
+ })[0];
+ let download = await github.actions.downloadArtifact({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ artifact_id: matchArtifact.id,
+ archive_format: "zip",
+ });
+ let fs = require("fs");
+ fs.writeFileSync("${{github.workspace}}/clang-tidy-result.zip", Buffer.from(download.data));
+
+ - name: Extract analysis results
+ run: |
+ mkdir clang-tidy-result
+ unzip clang-tidy-result.zip -d clang-tidy-result
+
+ - name: Run clang-tidy-pr-comments action
+ uses: platisd/clang-tidy-pr-comments@master
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ clang_tidy_fixes: clang-tidy-result/fixes.yml
+ pull_request_id: ${{ env.pr_id }}
diff --git a/thirdparty/argparse/CMakeLists.txt b/thirdparty/argparse/CMakeLists.txt
index 19901e58b5..19d894207d 100644
--- a/thirdparty/argparse/CMakeLists.txt
+++ b/thirdparty/argparse/CMakeLists.txt
@@ -7,23 +7,15 @@ project(argparse
LANGUAGES CXX
)
-option(ARGPARSE_INSTALL ON)
-option(ARGPARSE_BUILD_TESTS OFF)
-option(ARGPARSE_LONG_VERSION_ARG_ONLY OFF)
+option(ARGPARSE_INSTALL "Include an install target" ON)
+option(ARGPARSE_BUILD_TESTS "Build tests" OFF)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
-string(REPLACE "/${CMAKE_LIBRARY_ARCHITECTURE}" "" CMAKE_INSTALL_LIBDIR_ARCHIND "${CMAKE_INSTALL_LIBDIR}")
-
add_library(argparse INTERFACE)
add_library(argparse::argparse ALIAS argparse)
-
-if (ARGPARSE_LONG_VERSION_ARG_ONLY)
- target_compile_definitions(argparse INTERFACE ARGPARSE_LONG_VERSION_ARG_ONLY=true)
-endif ()
-
target_compile_features(argparse INTERFACE cxx_std_17)
target_include_directories(argparse INTERFACE
$
@@ -41,7 +33,7 @@ if(ARGPARSE_INSTALL)
install(TARGETS argparse EXPORT argparseConfig)
install(EXPORT argparseConfig
NAMESPACE argparse::
- DESTINATION ${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/${PROJECT_NAME})
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(FILES ${CMAKE_CURRENT_LIST_DIR}/include/argparse/argparse.hpp
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/argparse)
@@ -64,7 +56,7 @@ if(ARGPARSE_INSTALL)
NAMESPACE argparse::)
install(FILES "${CMAKE_CONFIG_VERSION_FILE_NAME}"
- DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/cmake/${PROJECT_NAME}")
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}")
set(PackagingTemplatesDir "${CMAKE_CURRENT_SOURCE_DIR}/packaging")
@@ -94,6 +86,8 @@ if(ARGPARSE_INSTALL)
set(PKG_CONFIG_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc")
configure_file("${PackagingTemplatesDir}/pkgconfig.pc.in" "${PKG_CONFIG_FILE_NAME}" @ONLY)
install(FILES "${PKG_CONFIG_FILE_NAME}"
- DESTINATION "${CMAKE_INSTALL_LIBDIR_ARCHIND}/pkgconfig"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
)
endif()
+
+include(CPack)
diff --git a/thirdparty/argparse/README.md b/thirdparty/argparse/README.md
index 078489a15b..5c7957651d 100644
--- a/thirdparty/argparse/README.md
+++ b/thirdparty/argparse/README.md
@@ -3,7 +3,6 @@
-
@@ -68,6 +67,8 @@ argparse::ArgumentParser program("program_name");
**NOTE:** There is an optional second argument to the `ArgumentParser` which is the program version. Example: `argparse::ArgumentParser program("libfoo", "1.9.0");`
+**NOTE:** There are optional third and fourth arguments to the `ArgumentParser` which control default arguments. Example: `argparse::ArgumentParser program("libfoo", "1.9.0", default_arguments::help, false);` See [Default Arguments](#default-arguments), below.
+
To add a new argument, simply call ```.add_argument(...)```. You can provide a variadic list of argument names that you want to group together, e.g., ```-v``` and ```--verbose```
```cpp
@@ -97,7 +98,7 @@ int main(int argc, char *argv[]) {
catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
auto input = program.get("square");
@@ -557,7 +558,9 @@ The grammar follows `std::from_chars`, but does not exactly duplicate it. For ex
### Default Arguments
-`argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. These default actions exit the program after displaying a help or version message, respectively. These defaults arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
+`argparse` provides predefined arguments and actions for `-h`/`--help` and `-v`/`--version`. By default, these actions will **exit** the program after displaying a help or version message, respectively. This exit does not call destructors, skipping clean-up of taken resources.
+
+These default arguments can be disabled during `ArgumentParser` creation so that you can handle these arguments in your own way. (Note that a program name and version must be included when choosing default arguments.)
```cpp
argparse::ArgumentParser program("test", "1.0", default_arguments::none);
@@ -576,6 +579,12 @@ The above code snippet outputs a help message and continues to run. It does not
The default is `default_arguments::all` for included arguments. No default arguments will be added with `default_arguments::none`. `default_arguments::help` and `default_arguments::version` will individually add `--help` and `--version`.
+The default arguments can be used while disabling the default exit with these arguments. This forth argument to `ArgumentParser` (`exit_on_default_arguments`) is a bool flag with a default **true** value. The following call will retain `--help` and `--version`, but will not exit when those arguments are used.
+
+```cpp
+argparse::ArgumentParser program("test", "1.0", default_arguments::all, false)
+```
+
### Gathering Remaining Arguments
`argparse` supports gathering "remaining" arguments at the end of the command, e.g., for use in a compiler:
@@ -685,25 +694,30 @@ main
### Parent Parsers
-Sometimes, several parsers share a common set of arguments. Rather than repeating the definitions of these arguments, a single parser with all the common arguments can be added as a parent to another ArgumentParser instance. The ```.add_parents``` method takes a list of ArgumentParser objects, collects all the positional and optional actions from them, and adds these actions to the ArgumentParser object being constructed:
+A parser may use arguments that could be used by other parsers.
+
+These shared arguments can be added to a parser which is then used as a "parent" for parsers which also need those arguments. One or more parent parsers may be added to a parser with `.add_parents`. The positional and optional arguments in each parent is added to the child parser.
```cpp
-argparse::ArgumentParser parent_parser("main");
-parent_parser.add_argument("--parent")
+argparse::ArgumentParser surface_parser("surface", 1.0, argparse::default_arguments::none);
+parent_parser.add_argument("--area")
.default_value(0)
.scan<'i', int>();
-argparse::ArgumentParser foo_parser("foo");
-foo_parser.add_argument("foo");
-foo_parser.add_parents(parent_parser);
-foo_parser.parse_args({ "./main", "--parent", "2", "XXX" }); // parent = 2, foo = XXX
+argparse::ArgumentParser floor_parser("floor");
+floor_parser.add_argument("tile_size").scan<'i', int>();
+floor_parser.add_parents(surface_parser);
+floor_parser.parse_args({ "./main", "--area", "200", "12" }); // --area = 200, tile_size = 12
-argparse::ArgumentParser bar_parser("bar");
-bar_parser.add_argument("--bar");
-bar_parser.parse_args({ "./main", "--bar", "YYY" }); // bar = YYY
+argparse::ArgumentParser ceiling_parser("ceiling");
+ceiling_parser.add_argument("--color");
+ceiling_parser.add_parents(surface_parser);
+ceiling_parser.parse_args({ "./main", "--color", "gray" }); // --area = 0, --color = "gray"
```
-Note You must fully initialize the parsers before passing them via ```.add_parents```. If you change the parent parsers after the child parser, those changes will not be reflected in the child.
+Changes made to parents after they are added to a parser are not reflected in any child parsers. Completely initialize parent parsers before adding them to a parser.
+
+Each parser will have the standard set of default arguments. Disable the default arguments in parent parsers to avoid duplicate help output.
### Subcommands
@@ -766,7 +780,7 @@ int main(int argc, char *argv[]) {
catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
// Use arguments
@@ -827,6 +841,20 @@ When a help message is requested from a subparser, only the help for that partic
Additionally, every parser has the `.is_subcommand_used("")` and `.is_subcommand_used(subparser)` member functions to check if a subcommand was used.
+### Getting Argument and Subparser Instances
+
+```Argument``` and ```ArgumentParser``` instances added to an ```ArgumentParser``` can be retrieved with ```.at()```. The default return type is ```Argument```.
+
+```cpp
+argparse::ArgumentParser program("test");
+
+program.add_argument("--dir");
+program.at("--dir").default_value(std::string("/home/user"));
+
+program.add_subparser(argparse::ArgumentParser{"walk"});
+program.at("walk").add_argument("depth");
+```
+
### Parse Known Args
Sometimes a program may only parse a few of the command-line arguments, passing the remaining arguments on to another script or program. In these cases, the `parse_known_args()` function can be useful. It works much like `parse_args()` except that it does not produce an error when extra arguments are present. Instead, it returns a list of remaining argument strings.
@@ -879,7 +907,7 @@ int main(int argc, char *argv[]) {
catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("+f")) {
@@ -927,7 +955,7 @@ int main(int argc, char *argv[]) {
catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("--foo")) {
@@ -1075,7 +1103,7 @@ int main(int argc, char *argv[]) {
catch (const std::runtime_error& err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("--foo")) {
@@ -1141,7 +1169,7 @@ sudo make install
| :------------------- | :--------------- | :----------------- |
| GCC >= 8.3.0 | libstdc++ | Ubuntu 18.04 |
| Clang >= 7.0.0 | libc++ | Xcode 10.2 |
-| MSVC >= 14.16 | Microsoft STL | Visual Studio 2017 |
+| MSVC >= 16.8 | Microsoft STL | Visual Studio 2019 |
## Contributing
Contributions are welcome, have a look at the [CONTRIBUTING.md](CONTRIBUTING.md) document for more information.
diff --git a/thirdparty/argparse/include/argparse/argparse.hpp b/thirdparty/argparse/include/argparse/argparse.hpp
index f4e4c6b248..a779e014ea 100644
--- a/thirdparty/argparse/include/argparse/argparse.hpp
+++ b/thirdparty/argparse/include/argparse/argparse.hpp
@@ -376,7 +376,8 @@ class Argument {
explicit Argument(std::string_view prefix_chars,
std::array &&a,
std::index_sequence /*unused*/)
- : m_is_optional((is_optional(a[I], prefix_chars) || ...)),
+ : 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]), ...);
@@ -408,6 +409,10 @@ public:
return *this;
}
+ Argument &default_value(const char *value) {
+ return default_value(std::string(value));
+ }
+
Argument &required() {
m_is_required = true;
return *this;
@@ -501,10 +506,10 @@ public:
m_num_args_range = NArgsRange{0, 1};
break;
case nargs_pattern::any:
- m_num_args_range = NArgsRange{0, std::numeric_limits::max()};
+ 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()};
+ m_num_args_range = NArgsRange{1, (std::numeric_limits::max)()};
break;
}
return *this;
@@ -668,7 +673,36 @@ public:
name_stream << " " << argument.m_metavar;
}
}
- stream << name_stream.str() << "\t" << argument.m_help;
+
+ // align multiline help message
+ auto stream_width = stream.width();
+ auto name_padding = std::string(name_stream.str().size(), ' ');
+ auto pos = 0;
+ auto prev = 0;
+ auto first_line = true;
+ auto hspace = " "; // minimal space between name and help message
+ stream << name_stream.str();
+ std::string_view help_view(argument.m_help);
+ while ((pos = argument.m_help.find('\n', prev)) != std::string::npos) {
+ auto line = help_view.substr(prev, pos - prev + 1);
+ if (first_line) {
+ stream << hspace << line;
+ first_line = false;
+ } else {
+ stream.width(stream_width);
+ stream << name_padding << hspace << line;
+ }
+ prev += pos - prev + 1;
+ }
+ if (first_line) {
+ stream << hspace << argument.m_help;
+ } else {
+ auto leftover = help_view.substr(prev, argument.m_help.size() - prev);
+ if (!leftover.empty()) {
+ stream.width(stream_width);
+ stream << name_padding << hspace << leftover;
+ }
+ }
// print nargs spec
if (!argument.m_help.empty()) {
@@ -698,10 +732,13 @@ public:
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 a == b; });
+ [](const auto &a, const auto &b) {
+ return std::any_cast(a) == b;
+ });
}
}
@@ -725,7 +762,7 @@ private:
bool is_exact() const { return m_min == m_max; }
bool is_right_bounded() const {
- return m_max < std::numeric_limits::max();
+ return m_max < (std::numeric_limits::max)();
}
std::size_t get_min() const { return m_min; }
@@ -740,7 +777,7 @@ private:
stream << "[nargs: " << range.m_min << "] ";
}
} else {
- if (range.m_max == std::numeric_limits::max()) {
+ if (range.m_max == (std::numeric_limits::max)()) {
stream << "[nargs: " << range.m_min << " or more] ";
} else {
stream << "[nargs=" << range.m_min << ".." << range.m_max << "] ";
@@ -968,18 +1005,16 @@ private:
* Get argument value given a type
* @throws std::logic_error in case of incompatible types
*/
- template
- auto get() const
- -> std::conditional_t, T, const T &> {
+ 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());
+ return std::any_cast(m_values.front());
}
}
if (m_default_value.has_value()) {
- return *std::any_cast(&m_default_value);
+ return std::any_cast(m_default_value);
}
if constexpr (details::IsContainer) {
if (!m_accepts_optional_like_value) {
@@ -1015,7 +1050,7 @@ private:
T result;
std::transform(
std::begin(operand), std::end(operand), std::back_inserter(result),
- [](const auto &value) { return *std::any_cast(&value); });
+ [](const auto &value) { return std::any_cast(value); });
return result;
}
@@ -1033,11 +1068,12 @@ private:
[](const std::string &value) { return value; }};
std::vector m_values;
NArgsRange m_num_args_range{1, 1};
- bool m_accepts_optional_like_value = false;
- bool m_is_optional : true;
- bool m_is_required : true;
- bool m_is_repeatable : true;
- bool m_is_used : true; // True if the optional argument is used by user
+ // 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
};
@@ -1045,14 +1081,18 @@ class ArgumentParser {
public:
explicit ArgumentParser(std::string program_name = {},
std::string version = "1.0",
- default_arguments add_args = default_arguments::all)
+ 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();
- std::exit(0);
+ if (m_exit_on_default_arguments) {
+ std::exit(0);
+ }
})
.default_value(false)
.help("shows help message and exits")
@@ -1063,7 +1103,9 @@ public:
add_argument("-v", "--version")
.action([&](const auto & /*unused*/) {
std::cout << m_version << std::endl;
- std::exit(0);
+ if (m_exit_on_default_arguments) {
+ std::exit(0);
+ }
})
.default_value(false)
.help("prints version information and exits")
@@ -1167,6 +1209,22 @@ public:
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;
@@ -1229,9 +1287,7 @@ public:
* @throws std::logic_error if the option has no value
* @throws std::bad_any_cast if the option is not of type T
*/
- template
- auto get(std::string_view arg_name) const
- -> std::conditional_t, T, const T &> {
+ template T get(std::string_view arg_name) const {
if (!m_is_parsed) {
throw std::logic_error("Nothing parsed, no arguments are available.");
}
@@ -1365,13 +1421,7 @@ public:
// Add any options inline here
for (const auto &argument : this->m_optional_arguments) {
- if (argument.m_names.front() == "-v") {
- continue;
- } else if (argument.m_names.front() == "-h") {
- stream << " [-h]";
- } else {
- stream << " " << argument.get_inline_usage();
- }
+ stream << " " << argument.get_inline_usage();
}
// Put positional arguments after the optionals
for (const auto &argument : this->m_positional_arguments) {
@@ -1639,10 +1689,10 @@ private:
}
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());
+ 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());
+ max_size = std::max(max_size, command.size());
}
return max_size;
}
@@ -1661,6 +1711,7 @@ private:
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;
diff --git a/thirdparty/argparse/samples/compound_arguments.cpp b/thirdparty/argparse/samples/compound_arguments.cpp
index 66d5c920e1..4cfc037fcd 100644
--- a/thirdparty/argparse/samples/compound_arguments.cpp
+++ b/thirdparty/argparse/samples/compound_arguments.cpp
@@ -19,7 +19,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
auto a = program.get("-a"); // true
diff --git a/thirdparty/argparse/samples/custom_assignment_characters.cpp b/thirdparty/argparse/samples/custom_assignment_characters.cpp
index d5deff126b..7e35ae0b19 100644
--- a/thirdparty/argparse/samples/custom_assignment_characters.cpp
+++ b/thirdparty/argparse/samples/custom_assignment_characters.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("--foo")) {
diff --git a/thirdparty/argparse/samples/custom_prefix_characters.cpp b/thirdparty/argparse/samples/custom_prefix_characters.cpp
index 01e248a650..9f8917a366 100644
--- a/thirdparty/argparse/samples/custom_prefix_characters.cpp
+++ b/thirdparty/argparse/samples/custom_prefix_characters.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("+f")) {
diff --git a/thirdparty/argparse/samples/gathering_remaining_arguments.cpp b/thirdparty/argparse/samples/gathering_remaining_arguments.cpp
index 4f404544f1..be13f10f9b 100644
--- a/thirdparty/argparse/samples/gathering_remaining_arguments.cpp
+++ b/thirdparty/argparse/samples/gathering_remaining_arguments.cpp
@@ -12,7 +12,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
try {
diff --git a/thirdparty/argparse/samples/is_used.cpp b/thirdparty/argparse/samples/is_used.cpp
index 27e0373b23..ffc05f880a 100644
--- a/thirdparty/argparse/samples/is_used.cpp
+++ b/thirdparty/argparse/samples/is_used.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
auto color = program.get("--color"); // "orange"
diff --git a/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp b/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
index eebbdc6170..0f6ab8159f 100644
--- a/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
+++ b/thirdparty/argparse/samples/joining_repeated_optional_arguments.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
auto colors = program.get>(
diff --git a/thirdparty/argparse/samples/list_of_arguments.cpp b/thirdparty/argparse/samples/list_of_arguments.cpp
index e533e477ee..996e7480e7 100644
--- a/thirdparty/argparse/samples/list_of_arguments.cpp
+++ b/thirdparty/argparse/samples/list_of_arguments.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
auto files = program.get>(
diff --git a/thirdparty/argparse/samples/negative_numbers.cpp b/thirdparty/argparse/samples/negative_numbers.cpp
index ac4284fb4d..319245fe4b 100644
--- a/thirdparty/argparse/samples/negative_numbers.cpp
+++ b/thirdparty/argparse/samples/negative_numbers.cpp
@@ -17,7 +17,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program.is_used("integer")) {
diff --git a/thirdparty/argparse/samples/optional_flag_argument.cpp b/thirdparty/argparse/samples/optional_flag_argument.cpp
index 2f859540fd..f935ecf0be 100644
--- a/thirdparty/argparse/samples/optional_flag_argument.cpp
+++ b/thirdparty/argparse/samples/optional_flag_argument.cpp
@@ -15,7 +15,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
if (program["--verbose"] == true) {
diff --git a/thirdparty/argparse/samples/positional_argument.cpp b/thirdparty/argparse/samples/positional_argument.cpp
index 03ffb0a68e..4343863ce3 100644
--- a/thirdparty/argparse/samples/positional_argument.cpp
+++ b/thirdparty/argparse/samples/positional_argument.cpp
@@ -16,7 +16,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
int input = program.get("square");
diff --git a/thirdparty/argparse/samples/required_optional_argument.cpp b/thirdparty/argparse/samples/required_optional_argument.cpp
index efea523154..0271f500b3 100644
--- a/thirdparty/argparse/samples/required_optional_argument.cpp
+++ b/thirdparty/argparse/samples/required_optional_argument.cpp
@@ -14,7 +14,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
std::cout << "Output written to " << program.get("-o") << "\n";
diff --git a/thirdparty/argparse/samples/subcommands.cpp b/thirdparty/argparse/samples/subcommands.cpp
index 74d610e14c..ba1059dfc7 100644
--- a/thirdparty/argparse/samples/subcommands.cpp
+++ b/thirdparty/argparse/samples/subcommands.cpp
@@ -60,7 +60,7 @@ int main(int argc, char *argv[]) {
} catch (const std::runtime_error &err) {
std::cerr << err.what() << std::endl;
std::cerr << program;
- std::exit(1);
+ return 1;
}
// Use arguments
diff --git a/thirdparty/argparse/test/CMakeLists.txt b/thirdparty/argparse/test/CMakeLists.txt
index 47b9ba5c7b..a903f09f90 100644
--- a/thirdparty/argparse/test/CMakeLists.txt
+++ b/thirdparty/argparse/test/CMakeLists.txt
@@ -27,11 +27,13 @@ file(GLOB ARGPARSE_TEST_SOURCES
main.cpp
test_actions.cpp
test_append.cpp
+ test_as_container.cpp
test_bool_operator.cpp
test_compound_arguments.cpp
test_container_arguments.cpp
test_const_correct.cpp
test_default_args.cpp
+ test_default_value.cpp
test_get.cpp
test_help.cpp
test_invalid_arguments.cpp
diff --git a/thirdparty/argparse/test/test_as_container.cpp b/thirdparty/argparse/test/test_as_container.cpp
new file mode 100644
index 0000000000..2f795fbf6c
--- /dev/null
+++ b/thirdparty/argparse/test/test_as_container.cpp
@@ -0,0 +1,52 @@
+#include
+#include
+
+using doctest::test_suite;
+
+TEST_CASE("Get argument with .at()" * test_suite("as_container")) {
+ argparse::ArgumentParser program("test");
+ auto &dir_arg = program.add_argument("--dir");
+ program.at("--dir").default_value(std::string("/home/user"));
+
+ SUBCASE("and default value") {
+ program.parse_args({"test"});
+ REQUIRE(&(program.at("--dir")) == &dir_arg);
+ REQUIRE(program["--dir"] == std::string("/home/user"));
+ REQUIRE(program.at("--dir") == std::string("/home/user"));
+ }
+
+ SUBCASE("and user-supplied value") {
+ program.parse_args({"test", "--dir", "/usr/local/database"});
+ REQUIRE(&(program.at("--dir")) == &dir_arg);
+ REQUIRE(program["--dir"] == std::string("/usr/local/database"));
+ REQUIRE(program.at("--dir") == std::string("/usr/local/database"));
+ }
+
+ SUBCASE("with unknown argument") {
+ program.parse_args({"test"});
+ REQUIRE_THROWS_WITH_AS(program.at("--folder"),
+ "No such argument: --folder", std::logic_error);
+ }
+}
+
+TEST_CASE("Get subparser with .at()" * test_suite("as_container")) {
+ argparse::ArgumentParser program("test");
+
+ argparse::ArgumentParser walk_cmd("walk");
+ auto &speed = walk_cmd.add_argument("speed");
+
+ program.add_subparser(walk_cmd);
+
+ SUBCASE("and its argument") {
+ program.parse_args({"test", "walk", "4km/h"});
+ REQUIRE(&(program.at("walk")) == &walk_cmd);
+ REQUIRE(&(program.at("walk").at("speed")) == &speed);
+ REQUIRE(program.at("walk").is_used("speed"));
+ }
+
+ SUBCASE("with unknown command") {
+ program.parse_args({"test"});
+ REQUIRE_THROWS_WITH_AS(program.at("fly"),
+ "No such subparser: fly", std::logic_error);
+ }
+}
diff --git a/thirdparty/argparse/test/test_default_args.cpp b/thirdparty/argparse/test/test_default_args.cpp
index 7b5e581f29..d0cdd25330 100644
--- a/thirdparty/argparse/test/test_default_args.cpp
+++ b/thirdparty/argparse/test/test_default_args.cpp
@@ -1,5 +1,7 @@
#include
#include
+#include
+#include
using doctest::test_suite;
@@ -17,3 +19,13 @@ TEST_CASE("Do not include default arguments" * test_suite("default_args")) {
REQUIRE_THROWS_AS(parser.get("--help"), std::logic_error);
REQUIRE_THROWS_AS(parser.get("--version"), std::logic_error);
}
+
+TEST_CASE("Do not exit on default arguments" * test_suite("default_args")) {
+ argparse::ArgumentParser parser("test", "1.0",
+ argparse::default_arguments::all, false);
+ std::stringstream buf;
+ std::streambuf* saved_cout_buf = std::cout.rdbuf(buf.rdbuf());
+ parser.parse_args({"test", "--help"});
+ std::cout.rdbuf(saved_cout_buf);
+ REQUIRE(parser.is_used("--help"));
+}
diff --git a/thirdparty/argparse/test/test_default_value.cpp b/thirdparty/argparse/test/test_default_value.cpp
new file mode 100644
index 0000000000..15271f96b4
--- /dev/null
+++ b/thirdparty/argparse/test/test_default_value.cpp
@@ -0,0 +1,21 @@
+#include
+#include
+#include
+
+using doctest::test_suite;
+
+TEST_CASE("Use a 'string' default value" * test_suite("default_value")) {
+ argparse::ArgumentParser program("test");
+
+ SUBCASE("Use a const char[] default value") {
+ program.add_argument("--arg").default_value("array of char");
+ REQUIRE_NOTHROW(program.parse_args({"test"}));
+ REQUIRE(program.get("--arg") == std::string("array of char"));
+ }
+
+ SUBCASE("Use a std::string default value") {
+ program.add_argument("--arg").default_value(std::string("string object"));
+ REQUIRE_NOTHROW(program.parse_args({"test"}));
+ REQUIRE(program.get("--arg") == std::string("string object"));
+ }
+}
diff --git a/thirdparty/argparse/test/test_get.cpp b/thirdparty/argparse/test/test_get.cpp
index 9cc046c8d1..ad719b0505 100644
--- a/thirdparty/argparse/test/test_get.cpp
+++ b/thirdparty/argparse/test/test_get.cpp
@@ -33,3 +33,10 @@ TEST_CASE("Implicit argument" * test_suite("ArgumentParser::get")) {
REQUIRE_THROWS_WITH_AS(program.get("--stuff"),
"No value provided for '--stuff'.", std::logic_error);
}
+
+TEST_CASE("Mismatched type for argument" * test_suite("ArgumentParser::get")) {
+ argparse::ArgumentParser program("test");
+ program.add_argument("-s", "--stuff"); // as default type, a std::string
+ REQUIRE_NOTHROW(program.parse_args({"test", "-s", "321"}));
+ REQUIRE_THROWS_AS(program.get("--stuff"), std::bad_any_cast);
+}
diff --git a/thirdparty/argparse/test/test_help.cpp b/thirdparty/argparse/test/test_help.cpp
index a81af42284..293daef187 100644
--- a/thirdparty/argparse/test/test_help.cpp
+++ b/thirdparty/argparse/test/test_help.cpp
@@ -73,3 +73,48 @@ TEST_CASE("Users can replace default -h/--help" * test_suite("help")) {
program.parse_args({"test", "--help"});
REQUIRE_FALSE(buffer.str().empty());
}
+
+TEST_CASE("Multiline help message alignment") {
+ // '#' is used at the beginning of each help message line to simplify testing.
+ // It is important to ensure that this character doesn't appear elsewhere in the test case.
+ // Default arguments (e.g., -h/--help, -v/--version) are not included in this test.
+ argparse::ArgumentParser program("program");
+ program.add_argument("INPUT1")
+ .help(
+ "#This is the first line of help message.\n"
+ "#And this is the second line of help message."
+ );
+ program.add_argument("program_input2")
+ .help("#There is only one line.");
+ program.add_argument("-p", "--prog_input3")
+ .help(
+R"(#Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+#Sed ut perspiciatis unde omnis iste natus error sit voluptatem
+#accusantium doloremque laudantium, totam rem aperiam...)"
+ );
+ program.add_argument("--verbose").default_value(false).implicit_value(true);
+
+ std::ostringstream stream;
+ stream << program;
+ std::istringstream iss(stream.str());
+
+ int help_message_start = -1;
+ std::string line;
+ while (std::getline(iss, line)) {
+ // Find the position of '#', which indicates the start of the help message line
+ auto pos = line.find('#');
+
+ if (pos == std::string::npos) {
+ continue;
+ }
+
+ if (help_message_start == -1) {
+ help_message_start = pos;
+ } else {
+ REQUIRE(pos == help_message_start);
+ }
+ }
+
+ // Make sure we have at least one help message
+ REQUIRE(help_message_start != -1);
+}