1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 20:23:55 +00:00
CLI11/tests/HelpTest.cpp
Marc 65442ad846
A better Help formatter (V2) (#866)
_This is the new PR I've mentioned to work on in PR #858_

## A better Help Formatter
_See below for images of the new help page_

Finally, after a lot of planning, understanding CLI11's codebase,
testing and coding, the new default Help Formatter is done. There are a
lot of changes to make the help page more readable and closer to UNIX
standards, see Changelog below for details. One of the highlights is
automatic paragraph formatting with correct line wrapping for App and
options/flag descriptions as well as the footer.
A goal was to provide more flexibility and better readability for the
help page while providing full compatibility with Apps using CLI11 (no
breaking changes and no changes to Apps required). Also better support
for different terminal sizes. Users can now specify three new optional
attributes: `right_column_width_`, `description_paragraph_width_` and
`footer_paragraph_width_`. See code documentation for more details. The
different columns for options/flags now scale with the set
`column_width_` value: Single dash flags occupy 33% of the set
`column_width_`, double dash flags and options (like REQUIRED) 66%.
These new attributes allow for indirectly respecting terminal geometry,
footer paragraph formatting has also been added (#355). This PR also
implements the issues #353 and #856.
The new help page formatting can also be used as an input for man page
generation, since it's oriented on the man page style (#413).
[help2man](https://www.gnu.org/software/help2man/) can be used to
generate man pages from help output (see comment down below for
example).

I thoroughly tested this code with all possible combinations of flags,
options, positionals, subcommands, validators, ...
So far everything works great.
I hope this PR looks good and meets all requirements. I'm looking
forward to the implementation of this PR into CLI11. If you have any
questions or suggestions feel free to comment.

### Fixed/implemented issues by this PR
- #353 Better options formatting
- #856 Space between options
- #355 Footer formatting
- #413 Man page generation can be achieved using help2man with the new
help formatting
- https://github.com/CLIUtils/CLI11/issues/384#issuecomment-570066436
Better help formatting can be marked as complete

### What about the failing tests?
Of course the tests expect the old help text format. This is why 6 of
the tests are failing. Since it is a bit of work to migrate the tests to
the new help format, I first wanted to push out this PR and get
confirmation before I'll update all the tests.
So please let me know if this PR gets implemented, what changes should
be made and then I'll migrate the tests to the new help format, either
in this PR or I'll make a new one.

## Changelog:
#### There are _no breaking changes_. Every App using CLI11 will work
with this new formatter with no changes required.
- Added empty lines at beginning and end of help text
- Removed double new-line between option groups for consistency. Now all
sections have the same number of new-lines
- Switched usage and description order
- Only show "Usage"-string if no App name is present. This provides
better readability
- Made categories (Options, Positionals, ...) capital
- Changed `ConfigBase::to_config` to correctly process capital
"OPTIONS"-group (only affects descriptions of the config file, not a
breaking change)
- Added a paragraph formatter function `streamOutAsParagraph` to
StringTools.hpp
- Made "description" a paragraph block with correct, word respecting
line wrapping and indentation (using the new paragraph formatter
function)
- Made the footer a paragraph block with correct, word respecting line
wrapping and indentation
- Updated documentation for `column_width_` to make it more clear
- Added new member: `right_column_width_`, added getter and setter for
`right_column_width_`
- Added new member: `description_paragraph_width_`, added getter and
setter for `description_paragraph_width_`
- Added new member: `footer_paragraph_width_`, added getter and setter
for `footer_paragraph_width_ `
- Positionals description are now formatted as paragraph with correct,
word respecting line wrapping
- Options description are now formatted as paragraph with correct, word
respecting line wrapping
- Short and long options/flags/names are now correctly formatted to
always be at the right position (also for subcommand options/flags)
- Short and long options/flags/names column widths scale linearly with
the `column_width_` attribute to better adapt to different
`column_width_` sizes
- Merged PR #860

## What's planned for the future?
- I'm thinking of better formatting the options of flags (like REQUIRED,
TEXT, INT, ...) and make them also in a seperate column. This way they
would also always be at the same position. However I decided against it
for this PR, since I wanted them to be as close as possible to the
actual flag. With my implementation it is quite easy to add this change
in the future.
- Subcommands: I'm planning on better formatting the Subcommands. With
this PR only the short and long flags/options of subcommands are better
formatted (like it is with the main flags, see images down below).
- Maybe implement a different way to display expected data type options
(TEXT, INT, ...). For example: `--file-name=<TEXT>` for long flags only
and if `disable_flag_override_` is false.
- Maybe add something like this:
https://github.com/CLIUtils/CLI11/issues/554

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Philip Top <phlptp@gmail.com>
2024-10-07 08:13:04 -07:00

1508 lines
43 KiB
C++

// Copyright (c) 2017-2024, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause
#ifdef CLI11_SINGLE_FILE
#include "CLI11.hpp"
#else
#include "CLI/CLI.hpp"
#endif
#include "app_helper.hpp"
#include "catch.hpp"
#include <fstream>
TEST_CASE("THelp: Basic", "[help]") {
CLI::App app{"My prog"};
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, Contains("-h, --help"));
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
}
TEST_CASE("THelp: Usage", "[help]") {
CLI::App app{"My prog"};
app.usage("use: just use it");
std::string help = app.help();
CHECK_THAT(help, Contains("use: just use it"));
}
TEST_CASE("THelp: UsageCallback", "[help]") {
CLI::App app{"My prog"};
app.usage([]() { return "use: just use it"; });
std::string help = app.help();
CHECK_THAT(help, Contains("use: just use it"));
}
TEST_CASE("THelp: UsageCallbackBoth", "[help]") {
CLI::App app{"My prog"};
app.usage([]() { return "use: just use it"; });
app.usage("like 1, 2, and 3");
std::string help = app.help();
CHECK_THAT(help, Contains("use: just use it"));
CHECK_THAT(help, Contains("like 1, 2, and 3"));
}
TEST_CASE("THelp: Footer", "[help]") {
CLI::App app{"My prog"};
app.footer("Report bugs to bugs@example.com");
std::string help = app.help();
CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
}
TEST_CASE("THelp: FooterCallback", "[help]") {
CLI::App app{"My prog"};
app.footer([]() { return "Report bugs to bugs@example.com"; });
std::string help = app.help();
CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
}
TEST_CASE("THelp: FooterCallbackBoth", "[help]") {
CLI::App app{"My prog"};
app.footer([]() { return "Report bugs to bugs@example.com"; });
app.footer(" foot!!!!");
std::string help = app.help();
CHECK_THAT(help, Contains("Report bugs to bugs@example.com"));
CHECK_THAT(help, Contains("foot!!!!"));
}
TEST_CASE("THelp: OptionalPositional", "[help]") {
CLI::App app{"My prog", "program"};
std::string x;
app.add_option("something", x, "My option here");
std::string help = app.help();
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("something TEXT"));
CHECK_THAT(help, Contains("My option here"));
CHECK_THAT(help, Contains("program [OPTIONS] [something]"));
}
TEST_CASE("THelp: Hidden", "[help]") {
CLI::App app{"My prog"};
std::string x;
app.add_option("something", x, "My option here")->group("");
std::string y;
app.add_option("--another", y)->group("");
std::string help = app.help();
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, !Contains("[something]"));
CHECK_THAT(help, !Contains("something "));
CHECK_THAT(help, !Contains("another"));
}
TEST_CASE("THelp: deprecatedOptions", "[help]") {
CLI::App app{"My prog"};
std::string x;
auto *soption = app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);
CLI::deprecate_option(soption, "something_else");
std::string help = app.help();
CHECK_THAT(help, Contains("DEPRECATED"));
CHECK_THAT(help, Contains("something"));
CHECK_NOTHROW(app.parse("--something deprecated"));
}
TEST_CASE("THelp: deprecatedOptions2", "[help]") {
CLI::App app{"My prog"};
std::string x;
app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);
CLI::deprecate_option(&app, "--something");
std::string help = app.help();
CHECK_THAT(help, Contains("DEPRECATED"));
CHECK_THAT(help, Contains("something"));
CHECK_NOTHROW(app.parse("--something deprecated"));
}
TEST_CASE("THelp: deprecatedOptions3", "[help]") {
CLI::App app{"My prog"};
app.get_formatter()->right_column_width(100);
std::string x;
app.add_option("--something", x, "Some Description");
app.add_option("--something_else", x, "Some other description");
std::string y;
app.add_option("--another", y);
CLI::deprecate_option(app, "--something", "--something_else");
std::string help = app.help();
CHECK_THAT(help, Contains("DEPRECATED"));
CHECK_THAT(help, Contains("'--something_else' instead"));
CHECK_NOTHROW(app.parse("--something deprecated"));
}
TEST_CASE("THelp: retiredOptions", "[help]") {
CLI::App app{"My prog"};
std::string x;
auto *opt1 = app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);
CLI::retire_option(app, opt1);
std::string help = app.help();
CHECK_THAT(help, Contains("RETIRED"));
CHECK_THAT(help, Contains("something"));
CHECK_NOTHROW(app.parse("--something old"));
}
TEST_CASE("THelp: retiredOptions2", "[help]") {
CLI::App app{"My prog"};
std::string x;
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);
CLI::retire_option(&app, "--something");
std::string help = app.help();
CHECK_THAT(help, Contains("RETIRED"));
CHECK_THAT(help, Contains("something"));
CHECK_NOTHROW(app.parse("--something old"));
}
TEST_CASE("THelp: retiredOptions3", "[help]") {
CLI::App app{"My prog"};
std::string x;
app.add_option("--something", x, "My option here");
app.add_option("--something_else", x, "My option here");
std::string y;
app.add_option("--another", y);
CLI::retire_option(app, "--something");
std::string help = app.help();
CHECK_THAT(help, Contains("RETIRED"));
CHECK_THAT(help, Contains("something"));
CHECK_NOTHROW(app.parse("--something old"));
}
TEST_CASE("THelp: HiddenGroup", "[help]") {
CLI::App app{"My prog"};
// empty option group name should be hidden
auto *hgroup = app.add_option_group("");
std::string x;
hgroup->add_option("something", x, "My option here");
std::string y;
hgroup->add_option("--another", y);
std::string help = app.help();
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, !Contains("[something]"));
CHECK_THAT(help, !Contains("something "));
CHECK_THAT(help, !Contains("another"));
hgroup->group("ghidden");
help = app.help();
CHECK_THAT(help, Contains("something "));
CHECK_THAT(help, Contains("another"));
}
// from https://github.com/CLIUtils/CLI11/issues/1045
TEST_CASE("THelp: multiple_group", "[help]") {
CLI::App app{"test_group"};
auto *group1 = app.add_option_group("outGroup");
auto *group2 = app.add_option_group("inGroup");
std::string outFile("");
group1->add_option("--outfile,-o", outFile, "specify the file location of the output")->required();
std::string inFile("");
group2->add_option("--infile,-i", inFile, "specify the file location of the input")->required();
auto help = app.help();
int inCount = 0;
int outCount = 0;
auto iFind = help.find("inGroup");
while(iFind != std::string::npos) {
++inCount;
iFind = help.find("inGroup", iFind + 6);
}
auto oFind = help.find("outGroup");
while(oFind != std::string::npos) {
++outCount;
oFind = help.find("outGroup", oFind + 6);
}
CHECK(inCount == 1);
CHECK(outCount == 1);
}
TEST_CASE("THelp: OptionalPositionalAndOptions", "[help]") {
CLI::App app{"My prog", "AnotherProgram"};
app.add_flag("-q,--quick");
std::string x;
app.add_option("something", x, "My option here");
std::string help = app.help();
CHECK_THAT(help, Contains("AnotherProgram [OPTIONS] [something]"));
}
TEST_CASE("THelp: RequiredPositionalAndOptions", "[help]") {
CLI::App app{"My prog"};
app.add_flag("-q,--quick");
std::string x;
app.add_option("something", x, "My option here")->required();
std::string help = app.help();
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("Usage: [OPTIONS] something"));
}
TEST_CASE("THelp: MultiOpts", "[help]") {
CLI::App app{"My prog"};
std::vector<int> x, y;
app.add_option("-q,--quick", x, "Disc")->expected(2);
app.add_option("-v,--vals", y, "Other");
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("Usage: [OPTIONS]"));
CHECK_THAT(help, Contains("INT x 2"));
CHECK_THAT(help, Contains("INT ..."));
}
TEST_CASE("THelp: VectorOpts", "[help]") {
CLI::App app{"My prog"};
std::vector<int> x = {1, 2};
app.add_option("-q,--quick", x)->capture_default_str();
std::string help = app.help();
CHECK_THAT(help, Contains("[1,2]"));
CHECK_THAT(help, Contains(" ..."));
}
TEST_CASE("THelp: MultiPosOpts", "[help]") {
CLI::App app{"My prog"};
app.name("program");
std::vector<int> x, y;
app.add_option("quick", x, "Disc")->expected(2);
app.add_option("vals", y, "Other");
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("program [OPTIONS]"));
CHECK_THAT(help, Contains("INT x 2"));
CHECK_THAT(help, Contains("INT ..."));
CHECK_THAT(help, Contains("[quick(2x)]"));
CHECK_THAT(help, Contains("[vals...]"));
}
TEST_CASE("THelp: EnvName", "[help]") {
CLI::App app{"My prog"};
std::string input;
app.add_option("--something", input)->envname("SOME_ENV");
std::string help = app.help();
CHECK_THAT(help, Contains("SOME_ENV"));
}
TEST_CASE("THelp: Needs", "[help]") {
CLI::App app{"My prog"};
CLI::Option *op1 = app.add_flag("--op1");
app.add_flag("--op2")->needs(op1);
std::string help = app.help();
CHECK_THAT(help, Contains("Needs: --op1"));
}
TEST_CASE("THelp: NeedsPositional", "[help]") {
CLI::App app{"My prog"};
int x{0}, y{0};
CLI::Option *op1 = app.add_option("op1", x, "one");
app.add_option("op2", y, "two")->needs(op1);
std::string help = app.help();
CHECK_THAT(help, Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("Needs: op1"));
}
TEST_CASE("THelp: Excludes", "[help]") {
CLI::App app{"My prog"};
CLI::Option *op1 = app.add_flag("--op1");
app.add_flag("--op2")->excludes(op1);
std::string help = app.help();
CHECK_THAT(help, Contains("Excludes: --op1"));
}
TEST_CASE("THelp: ExcludesPositional", "[help]") {
CLI::App app{"My prog"};
int x{0}, y{0};
CLI::Option *op1 = app.add_option("op1", x);
app.add_option("op2", y)->excludes(op1);
std::string help = app.help();
CHECK_THAT(help, Contains("POSITIONALS:"));
CHECK_THAT(help, Contains("Excludes: op1"));
}
TEST_CASE("THelp: ExcludesSymmetric", "[help]") {
CLI::App app{"My prog"};
CLI::Option *op1 = app.add_flag("--op1");
app.add_flag("--op2")->excludes(op1);
std::string help = app.help();
CHECK_THAT(help, Contains("Excludes: --op2"));
}
TEST_CASE("THelp: ManualSetters", "[help]") {
CLI::App app{"My prog"};
int x{1};
CLI::Option *op1 = app.add_option("--op", x);
op1->default_str("12");
op1->type_name("BIGGLES");
CHECK(1 == x);
std::string help = app.help();
CHECK_THAT(help, Contains("[12]"));
CHECK_THAT(help, Contains("BIGGLES"));
op1->default_val("14");
CHECK(14 == x);
help = app.help();
CHECK_THAT(help, Contains("[14]"));
op1->default_val(12);
CHECK(12 == x);
help = app.help();
CHECK_THAT(help, Contains("[12]"));
CHECK(op1->get_run_callback_for_default());
op1->run_callback_for_default(false);
CHECK(!op1->get_run_callback_for_default());
op1->default_val(18);
// x should not be modified in this case
CHECK(12 == x);
help = app.help();
CHECK_THAT(help, Contains("[18]"));
}
TEST_CASE("THelp: ManualSetterOverFunction", "[help]") {
CLI::App app{"My prog"};
int x{1};
CLI::Option *op1 = app.add_option("--op1", x)->check(CLI::IsMember({1, 2}));
CLI::Option *op2 = app.add_option("--op2", x)->transform(CLI::IsMember({1, 2}));
op1->default_str("12");
op1->type_name("BIGGLES");
op2->type_name("QUIGGLES");
CHECK(1 == x);
std::string help = app.help();
CHECK_THAT(help, Contains("[12]"));
CHECK_THAT(help, Contains("BIGGLES"));
CHECK_THAT(help, Contains("QUIGGLES"));
CHECK_THAT(help, Contains("{1,2}"));
}
TEST_CASE("THelp: Subcom", "[help]") {
CLI::App app{"My prog"};
auto *sub1 = app.add_subcommand("sub1");
app.add_subcommand("sub2");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
app.require_subcommand();
help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] SUBCOMMAND"));
help = sub1->help();
CHECK_THAT(help, Contains("sub1 [OPTIONS]"));
char x[] = "./myprogram"; // NOLINT(modernize-avoid-c-arrays)
char y[] = "sub2"; // NOLINT(modernize-avoid-c-arrays)
std::vector<char *> args = {x, y};
app.parse(static_cast<int>(args.size()), args.data());
help = app.help();
CHECK_THAT(help, Contains("./myprogram sub2"));
}
TEST_CASE("THelp: Subcom_alias", "[help]") {
CLI::App app{"My prog"};
auto *sub1 = app.add_subcommand("sub1", "Subcommand1 description test");
sub1->alias("sub_alias1");
sub1->alias("sub_alias2");
app.add_subcommand("sub2", "Subcommand2 description test");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
CHECK_THAT(help, Contains("sub_alias1"));
CHECK_THAT(help, Contains("sub_alias2"));
}
TEST_CASE("THelp: Subcom_alias_group", "[help]") {
CLI::App app{"My prog"};
auto *sub1 = app.add_subcommand("", "Subcommand1 description test");
sub1->alias("sub_alias1");
sub1->alias("sub_alias2");
app.add_subcommand("sub2", "Subcommand2 description test");
std::string help = app.help();
CHECK_THAT(help, Contains("Usage: [OPTIONS] [SUBCOMMAND]"));
CHECK_THAT(help, Contains("sub_alias1"));
CHECK_THAT(help, Contains("sub_alias2"));
}
TEST_CASE("THelp: MasterName", "[help]") {
CLI::App app{"My prog", "MyRealName"};
char x[] = "./myprogram"; // NOLINT(modernize-avoid-c-arrays)
std::vector<char *> args = {x};
app.parse(static_cast<int>(args.size()), args.data());
CHECK_THAT(app.help(), Contains("MyRealName"));
}
TEST_CASE("THelp: IntDefaults", "[help]") {
CLI::App app{"My prog"};
int one{1}, two{2};
app.add_option("--one", one, "Help for one")->capture_default_str();
app.add_option("--set", two, "Help for set")->capture_default_str()->check(CLI::IsMember({2, 3, 4}));
std::string help = app.help();
CHECK_THAT(help, Contains("--one"));
CHECK_THAT(help, Contains("--set"));
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, Contains("[2]"));
CHECK_THAT(help, Contains("2,3,4"));
}
TEST_CASE("THelp: SetLower", "[help]") {
CLI::App app{"My prog"};
app.option_defaults()->always_capture_default();
std::string def{"One"};
app.add_option("--set", def, "Help for set")->check(CLI::IsMember({"oNe", "twO", "THREE"}));
std::string help = app.help();
CHECK_THAT(help, Contains("--set"));
CHECK_THAT(help, Contains("[One]"));
CHECK_THAT(help, Contains("oNe"));
CHECK_THAT(help, Contains("twO"));
CHECK_THAT(help, Contains("THREE"));
}
TEST_CASE("THelp: OnlyOneHelp", "[help]") {
CLI::App app{"My prog"};
// It is not supported to have more than one help flag, last one wins
app.set_help_flag("--help", "No short name allowed");
app.set_help_flag("--yelp", "Alias for help");
std::vector<std::string> input{"--help"};
CHECK_THROWS_AS(app.parse(input), CLI::ExtrasError);
}
TEST_CASE("THelp: MultiHelp", "[help]") {
CLI::App app{"My prog"};
// It is not supported to have more than one help flag, last one wins
app.set_help_flag("--help,-h,-?", "No short name allowed");
app.allow_windows_style_options();
std::vector<std::string> input{"/?"};
CHECK_THROWS_AS(app.parse(input), CLI::CallForHelp);
}
TEST_CASE("THelp: OnlyOneAllHelp", "[help]") {
CLI::App app{"My prog"};
// It is not supported to have more than one help flag, last one wins
app.set_help_all_flag("--help-all", "No short name allowed");
app.set_help_all_flag("--yelp", "Alias for help");
std::vector<std::string> input{"--help-all"};
CHECK_THROWS_AS(app.parse(input), CLI::ExtrasError);
std::vector<std::string> input2{"--yelp"};
CHECK_THROWS_AS(app.parse(input2), CLI::CallForAllHelp);
// Remove the flag
app.set_help_all_flag();
std::vector<std::string> input3{"--yelp"};
CHECK_THROWS_AS(app.parse(input3), CLI::ExtrasError);
}
TEST_CASE("THelp: RemoveHelp", "[help]") {
CLI::App app{"My prog"};
app.set_help_flag();
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("-h,--help"));
CHECK_THAT(help, !Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
std::vector<std::string> input{"--help"};
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
}
}
TEST_CASE("THelp: RemoveOtherMethodHelp", "[help]") {
CLI::App app{"My prog"};
// Don't do this. Just in case, let's make sure it works.
app.remove_option(const_cast<CLI::Option *>(app.get_help_ptr()));
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("-h,--help"));
CHECK_THAT(help, !Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
std::vector<std::string> input{"--help"};
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
}
}
TEST_CASE("THelp: RemoveOtherMethodHelpAll", "[help]") {
CLI::App app{"My prog"};
app.set_help_all_flag("--help-all");
// Don't do this. Just in case, let's make sure it works.
app.remove_option(const_cast<CLI::Option *>(app.get_help_all_ptr()));
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("--help-all"));
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
std::vector<std::string> input{"--help-all"};
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
}
}
TEST_CASE("THelp: NoHelp", "[help]") {
CLI::App app{"My prog"};
app.set_help_flag();
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("-h,--help"));
CHECK_THAT(help, !Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
std::vector<std::string> input{"--help"};
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
}
}
TEST_CASE("THelp: CustomHelp", "[help]") {
CLI::App app{"My prog"};
CLI::Option *help_option = app.set_help_flag("--yelp", "display help and exit");
CHECK(help_option == app.get_help_ptr());
std::string help = app.help();
CHECK_THAT(help, Contains("My prog"));
CHECK_THAT(help, !Contains("-h,--help"));
CHECK_THAT(help, Contains("--yelp"));
CHECK_THAT(help, Contains("OPTIONS:"));
CHECK_THAT(help, Contains("Usage:"));
std::vector<std::string> input{"--yelp"};
try {
app.parse(input);
} catch(const CLI::CallForHelp &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
}
}
TEST_CASE("THelp: HelpSubcommandPriority", "[help]") {
CLI::App app{"My prog"};
app.set_help_flag("-h", "display help and exit");
auto *sub1 = app.add_subcommand("sub1");
std::string someFile = "";
put_env("SOME_FILE", "NOT_A_FILE");
sub1->add_option("-f,--file", someFile)->envname("SOME_FILE")->required()->expected(1)->check(CLI::ExistingFile);
std::string input{"sub1 -h"};
CHECK_THROWS_AS(app.parse(input), CLI::CallForHelp);
unset_env("SOME_FILE");
}
TEST_CASE("THelp: NextLineShouldBeAlignmentInMultilineDescription", "[help]") {
CLI::App app;
int i{0};
const std::string first{"first line"};
const std::string second{"second line"};
app.add_option("-i,--int", i, first + "\n" + second);
const std::string help = app.help();
const auto width = app.get_formatter()->get_column_width();
auto first_loc = help.find("first");
auto first_new_line = help.find_last_of('\n', first_loc);
auto second_loc = help.find("second");
auto second_new_line = help.find_last_of('\n', second_loc);
CHECK(first_loc - first_new_line - 1 == width);
CHECK(second_loc - second_new_line - 1 == width);
CHECK(second_new_line > first_loc);
}
TEST_CASE("THelp: CheckRightWidth", "[help]") {
CLI::App app;
int i{0};
const std::string first{"first line"};
const std::string second{"second line"};
app.add_option("-i,--int", i, first + "\n" + second);
app.get_formatter()->column_width(24);
CHECK(app.get_formatter()->get_column_width() == 24);
const std::string help = app.help();
auto first_loc = help.find("first");
auto first_new_line = help.find_last_of('\n', first_loc);
auto second_loc = help.find("second");
auto second_new_line = help.find_last_of('\n', second_loc);
CHECK(first_loc - first_new_line - 1 == 24);
CHECK(second_loc - second_new_line - 1 == 24);
CHECK(second_new_line > first_loc);
}
static const std::string long_string{
"AAARG this is a long line description that will span across multiple lines and still go on and on. This is meant "
"to test how the help handler handles things like this"};
TEST_CASE("THelp: longLineAlignment", "[help]") {
CLI::App app;
int i{0};
app.add_option("-i,--int,--int_very_long_option_name_that_just_keeps_going_on_and_on_and_on_and_on_and_on_possibly_"
"to_infitinty,--and_another_long_name_just_for_fun",
i,
long_string);
std::string help = app.help();
auto width = app.get_formatter()->get_right_column_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
app.get_formatter()->right_column_width(30);
width = app.get_formatter()->get_right_column_width();
CHECK(width == 30);
help = app.help();
first_loc = help.find("AAARG");
first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: longPositional", "[help]") {
CLI::App app;
int i{0};
app.add_option("int_very_long_option_name_that_just_keeps_going_on_and_on_and_on_and_on_and_on_possibly_"
"to_infitinty",
i,
long_string);
std::string help = app.help();
auto width = app.get_formatter()->get_right_column_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: SubcommandNewLineDescription", "[help]") {
const std::string nl_description{"this is a description with aX \n X\\n in it and just for fun \n\t another"};
CLI::App app;
int i{0};
app.add_option("-i,--int", i);
app.add_subcommand("subcom1", nl_description);
std::string help = app.help();
auto width = app.get_formatter()->get_column_width();
auto first_X = help.find_first_of('X');
auto first_new_line = help.find_first_of('\n', first_X);
auto second_X = help.find_first_of('X', first_new_line);
CHECK(second_X - first_new_line > width);
}
TEST_CASE("THelp: longDescription", "[help]") {
CLI::App app(long_string, "long_desc");
int i{0};
app.add_option("-i,--int", i);
std::string help = app.help();
auto width = app.get_formatter()->get_description_paragraph_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
app.get_formatter()->description_paragraph_width(30);
width = app.get_formatter()->get_description_paragraph_width();
CHECK(width == 30);
help = app.help();
first_loc = help.find("AAARG");
first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: longSubcommandDescription", "[help]") {
CLI::App app;
int i{0};
app.add_option("-i,--int", i);
app.add_subcommand("test1", long_string);
std::string help = app.help();
auto width = app.get_formatter()->get_right_column_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
app.get_formatter()->right_column_width(30);
width = 30;
help = app.help();
first_loc = help.find("AAARG");
first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: longSubcommandDescriptionExpanded", "[help]") {
CLI::App app;
int i{0};
app.add_option("-i,--int", i);
app.add_subcommand("test1", long_string);
auto help = app.help("", CLI::AppFormatMode::All);
auto width = app.get_formatter()->get_description_paragraph_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: longFooter", "[help]") {
CLI::App app("test long footer", "long_desc");
int i{0};
app.footer(long_string);
app.add_option("-i,--int", i);
std::string help = app.help();
auto width = app.get_formatter()->get_footer_paragraph_width();
auto first_loc = help.find("AAARG");
auto first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
app.get_formatter()->footer_paragraph_width(30);
width = app.get_formatter()->get_footer_paragraph_width();
CHECK(width == 30);
help = app.help();
first_loc = help.find("AAARG");
first_new_line = help.find_first_of('\n', first_loc);
CHECK(first_new_line - first_loc - 1 < width);
}
TEST_CASE("THelp: NiceName", "[help]") {
CLI::App app;
int x{0};
auto *long_name = app.add_option("-s,--long,-q,--other,that", x);
auto *short_name = app.add_option("more,-x,-y", x);
auto *positional = app.add_option("posit", x);
CHECK("--long" == long_name->get_name());
CHECK("-x" == short_name->get_name());
CHECK("posit" == positional->get_name());
}
TEST_CASE("Exit: ErrorWithHelp", "[help]") {
CLI::App app{"My prog"};
std::vector<std::string> input{"-h"};
try {
app.parse(input);
} catch(const CLI::CallForHelp &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
}
}
TEST_CASE("Exit: ErrorWithAllHelp", "[help]") {
CLI::App app{"My prog"};
app.set_help_all_flag("--help-all", "All help");
std::vector<std::string> input{"--help-all"};
try {
app.parse(input);
} catch(const CLI::CallForAllHelp &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::Success));
}
}
TEST_CASE("Exit: ErrorWithoutHelp", "[help]") {
CLI::App app{"My prog"};
std::vector<std::string> input{"--none"};
try {
app.parse(input);
} catch(const CLI::ParseError &e) {
CHECK(e.get_exit_code() == static_cast<int>(CLI::ExitCodes::ExtrasError));
}
}
TEST_CASE("Exit: ExitCodes", "[help]") {
CLI::App app;
auto i = static_cast<int>(CLI::ExitCodes::ExtrasError);
CHECK(app.exit(CLI::Success()) == 0);
CHECK(app.exit(CLI::CallForHelp()) == 0);
CHECK(app.exit(CLI::ExtrasError({"Thing"})) == i);
CHECK(app.exit(CLI::RuntimeError(42)) == 42);
CHECK(app.exit(CLI::RuntimeError()) == 1);
}
struct CapturedHelp {
CLI::App app{"My Test Program"};
std::stringstream out{};
std::stringstream err{};
int run(const CLI::Error &e) { return app.exit(e, out, err); }
void reset() {
out.clear();
err.clear();
}
};
TEST_CASE_METHOD(CapturedHelp, "Successful", "[help]") {
CHECK(0 == run(CLI::Success()));
CHECK(out.str().empty());
CHECK(err.str().empty());
}
TEST_CASE_METHOD(CapturedHelp, "JustAnError", "[help]") {
CHECK(42 == run(CLI::RuntimeError(42)));
CHECK(out.str().empty());
CHECK(err.str().empty());
}
TEST_CASE_METHOD(CapturedHelp, "CallForHelp", "[help]") {
CHECK(0 == run(CLI::CallForHelp()));
CHECK(app.help() == out.str());
CHECK(err.str().empty());
}
TEST_CASE_METHOD(CapturedHelp, "CallForAllHelp", "[help]") {
CHECK(0 == run(CLI::CallForAllHelp()));
CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
CHECK(err.str().empty());
}
TEST_CASE_METHOD(CapturedHelp, "CallForAllHelpOutput", "[help]") {
app.set_help_all_flag("--help-all", "Help all");
app.add_subcommand("one", "One description");
CLI::App *sub = app.add_subcommand("two");
sub->add_flag("--three");
CHECK(0 == run(CLI::CallForAllHelp()));
CHECK(app.help("", CLI::AppFormatMode::All) == out.str());
CHECK(err.str().empty());
CHECK_THAT(out.str(), Contains("one"));
CHECK_THAT(out.str(), Contains("two"));
CHECK_THAT(out.str(), Contains("--three"));
CHECK_THAT(out.str(), Contains("SUBCOMMANDS:"));
CHECK_THAT(out.str(), Contains("--help-all"));
CHECK_THAT(out.str(), Contains("My Test Program"));
}
TEST_CASE_METHOD(CapturedHelp, "NewFormattedHelp", "[help]") {
app.formatter_fn([](const CLI::App *, std::string, CLI::AppFormatMode) { return "New Help"; });
CHECK(0 == run(CLI::CallForHelp()));
CHECK("New Help" == out.str());
CHECK(err.str().empty());
}
TEST_CASE_METHOD(CapturedHelp, "NormalError", "[help]") {
CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
CHECK(out.str().empty());
CHECK_THAT(err.str(), Contains("for more information"));
CHECK_THAT(err.str(), !Contains("ExtrasError"));
CHECK_THAT(err.str(), Contains("Thing"));
CHECK_THAT(err.str(), !Contains(" or "));
CHECK_THAT(err.str(), !Contains("Usage"));
}
TEST_CASE_METHOD(CapturedHelp, "DoubleError", "[help]") {
app.set_help_all_flag("--help-all");
CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
CHECK(out.str().empty());
CHECK_THAT(err.str(), Contains("for more information"));
CHECK_THAT(err.str(), Contains(" --help "));
CHECK_THAT(err.str(), Contains(" --help-all "));
CHECK_THAT(err.str(), Contains(" or "));
CHECK_THAT(err.str(), !Contains("ExtrasError"));
CHECK_THAT(err.str(), Contains("Thing"));
CHECK_THAT(err.str(), !Contains("Usage"));
}
TEST_CASE_METHOD(CapturedHelp, "AllOnlyError", "[help]") {
app.set_help_all_flag("--help-all");
app.set_help_flag();
CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
CHECK(out.str().empty());
CHECK_THAT(err.str(), Contains("for more information"));
CHECK_THAT(err.str(), !Contains(" --help "));
CHECK_THAT(err.str(), Contains(" --help-all "));
CHECK_THAT(err.str(), !Contains(" or "));
CHECK_THAT(err.str(), !Contains("ExtrasError"));
CHECK_THAT(err.str(), Contains("Thing"));
CHECK_THAT(err.str(), !Contains("Usage"));
}
TEST_CASE_METHOD(CapturedHelp, "ReplacedError", "[help]") {
app.failure_message(CLI::FailureMessage::help);
CHECK(static_cast<int>(CLI::ExitCodes::ExtrasError) == run(CLI::ExtrasError({"Thing"})));
CHECK(out.str().empty());
CHECK_THAT(err.str(), !Contains("for more information"));
CHECK_THAT(err.str(), Contains("ERROR: ExtrasError"));
CHECK_THAT(err.str(), Contains("Thing"));
CHECK_THAT(err.str(), Contains("Usage"));
}
// #87
TEST_CASE("THelp: CustomDoubleOption", "[help]") {
std::pair<int, double> custom_opt;
CLI::App app;
auto *opt = app.add_option("posit", [&custom_opt](CLI::results_t vals) {
custom_opt = {stol(vals.at(0)), stod(vals.at(1))};
return true;
});
opt->type_name("INT FLOAT")->type_size(2);
CHECK_THAT(app.help(), !Contains("x 2"));
}
TEST_CASE("THelp: CheckEmptyTypeName", "[help]") {
CLI::App app;
auto *opt = app.add_flag("-f,--flag");
std::string name = opt->get_type_name();
CHECK(name.empty());
}
TEST_CASE("THelp: FlagDefaults", "[help]") {
CLI::App app;
app.add_flag("-t,--not{false}")->default_str("false");
auto str = app.help();
CHECK_THAT(str, Contains("--not{false}"));
}
TEST_CASE("THelp: AccessDescription", "[help]") {
CLI::App app{"My description goes here"};
CHECK("My description goes here" == app.get_description());
}
TEST_CASE("THelp: SetDescriptionAfterCreation", "[help]") {
CLI::App app{""};
app.description("My description goes here");
CHECK("My description goes here" == app.get_description());
CHECK_THAT(app.help(), Contains("My description goes here"));
}
TEST_CASE("THelp: AccessOptionDescription", "[help]") {
CLI::App app{};
int x{0};
auto *opt = app.add_option("-a,--alpha", x, "My description goes here");
CHECK("My description goes here" == opt->get_description());
}
TEST_CASE("THelp: SetOptionDescriptionAfterCreation", "[help]") {
CLI::App app{};
int x{0};
auto *opt = app.add_option("-a,--alpha", x);
opt->description("My description goes here");
CHECK("My description goes here" == opt->get_description());
CHECK_THAT(app.help(), Contains("My description goes here"));
}
TEST_CASE("THelp: CleanNeeds", "[help]") {
CLI::App app;
int x{0};
auto *a_name = app.add_option("-a,--alpha", x);
app.add_option("-b,--boo", x)->needs(a_name);
CHECK_THAT(app.help(), !Contains("Requires"));
CHECK_THAT(app.help(), !Contains("Needs: -a,--alpha"));
CHECK_THAT(app.help(), Contains("Needs: --alpha"));
}
TEST_CASE("THelp: RequiredPrintout", "[help]") {
CLI::App app;
int x{0};
app.add_option("-a,--alpha", x)->required();
CHECK_THAT(app.help(), Contains(" REQUIRED"));
}
TEST_CASE("THelp: GroupOrder", "[help]") {
CLI::App app;
app.add_flag("--one")->group("zee");
app.add_flag("--two")->group("aee");
std::string help = app.help();
auto zee_loc = help.find("zee");
auto aee_loc = help.find("aee");
CHECK(std::string::npos != zee_loc);
CHECK(std::string::npos != aee_loc);
CHECK(aee_loc > zee_loc);
}
TEST_CASE("THelp: GroupNameError", "[help]") {
CLI::App app;
auto *f1 = app.add_flag("--one");
auto *f2 = app.add_flag("--two");
CHECK_THROWS_AS(f1->group("evil group name\non two lines"), CLI::IncorrectConstruction);
CHECK_THROWS_AS(f2->group(std::string(5, '\0')), CLI::IncorrectConstruction);
}
TEST_CASE("THelp: ValidatorsText", "[help]") {
CLI::App app;
std::string filename;
int x{0};
unsigned int y{0};
app.add_option("--f1", filename)->check(CLI::ExistingFile);
app.add_option("--f3", x)->check(CLI::Range(1, 4));
app.add_option("--f4", y)->check(CLI::Range(12));
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:FILE"));
CHECK_THAT(help, Contains("INT in [1 - 4]"));
CHECK_THAT(help, Contains("UINT:INT in [0 - 12]"));
}
TEST_CASE("THelp: ValidatorsTextCustom", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f1", filename)->check(CLI::ExistingFile.description("Existing file"));
std::string help = app.help();
CHECK_THAT(help, Contains("Existing file"));
}
TEST_CASE("THelp: ValidatorsNonPathText", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f2", filename)->check(CLI::NonexistentPath);
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:PATH"));
}
TEST_CASE("THelp: ValidatorsDirText", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f2", filename)->check(CLI::ExistingDirectory);
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:DIR"));
}
TEST_CASE("THelp: ValidatorsPathText", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f2", filename)->check(CLI::ExistingPath);
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:PATH"));
}
TEST_CASE("THelp: CombinedValidatorsText", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f1", filename)->check(CLI::ExistingFile | CLI::ExistingDirectory);
// This would be nice if it put something other than string, but would it be path or file?
// Can't programmatically tell!
// (Users can use ExistingPath, by the way)
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:(FILE) OR (DIR)"));
CHECK_THAT(help, !Contains("PATH"));
}
// Don't do this in real life, please
TEST_CASE("THelp: CombinedValidatorsPathyText", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f1", filename)->check(CLI::ExistingPath | CLI::NonexistentPath);
// Combining validators with the same type string is OK
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:"));
CHECK_THAT(help, Contains("PATH"));
}
// Don't do this in real life, please (and transform does nothing here)
TEST_CASE("THelp: CombinedValidatorsPathyTextAsTransform", "[help]") {
CLI::App app;
std::string filename;
app.add_option("--f1", filename)->transform(CLI::ExistingPath | CLI::NonexistentPath);
// Combining validators with the same type string is OK
std::string help = app.help();
CHECK_THAT(help, Contains("TEXT:(PATH(existing)) OR (PATH"));
}
// #113 Part 2
TEST_CASE("THelp: ChangingSet", "[help]") {
CLI::App app;
std::set<int> vals{1, 2, 3};
int val{0};
app.add_option("--val", val)->check(CLI::IsMember(&vals));
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, !Contains("4"));
vals.insert(4);
vals.erase(1);
help = app.help();
CHECK_THAT(help, !Contains("1"));
CHECK_THAT(help, Contains("4"));
}
TEST_CASE("THelp: ChangingSetDefaulted", "[help]") {
CLI::App app;
std::set<int> vals{1, 2, 3};
int val{2};
app.add_option("--val", val, "")->check(CLI::IsMember(&vals))->capture_default_str();
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, !Contains("4"));
vals.insert(4);
vals.erase(1);
help = app.help();
CHECK_THAT(help, !Contains("1"));
CHECK_THAT(help, Contains("4"));
}
TEST_CASE("THelp: ChangingCaselessSet", "[help]") {
CLI::App app;
std::set<std::string> vals{"1", "2", "3"};
std::string val;
app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, !Contains("4"));
vals.insert("4");
vals.erase("1");
help = app.help();
CHECK_THAT(help, !Contains("1"));
CHECK_THAT(help, Contains("4"));
}
TEST_CASE("THelp: ChangingCaselessSetDefaulted", "[help]") {
CLI::App app;
app.option_defaults()->always_capture_default();
std::set<std::string> vals{"1", "2", "3"};
std::string val = "2";
app.add_option("--val", val)->check(CLI::IsMember(&vals, CLI::ignore_case));
std::string help = app.help();
CHECK_THAT(help, Contains("1"));
CHECK_THAT(help, !Contains("4"));
vals.insert("4");
vals.erase("1");
help = app.help();
CHECK_THAT(help, !Contains("1"));
CHECK_THAT(help, Contains("4"));
}
// New defaults tests (1.8)
TEST_CASE("THelp: ChangingDefaults", "[help]") {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
x = {3, 4};
CHECK(x[0] == 3);
opt->capture_default_str();
x = {5, 6};
std::string help = app.help();
CHECK_THAT(help, Contains("[[3,4]]"));
CHECK_THAT(help, Contains("..."));
CHECK_THAT(help, Contains("INT"));
CHECK(x[0] == 5);
}
TEST_CASE("THelp: ChangingDefaultsWithAutoCapture", "[help]") {
CLI::App app;
app.option_defaults()->always_capture_default();
std::vector<int> x = {1, 2};
CHECK(x[0] == 1);
app.add_option("-q,--quick", x);
x = {3, 4};
CHECK(x[0] == 3);
std::string help = app.help();
CHECK_THAT(help, Contains("[[1,2]]"));
CHECK_THAT(help, Contains("..."));
}
TEST_CASE("THelp: FunctionDefaultString", "[help]") {
CLI::App app;
std::vector<int> x = {1, 2};
CLI::Option *opt = app.add_option("-q,--quick", x);
opt->default_function([]() { return std::string("Powerful"); });
opt->capture_default_str();
std::string help = app.help();
CHECK_THAT(help, Contains("[Powerful]"));
}
TEST_CASE("TVersion: simple_flag", "[help]") {
CLI::App app;
app.set_version_flag("-v,--version", "VERSION " CLI11_VERSION);
auto vers = app.version();
CHECK_THAT(vers, Contains("VERSION"));
app.set_version_flag();
CHECK(app.version().empty());
}
TEST_CASE("TVersion: callback_flag", "[help]") {
CLI::App app;
app.set_version_flag("-v,--version", []() { return std::string("VERSION " CLI11_VERSION); });
auto vers = app.version();
CHECK_THAT(vers, Contains("VERSION"));
app.set_version_flag("-v", []() { return std::string("VERSION2 " CLI11_VERSION); });
vers = app.version();
CHECK_THAT(vers, Contains("VERSION"));
}
TEST_CASE("TVersion: help", "[help]") {
CLI::App app;
app.set_version_flag("-v,--version", "version_string", "help_for_version");
auto hvers = app.help();
CHECK_THAT(hvers, Contains("help_for_version"));
app.set_version_flag("-v", []() { return std::string("VERSION2 " CLI11_VERSION); }, "help_for_version2");
hvers = app.help();
CHECK_THAT(hvers, Contains("help_for_version2"));
}
TEST_CASE("TVersion: parse_throw", "[help]") {
CLI::App app;
app.set_version_flag("--version", CLI11_VERSION);
CHECK_THROWS_AS(app.parse("--version"), CLI::CallForVersion);
CHECK_THROWS_AS(app.parse("--version --arg2 5"), CLI::CallForVersion);
auto *ptr = app.get_version_ptr();
ptr->ignore_case();
try {
app.parse("--Version");
} catch(const CLI::CallForVersion &v) {
CHECK_THAT(CLI11_VERSION, Equals(v.what()));
CHECK(0 == v.get_exit_code());
const auto &appc = app;
const auto *cptr = appc.get_version_ptr();
CHECK(1U == cptr->count());
}
}
TEST_CASE("TVersion: exit", "[help]") {
CLI::App app;
app.set_version_flag("--version", CLI11_VERSION);
try {
app.parse("--version");
} catch(const CLI::CallForVersion &v) {
std::ostringstream out;
auto ret = app.exit(v, out);
CHECK_THAT(out.str(), Contains(CLI11_VERSION));
CHECK(0 == ret);
}
}