Update argparse to get multiline alignment
This commit is contained in:
parent
270c195df9
commit
88a76d4b01
|
@ -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'
|
||||
|
|
|
@ -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/
|
|
@ -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 }}
|
|
@ -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
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
|
@ -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)
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://travis-ci.org/p-ranav/argparse.svg?branch=master" alt="travis"/>
|
||||
<a href="https://github.com/p-ranav/argparse/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="license"/>
|
||||
</a>
|
||||
|
@ -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<int>("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("<command_name>")` 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<T>()```. 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<argparse::ArgumentParser>("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.
|
||||
|
|
|
@ -376,7 +376,8 @@ class Argument {
|
|||
explicit Argument(std::string_view prefix_chars,
|
||||
std::array<std::string_view, N> &&a,
|
||||
std::index_sequence<I...> /*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<std::size_t>::max()};
|
||||
m_num_args_range = NArgsRange{0, (std::numeric_limits<std::size_t>::max)()};
|
||||
break;
|
||||
case nargs_pattern::at_least_one:
|
||||
m_num_args_range = NArgsRange{1, std::numeric_limits<std::size_t>::max()};
|
||||
m_num_args_range = NArgsRange{1, (std::numeric_limits<std::size_t>::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<T>) {
|
||||
return get<T>() == rhs;
|
||||
} else {
|
||||
using ValueType = typename T::value_type;
|
||||
auto lhs = get<T>();
|
||||
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<const ValueType &>(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<std::size_t>::max();
|
||||
return m_max < (std::numeric_limits<std::size_t>::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<std::size_t>::max()) {
|
||||
if (range.m_max == (std::numeric_limits<std::size_t>::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 <typename T>
|
||||
auto get() const
|
||||
-> std::conditional_t<details::IsContainer<T>, T, const T &> {
|
||||
template <typename T> T get() const {
|
||||
if (!m_values.empty()) {
|
||||
if constexpr (details::IsContainer<T>) {
|
||||
return any_cast_container<T>(m_values);
|
||||
} else {
|
||||
return *std::any_cast<T>(&m_values.front());
|
||||
return std::any_cast<T>(m_values.front());
|
||||
}
|
||||
}
|
||||
if (m_default_value.has_value()) {
|
||||
return *std::any_cast<T>(&m_default_value);
|
||||
return std::any_cast<T>(m_default_value);
|
||||
}
|
||||
if constexpr (details::IsContainer<T>) {
|
||||
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<ValueType>(&value); });
|
||||
[](const auto &value) { return std::any_cast<ValueType>(value); });
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -1033,11 +1068,12 @@ private:
|
|||
[](const std::string &value) { return value; }};
|
||||
std::vector<std::any> 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 <typename T = Argument>
|
||||
T& at(std::string_view name) {
|
||||
if constexpr (std::is_same_v<T, Argument>) {
|
||||
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 <typename T = std::string>
|
||||
auto get(std::string_view arg_name) const
|
||||
-> std::conditional_t<details::IsContainer<T>, T, const T &> {
|
||||
template <typename T = std::string> 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<std::size_t>(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<std::size_t>(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;
|
||||
|
|
|
@ -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<bool>("-a"); // true
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<std::string>("--color"); // "orange"
|
||||
|
|
|
@ -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<std::vector<std::string>>(
|
||||
|
|
|
@ -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<std::vector<std::string>>(
|
||||
|
|
|
@ -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")) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<int>("square");
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
#include <argparse/argparse.hpp>
|
||||
#include <doctest.hpp>
|
||||
|
||||
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<argparse::ArgumentParser>("walk")) == &walk_cmd);
|
||||
REQUIRE(&(program.at<argparse::ArgumentParser>("walk").at("speed")) == &speed);
|
||||
REQUIRE(program.at<argparse::ArgumentParser>("walk").is_used("speed"));
|
||||
}
|
||||
|
||||
SUBCASE("with unknown command") {
|
||||
program.parse_args({"test"});
|
||||
REQUIRE_THROWS_WITH_AS(program.at<argparse::ArgumentParser>("fly"),
|
||||
"No such subparser: fly", std::logic_error);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
#include <argparse/argparse.hpp>
|
||||
#include <doctest.hpp>
|
||||
#include <sstream>
|
||||
#include <streambuf>
|
||||
|
||||
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"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#include <argparse/argparse.hpp>
|
||||
#include <doctest.hpp>
|
||||
#include <string>
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
|
@ -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<int>("--stuff"), std::bad_any_cast);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue