Update argparse to get multiline alignment

This commit is contained in:
Marek Roszko 2023-08-31 22:09:34 -04:00
parent 270c195df9
commit 88a76d4b01
24 changed files with 418 additions and 78 deletions

View File

@ -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'

View File

@ -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/

View File

@ -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 }}

View File

@ -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)

View File

@ -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.

View File

@ -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;

View File

@ -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

View File

@ -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")) {

View File

@ -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")) {

View File

@ -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 {

View File

@ -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"

View File

@ -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>>(

View File

@ -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>>(

View File

@ -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")) {

View File

@ -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) {

View File

@ -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");

View File

@ -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";

View File

@ -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

View File

@ -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

View File

@ -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);
}
}

View File

@ -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"));
}

View File

@ -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"));
}
}

View File

@ -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);
}

View File

@ -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);
}