1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-03 05:53:52 +00:00
CLI11/examples/CMakeLists.txt
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

258 lines
12 KiB
CMake

function(add_cli_exe T)
add_executable(${T} ${ARGN})
target_link_libraries(${T} PUBLIC CLI11)
set_property(TARGET ${T} PROPERTY FOLDER "Examples")
if(CLI11_FORCE_LIBCXX)
set_property(
TARGET ${T}
APPEND_STRING
PROPERTY LINK_FLAGS -stdlib=libc++)
endif()
endfunction()
if(CLI11_BUILD_EXAMPLES_JSON)
message(STATUS "Using nlohmann/json")
FetchContent_Declare(
json
URL https://github.com/nlohmann/json/releases/download/v3.7.3/include.zip
URL_HASH "SHA256=87b5884741427220d3a33df1363ae0e8b898099fbc59f1c451113f6732891014")
FetchContent_GetProperties(json)
if(NOT json_POPULATED)
FetchContent_Populate(json)
endif()
add_cli_exe(json json.cpp)
target_include_directories(json PUBLIC SYSTEM "${json_SOURCE_DIR}/single_include")
add_test(NAME json_config_out COMMAND json --item 2)
set_property(TEST json_config_out PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "2"]]
[["simple": false]] [[}]])
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/input.json" [=[{"item":3,"simple":false}]=])
add_test(NAME json_config_in COMMAND json --config "${CMAKE_CURRENT_BINARY_DIR}/input.json")
set_property(TEST json_config_in PROPERTY PASS_REGULAR_EXPRESSION [[{]] [["item": "3"]]
[["simple": false]] [[}]])
endif()
add_cli_exe(simple simple.cpp)
add_test(NAME simple_basic COMMAND simple)
add_test(NAME simple_all COMMAND simple -f filename.txt -c 12 --flag --flag -d 1.2)
set_property(
TEST simple_all
PROPERTY PASS_REGULAR_EXPRESSION "Working on file: filename.txt, direct count: 1, opt count: 1"
"Working on count: 12, direct count: 1, opt count: 1" "Received flag: 2 (2) times"
"Some value: 1.2")
add_test(NAME simple_version COMMAND simple --version)
set_property(TEST simple_version PROPERTY PASS_REGULAR_EXPRESSION "${CLI11_VERSION}")
add_cli_exe(subcommands subcommands.cpp)
add_test(NAME subcommands_none COMMAND subcommands)
set_property(TEST subcommands_none PROPERTY PASS_REGULAR_EXPRESSION "A subcommand is required")
add_test(
NAME subcommands_all
COMMAND subcommands --random start --file
name stop --count)
set_property(
TEST subcommands_all
PROPERTY PASS_REGULAR_EXPRESSION "Working on --file from start: name"
"Working on --count from stop: 1, direct count: 1" "Count of --random flag: 1"
"Subcommand: start" "Subcommand: stop")
add_cli_exe(subcom_partitioned subcom_partitioned.cpp)
add_test(NAME subcom_partitioned_none COMMAND subcom_partitioned)
set_property(
TEST subcom_partitioned_none
PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required"
"Run with --help for more information.")
add_test(NAME subcom_partitioned_all COMMAND subcom_partitioned --file this --count --count -d 1.2)
set_property(
TEST subcom_partitioned_all
PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:"
"Working on file: this, direct count: 1, opt count: 1"
"Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2")
# test shows that the help prints out for unnamed subcommands
add_test(NAME subcom_partitioned_help COMMAND subcom_partitioned --help)
set_property(TEST subcom_partitioned_help
PROPERTY PASS_REGULAR_EXPRESSION "-f,[ \\t]*--file TEXT REQUIRED" "-d,--double FLOAT")
####################################################
add_cli_exe(config_app config_app.cpp)
add_test(NAME config_app1 COMMAND config_app -p)
set_property(TEST config_app1 PROPERTY PASS_REGULAR_EXPRESSION "file=")
add_test(NAME config_app2 COMMAND config_app -p -f /)
set_property(TEST config_app2 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
add_test(NAME config_app3 COMMAND config_app -f "" -p)
set_property(TEST config_app3 PROPERTY PASS_REGULAR_EXPRESSION "file=\"\"")
add_test(NAME config_app4 COMMAND config_app -f "/" -p)
set_property(TEST config_app4 PROPERTY PASS_REGULAR_EXPRESSION "file=\"/\"")
####################################################
add_cli_exe(option_groups option_groups.cpp)
add_test(NAME option_groups_missing COMMAND option_groups)
set_property(TEST option_groups_missing PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from"
"is required")
add_test(NAME option_groups_extra COMMAND option_groups --csv --binary)
set_property(TEST option_groups_extra PROPERTY PASS_REGULAR_EXPRESSION "but 2 were given")
add_test(NAME option_groups_extra2 COMMAND option_groups --csv --address "192.168.1.1" -o
"test.out")
set_property(TEST option_groups_extra2 PROPERTY PASS_REGULAR_EXPRESSION "at most 1")
add_cli_exe(positional_arity positional_arity.cpp)
add_test(NAME positional_arity1 COMMAND positional_arity one)
set_property(TEST positional_arity1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
add_test(NAME positional_arity2 COMMAND positional_arity one two)
set_property(TEST positional_arity2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one" "File 2 = two")
add_test(NAME positional_arity3 COMMAND positional_arity 1 2 one)
set_property(TEST positional_arity3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
add_test(NAME positional_arity_fail COMMAND positional_arity 1 one two)
set_property(TEST positional_arity_fail PROPERTY PASS_REGULAR_EXPRESSION "Could not convert")
add_cli_exe(positional_validation positional_validation.cpp)
add_test(NAME positional_validation1 COMMAND positional_validation one)
set_property(TEST positional_validation1 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
add_test(NAME positional_validation2 COMMAND positional_validation one 1 2 two)
set_property(TEST positional_validation2 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one"
"File 2 = two")
add_test(NAME positional_validation3 COMMAND positional_validation 1 2 one)
set_property(TEST positional_validation3 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one")
add_test(NAME positional_validation4 COMMAND positional_validation 1 one two 2)
set_property(TEST positional_validation4 PROPERTY PASS_REGULAR_EXPRESSION "File 1 = one"
"File 2 = two")
add_cli_exe(shapes shapes.cpp)
add_test(NAME shapes_all COMMAND shapes circle 4.4 circle 10.7 rectangle 4 4 circle 2.3 triangle
4.5 ++ rectangle 2.1 ++ circle 234.675)
set_property(
TEST shapes_all PROPERTY PASS_REGULAR_EXPRESSION "circle2" "circle4"
"rectangle2 with edges [2.1,2.1]" "triangel1 with sides [4.5]")
add_cli_exe(ranges ranges.cpp)
add_test(NAME ranges_range COMMAND ranges --range 1 2 3)
set_property(TEST ranges_range PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]")
add_test(NAME ranges_minmax COMMAND ranges --min 2 --max 3)
set_property(TEST ranges_minmax PROPERTY PASS_REGULAR_EXPRESSION "[2:1:3]")
add_test(NAME ranges_error COMMAND ranges --min 2 --max 3 --step 1 --range 1 2 3)
set_property(TEST ranges_error PROPERTY PASS_REGULAR_EXPRESSION "Exactly 1 option from")
add_cli_exe(validators validators.cpp)
add_test(NAME validators_help COMMAND validators --help)
set_property(
TEST validators_help
PROPERTY PASS_REGULAR_EXPRESSION " -f,[ \\t]*--file TEXT:FILE[\\r\\n\\t ]+File name"
" -v,[ \\t]*--value INT:INT in [3 - 6][\\r\\n\\t ]+Value in range")
add_test(NAME validators_file COMMAND validators --file nonex.xxx)
set_property(
TEST validators_file PROPERTY PASS_REGULAR_EXPRESSION "--file: File does not exist: nonex.xxx"
"Run with --help for more information.")
add_test(NAME validators_plain COMMAND validators --value 9)
set_property(
TEST validators_plain PROPERTY PASS_REGULAR_EXPRESSION "--value: Value 9 not in range 3 to 6"
"Run with --help for more information.")
add_cli_exe(groups groups.cpp)
add_test(NAME groups_none COMMAND groups)
set_property(
TEST groups_none PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:" "--file is required"
"Run with --help for more information.")
add_test(NAME groups_all COMMAND groups --file this --count --count -d 1.2)
set_property(
TEST groups_all
PROPERTY PASS_REGULAR_EXPRESSION "This is a timer:"
"Working on file: this, direct count: 1, opt count: 1"
"Working on count: 2, direct count: 2, opt count: 2" "Some value: 1.2")
add_cli_exe(inter_argument_order inter_argument_order.cpp)
add_test(NAME inter_argument_order COMMAND inter_argument_order --foo 1 2 3 --x --bar 4 5 6 --z
--foo 7 8)
set_property(
TEST inter_argument_order
PROPERTY
PASS_REGULAR_EXPRESSION
[=[foo : 1
foo : 2
foo : 3
bar : 4
bar : 5
bar : 6
foo : 7
foo : 8]=])
add_cli_exe(prefix_command prefix_command.cpp)
add_test(NAME prefix_command COMMAND prefix_command -v 3 2 1 -- other one two 3)
set_property(TEST prefix_command PROPERTY PASS_REGULAR_EXPRESSION "Prefix: 3 : 2 : 1"
"Remaining commands: other one two 3")
add_cli_exe(arg_capture arg_capture.cpp)
add_test(NAME arg_capture COMMAND arg_capture -v 27 --sub -v 13 --val prefix)
set_property(TEST arg_capture PROPERTY PASS_REGULAR_EXPRESSION "value=27")
add_test(NAME arg_capture2 COMMAND arg_capture -v 27 --sub -v 13 --val prefix)
set_property(TEST arg_capture2 PROPERTY PASS_REGULAR_EXPRESSION "after Args:-v 13 --val prefix")
add_cli_exe(callback_passthrough callback_passthrough.cpp)
add_test(NAME callback_passthrough1 COMMAND callback_passthrough --argname t2 --t2 test)
set_property(TEST callback_passthrough1 PROPERTY PASS_REGULAR_EXPRESSION "the value is now test")
add_test(NAME callback_passthrough2 COMMAND callback_passthrough --arg EEEK --argname arg)
set_property(TEST callback_passthrough2 PROPERTY PASS_REGULAR_EXPRESSION "the value is now EEEK")
add_cli_exe(enum enum.cpp)
add_test(NAME enum_pass COMMAND enum -l 1)
add_test(NAME enum_fail COMMAND enum -l 4)
set_property(TEST enum_fail PROPERTY PASS_REGULAR_EXPRESSION "--level: Check 4 value in {"
"FAILED")
add_cli_exe(enum_ostream enum_ostream.cpp)
add_test(NAME enum_ostream_pass COMMAND enum_ostream --level medium)
set_property(TEST enum_ostream_pass PROPERTY PASS_REGULAR_EXPRESSION "Enum received: Medium")
add_cli_exe(digit_args digit_args.cpp)
add_test(NAME digit_args COMMAND digit_args -h)
set_property(TEST digit_args PROPERTY PASS_REGULAR_EXPRESSION "-3{3}")
add_cli_exe(modhelp modhelp.cpp)
add_test(NAME modhelp COMMAND modhelp -a test -h)
set_property(TEST modhelp PROPERTY PASS_REGULAR_EXPRESSION "Option -a string in help: test")
add_subdirectory(subcom_in_files)
add_test(NAME subcom_in_files COMMAND subcommand_main subcommand_a -f this.txt --with-foo)
set_property(TEST subcom_in_files PROPERTY PASS_REGULAR_EXPRESSION "Working on file: this\.txt"
"Using foo!")
add_cli_exe(formatter formatter.cpp)
add_cli_exe(nested nested.cpp)
add_cli_exe(subcom_help subcom_help.cpp)
add_test(NAME subcom_help_normal COMMAND subcom_help sub --help)
add_test(NAME subcom_help_reversed COMMAND subcom_help --help sub)
add_cli_exe(retired retired.cpp)
add_test(NAME retired_retired_test COMMAND retired --retired_option)
add_test(NAME retired_retired_test2 COMMAND retired --retired_option 567)
add_test(NAME retired_retired_test3 COMMAND retired --retired_option2 567 689 789)
add_test(NAME retired_deprecated COMMAND retired --deprecate 19 20)
set_property(TEST retired_retired_test PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
set_property(TEST retired_retired_test2 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
set_property(TEST retired_retired_test3 PROPERTY PASS_REGULAR_EXPRESSION "WARNING.*retired")
set_property(TEST retired_deprecated PROPERTY PASS_REGULAR_EXPRESSION "deprecated.*not_deprecated")
#--------------------------------------------
add_cli_exe(custom_parse custom_parse.cpp)
add_test(NAME cp_test COMMAND custom_parse --dv 1.7)
set_property(TEST cp_test PROPERTY PASS_REGULAR_EXPRESSION "called correct")
#------------------------------------------------
# This executable is for manual testing and is expected to change regularly
add_cli_exe(tester testEXE.cpp)