cmdr-cxx
cmdr-cxx copied to clipboard
cmdr cxx version, a C++17 header-only command-line parser with hierarchical config data manager here
cmdr-cxx {#mainpage}
cmdr-cxx ^pre-release^ is a C++17 header-only command-line arguments parser, config manager, and application
framework. As a member of #cmdr series, it provides a
fully-functional Option Store (Configuration Manager) for your hierarchical configuration data.
See also golang version: cmdr.
Features
-
POSIX-Compliant command-line argument parser
- supports long flag (REQUIRED,
--help, short flag (-h), and aliases (--usage,--info, ...) - supports multi-level sub-commands
- supports short flag compat:
-vab==-v -a -b,-r3ap1zq==-r 3 -ap 1 -z -q - supports passthrough flag:
--will terminate the parsing - supports lots of data types for a flag: bool, int, uint, float, string, array, chrono duration, ...
- allows user-custom data types
- automated help-screen printing (
-hhhto print the hidden items)
- supports long flag (REQUIRED,
-
Robust Interfaces
-
Hooks or Actions:
- global: pre/post-invoke
- flags: on_hit
- commands: on_hit, pre/post-invoke, invoke
-
Supports non-single-char short flag:
-ap 1 -
Supports for
-D+,-D-to enable/disable a bool option -
Supports sortable command/flag groups
-
Supports toggleable flags - just like a radio button group
-
Free style flags arrangements:
$ app main sub4 bug-bug2 zero-sub3 -vqb2r1798b2r 234 --sub4-retry1 913 --bug-bug2-shell-name=fish ~~debug --int 67 -DDD --string 'must-be' --long 789 -
Smart suggestions for wrong command and flags
based on Jaro-Winkler distance. See Snapshot
-
Builtin commands and flags
- Help:
-h,-?,--help,--info,--usage, ...helpcommand:app help server pause==app server pause --help.
- Version & Build Info:
--version/--ver/-V,--build-info/-# version/versionscommand available.- Simulating version at runtime with
—-version-sim 1.9.1
- Help:
-
~~tree: lists all commands and sub-commands.~~debug: print the debugging info--no-color: disable terminal color in outputting--config <location>: specify the location of the root config file. [only for yaml-loader]
-
Verbose & Debug:
—verbose/-v,—debug/-D,—quiet/-q -
Supports
-I/usr/include -I=/usr/include-I /usr/include -I:/usroption argument specifications Automatically allows those formats (applied to long option too):-I file,-Ifile, and-I=files-I 'file',-I'file', and-I='files'-I "file",-I"file", and-I="files"
-
Envvars overrides:
HELP=1 ./bin/test-app2-c2 server pauseis the equivalent of./bin/test-app2-c2 server pause --help -
Extensible external loaders:
cli.set_global_on_loading_externals(...); -
Extending internal actions for special operations auch as printing help screen...
-
-
Hierarchical Data Manager -
Option Store- various data types supports
- accusing the item with its dotted path key (such as
server.tls.certs.cert-bundle) - See also Fast Doc section.
Status
- v0.5.0 - upgraded to compliant with cxx20 and later
- v0.3.1 - bugs fixed and added constexpr compiler_name and a new test: test-compiler;
- v0.3.0 - bugs fixed and sync codes for main compilers;
- v0.2.27 - LICENSE Changed to Apache 2.0; better adaptive in cmake import;
- v0.2.25 - follow windows virtual environment changed in adaptation; format codes with better style
- v0.2.23 - improved ci scripts, traits, detections, and more for better cross-platform portability;
- v0.2.21 - bug fixed; add more utilities: mmap, thread pool, factory, pipeable, ...;
- v0.2.20 - changed OS_xxx macros; added more portability;
- v0.2.19 - bug fixed
- v0.2.17 - added to homebrew.
- v0.2.15 - bug fixed
- v0.2.13 - any kind of fixes; added priority-queue; added
ASSERTIONS_ONLY,NO_ASSERTIONS_ONLY; addedis_iterable, added vector_to_string(vec);defer<T>&defer<bool>; many improvements merged; - v0.2.11 - maintained
- v0.2.10 - MSVC (Build Tool 16.7.2+, VS2019 passed), and others improvements (bash completion, ...)
- v0.2.9 - various fixes, improvements
- v0.2.8 - fixed cmdr11Config.cmake for importing transparently
- v0.2.7 -
auto &cli = cmdr::create(...) - v0.2.5 - public release starts
CXX 17 Compilers:
- gcc 10+: passed
- clang 12+: passed
- msvc build tool:
- 17.2.32505.173 (VS2022 or Build Tool) passed
- OLD: 16.7.2, 16.8.5 (VS2019 or Build Tool) passed
- NEW: VS2022 passed
Snapshots
cmdr-cxx prints an evident, clear, and logical help-screen. Please proceed the more snapshots
at #1 - Gallery.
Bonus
Usages
Local Deployment
Homebrew
cmdr-cxx can be installed from homebrew:
brew install hedzr/brew/cmdr-cxx
CMake Standard
cmdr-cxx is findable via CMake Modules.
You could install cmdr-cxx manually:
git clone https://github.com/hedzr/cmdr-cxx.git
cd cmdr-cxx
cmake -S . -B build/
cmake --build build/
cmake --install build/
# Or:
# cmake --build build/ --target install
#
# Sometimes sudo it:
# sudo cmake --build build/ --target install
# Or:
# cmake --install build/ --prefix ./install --strip
# sudo cp -R ./install/include/* /usr/local/include/
# sudo cp -R ./install/lib/cmake/cmdr11 /usr/local/lib/cmake/
cd ..
rm -rf cmdr-cxx
dependencies
To build cmdr-cxx, you might install these components at first:
- yaml-cpp
The typical install command could be brew install yaml-cpp,
For more information, please refer to the chapter Others.
Integrate to your cmake script
After installed at local cmake repository (Modules), cmdr-cxx can be integrated as your CMake module. So we might find
and use it:
find_package(cmdr11 REQUIRED)
add_executable(my-app)
target_link_libraries(my-app PRIVATE cmdr11::cmdr11)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
CXX_EXTENSIONS ON
)
Or you can download deps-cmdr11.cmake and include it:
add_executable(my-app)
include(deps-cmdr11) # put deps-cmdr11.cmake into your cmake module path at first
add_cmdr_cxx_to(my-app)
Short example
#include <cmdr11/cmdr11.hh>
#include "version.h" // xVERSION_STRING
int main(int argc, char *argv[]) {
auto &cli = cmdr::cli("app2", xVERSION_STRING, "hedzr",
"Copyright © 2021 by hedzr, All Rights Reserved.",
"A demo app for cmdr-cxx library.",
"$ ~ --help");
try {
using namespace cmdr::opt;
cli += sub_cmd{}("server", "s", "svr")
.description("server operations for listening")
.group("TCP/UDP/Unix");
{
auto &t1 = *cli.last_added_command();
t1 += opt{(int16_t)(8)}("retry", "r")
.description("set the retry times");
t1 += opt{(uint64_t) 2}("count", "c")
.description("set counter value");
t1 += opt{"localhost"}("host", "H", "hostname", "server-name")
.description("hostname or ip address")
.group("TCP")
.placeholder("HOST[:IP]")
.env_vars("HOST");
t1 += opt{(int16_t) 4567}("port", "p")
.description("listening port number")
.group("TCP")
.placeholder("PORT")
.env_vars("PORT", "SERVER_PORT");
t1 += sub_cmd{}("start", "s", "startup", "run")
.description("start the server as a daemon service, or run it at foreground")
.on_invoke([](cmdr::opt::cmd const &c, string_array const &remain_args) -> int {
UNUSED(c, remain_args);
std::cout << c.title() << " invoked.\n";
return 0;
});
auto &s1 = *t1.last_added_command();
s1 += cmdr::opt::opt{}("foreground", "f")
.description("run at fg");
t1 += sub_cmd{}("stop", "t", "shutdown")
.description("stop the daemon service, or stop the server");
t1 += sub_cmd{}("pause", "p")
.description("pause the daemon service");
t1 += sub_cmd{}("resume", "re")
.description("resume the paused daemon service");
t1 += sub_cmd{}("reload", "r")
.description("reload the daemon service");
t1 += sub_cmd{}("hot-reload", "hr")
.description("hot-reload the daemon service without stopping the process");
t1 += sub_cmd{}("status", "st", "info", "details")
.description("display the running status of the daemon service");
}
} catch (std::exception &e) {
std::cerr << "Exception caught for duplicated cmds/args: " << e.what() << '\n';
CMDR_DUMP_STACK_TRACE(e);
}
return cli.run(argc, argv);
}
It is a simple program.
Fast Document
Lookup a command and its flags
The operator () (cli("cmd1.sub-cmd2.sub-sub-cmd") ) could be used for retrieving a command (cmdr::opt::cmd& cc)
from cli:
auto &cc = cli("server");
CMDR_ASSERT(cc.valid());
CMDR_ASSERT(cc["count"].valid()); // the flag of 'server'
CMDR_ASSERT(cc["host"].valid());
CMDR_ASSERT(cc("status").valid()); // the sub-command of 'server'
CMDR_ASSERT(cc("start").valid()); // sub-command: 'start'
CMDR_ASSERT(cc("run", true).valid()); // or alias: 'run'
Once cc is valid, use [] to extract its flags.
The dotted key is allowed. For example: cc["start.port"].valid().
CMDR_ASSERT(cli("server.start").valid());
CMDR_ASSERT(cli("server.start.port").valid());
// get flag 'port' of command 'server.start':
CMDR_ASSERT(cc["start.port"].valid());
Extract the matched information of a flag
While a flag given from command-line is matched ok, it holds some hit info. Such as:
auto &cc = cli("server"); // get 'server' command object
CMDR_ASSERT(cc.valid()); // got ok?
CMDR_ASSERT(cc["count"].valid());
CMDR_ASSERT(cc["count"].hit_long()); // if `--count` given
CMDR_ASSERT(cc["count"].hit_long() == false); // if `-c` given
CMDR_ASSERT(cc["count"].hit_count() == 1); // if `--count` given
CMDR_ASSERT(cc["count"].hit_title() == "c"); // if `-c` given
CMDR_ASSERT(cli["verbose"].hit_count() == 3); // if `-vvv` given
// hit_xxx are available for a command too
CMDR_ASSERT(cc.hit_title() == "server"); // if 'server' command given
The value of a flag from command-line will be saved into Option Store, and extracted by shortcut cmdr::get_for_cli()
. For example:
auto verbose = cmdr::get_for_cli<bool>("verbose");
auto hostname = cmdr::get_for_cli<std::string>("server.host");
In Option Store, the flag value will be prefixed by "app.cli.", and get_for_cli wraps transparently.
The normal entries in
Options Storeare prefixed by string"app.". You could define another one of course.
To extract the normal configuration data, cmdr::set and cmdr::get are best choices. They will wrap and unwrap the
prefix app transparently.
auto verbose = cmdr::get<bool>("cli.verbose");
auto hostname = cmdr::get<std::string>("cli.server.host");
If you wanna extract them directly:
auto verbose = cmdr::get_store().get_raw<bool>("app.cli.verbose");
auto hostname = cmdr::get_store().get_raw<std::string>("app.cli.server.host");
auto verbose = cmdr::get_store().get_raw_p<bool>("app.cli", "verbose");
auto hostname = cmdr::get_store().get_raw_p<std::string>("app.cli", "server.host");
Set the value of a config item
Every entry in Option Store that we call it a config item. The entries are hierarchical. So we locate it with a dotted
key path string.
A config item is free for data type dynamically. That is saying, you could change the data type of a item at runtime. Such as setting one entry to integer array, from integer originally.
But it is hard for coding while you're working for a c++ program.
cmdr::set("wudao.count", 1);
cmdr::set("wudao.string", "str");
cmdr::set("wudao.float", 3.14f);
cmdr::set("wudao.double", 2.7183);
cmdr::set("wudao.array", std::vector{"a", "b", "c"});
cmdr::set("wudao.bool", false);
std::cout << cmdr::get<int>("wudao.count") << '\n';
auto const &aa = cmdr::get< std::vector<char const*> >("wudao.array");
std::cout << cmdr::string::join(aa, ", ", "[", "]") << '\n';
// Or: maybe you will like to stream out a `variable` with standard format.
cmdr::vars::variable& ab = cmdr::get_app().get("wudao.array");
std::cout << ab << '\n';
cmdr::vars::variable
cmdr-cxx provides stream-io on lots of types via cmdr::vars::variable, take a look for further.
Bundled Utilities or Helpers
There are some common cross-platform helper classes. Its can be found in these headers:
-
cmdr_defs.hh
-
cmdr_types.hh
-
cmdr_type_checks.hh
-
cmdr_utils.hh
-
cmdr_dbg.hh
-
cmdr_log.gg
-
cmdr_chrono.hh
-
cmdr_if.hh
-
cmdr_ios.hh
-
cmdr_mmap.hh
-
cmdr_os_io_redirect.hh
-
cmdr_path.hh
-
cmdr_process.hh
-
cmdr_pool.hh
-
cmdr_priority_queue.hh
-
cmdr_string.hh
-
cmdr_terminal.hh
Features to improve your app arch
cmdr-cxx provides some debugging features or top view to improve you design at CLI-side.
Default Action
We've been told that we can bind an action (via on_invoke) to a (sub-)command:
t1 += sub_cmd{}("start", "s", "startup", "run")
.description("start the server as a daemon service, or run it at foreground")
.on_invoke([](cmdr::opt::cmd const &c, string_array const &remain_args) -> int {
UNUSED(c, remain_args);
std::cout << c.title() << " invoked.\n";
return 0;
});
For those commands without binding to on_invoke, cmdr-cxx will invoke a default one, For example:
t1 += sub_cmd{}("pause", "p")
.description("pause the daemon service");
While the end-user is typing and they will got:
❯ ./bin/test-app2-c2 server pause
INVOKING: "pause, p", remains: .
command "pause, p" hit.
Simple naive? Dislike it? The non-hooked action can be customized.
User-custom non-hooked action
Yes you can:
#if CMDR_TEST_ON_COMMAND_NOT_HOOKED
cli.set_global_on_command_not_hooked([](cmdr::opt::cmd const &, string_array const &) {
cmdr::get_store().dump_full_keys(std::cout);
cmdr::get_store().dump_tree(std::cout);
return 0;
});
#endif
~~debug
Special flag has the leading sequence chars ~~.
~~debug can disable the command action and print the internal hitting information.
❯ ./bin/test-app2-c2 server pause -r 5 -c 3 -p 1357 -vvv ~~debug
command "pause, p" hit.
- 1 hits: "--port=PORT, -p" (hit title: "p", spec:0, long:0, env:0) => 1357
- 1 hits: "--retry, -r" (hit title: "r", spec:0, long:0, env:0) => 5
- 1 hits: "--count, -c" (hit title: "c", spec:0, long:0, env:0) => 3
- 3 hits: "--verbose, -v" (hit title: "v", spec:0, long:0, env:0) => true
- 1 hits: "--debug, -D, --debug-mode" (hit title: "debug", spec:true, long:true, env:false) => true
Another one in Gallary:
-DDD
Triple D means --debug --debug --debug. In ~~debug mode, triple D can dump more underlying value structure
inside Option Store.
The duplicated-flag exception there among others, is expecting because we're in testing.
~~debug --cli -DDD
The values of CLI flags are ignored but ~~cli can make them raised when dumping. See the snapshot
at #1 - Gallary.
~~tree
This flag will print the command hierarchical structure:
Remove the cmdr-cxx tail line
By default a citation line(s) will be printed at the ends of help screen:
I knew this option is what you want:
auto &cli = cmdr::cli("app2", CMDR_VERSION_STRING, "hedzr",
"Copyright © 2021 by hedzr, All Rights Reserved.",
"A demo app for cmdr-c11 library.",
"$ ~ --help")
// remove "Powered by cmdr-cxx" line
.set_no_cmdr_endings(true)
// customize the last line except cmdr endings
// .set_tail_line("")
.set_no_tail_line(true);
The "Type ... ..." line could be customized by set_tail_line(str), so called tail line,. Or, you can disable
the tail line by set_no_tail_line(bool).
The Powered by ... line can be disabled by set_no_cmdr_ending, so-called cmdr-ending line.
External Loaders
There is a builtin addon yaml-loader for loading the external config files in the pre-defined directory locations. As
a sample to show you how to write a external loader, yaml-loader will load and parse the yaml config file and merge it
into Option Store.
TODO:
conf.dnot processed now.
test-app-c1 demonstrates how to use it:
{
using namespace cmdr::addons::loaders;
cli.set_global_on_loading_externals(yaml_loader{}());
}
The coresponding cmake fragment might be:
#
# For test-app-c1, loading the dependency to yaml-cpp
#
include(loaders/yaml_loader)
add_yaml_loader(test-app2-c1)
This add-on needs a third-part library,
yaml-cpp, presented.
Specials
Inside cmdr-cxx, there are many optimizable points and some of them in working.
-
[x] enable dim text in terminal
CMDR_DIM=1 ./bin/test-app2-c2 main sub4 bug-bug2 -
[x]
--no-color: do NOT print colorful text with Terminal Escaped Sequences, envvarsPLAINorNO_COLORavailable too../bin/test-app2-c2 --no-color PLAIN=1 ./bin/test-app-c2 -
[x] enable very verbose debugging
#define CMDR_ENABLE_VERBOSE_LOG 1 #include <cmdr11/cmdr11.hh> -
[x] enable unhandled exception handler
cmdr::debug::UnhandledExceptionHookInstaller _ueh{}; // for c++ exceptions cmdr::debug::SigSegVInstaller _ssi{}; // for SIGSEGV ... return cli.run(argc, argv); -
[x]
-hhh(i.e.--help --help --help) will print the help screen with those invisible items (the hidden commands and flags). -
[x] Tab-stop position is adjustable based the options automatically
-
[x] The right-side of a line, in the help screen, command/flag decriptions usually, can be wrapped and aligned along the tab-stop width.
-
[ ] More...
Use cmdr-cxx As A New App Skeletion
That's very appreciated!
PROs
- cmdr-like programmatical interface.
- See also Short example and Fast Document
- See also test app sources
- A fully functional Hierarchical Configurable Data Management Mechanism (so called
Option Store) is ready for box. - Uses debug outputting macros:
cmdr_print,cmdr_debug,cmdr_trace(whileCMDR_ENABLE_VERBOSE_LOGdefined), see cmdr_log.hh
Contributions
Build
gcc 10+: passed
clang 12+: passed
msvc build tool 16.7.2, 16.8.5 (VS2019 or Build Tool) passed
# configure
cmake -DENABLE_AUTOMATE_TESTS=OFF -S . -B build/
# build
cmake --build build/
# install
cmake --build build/ --target install
# sometimes maybe sudo: sudo cmake --build build/ --target install
For msvs build tool, vcpkg should be present, so cmake configure command is:
cmake -DENABLE_AUTOMATE_TESTS=OFF -S . -B build/ -DCMAKE_TOOLCHAIN_FILE=%USERPROFILE%/work/vcpkg/scripts/buildsystems/vcpkg.cmake
If you clone vcvpkg source and bootstrap it at:
%USERPROFILE%/work/vcpkg.
Windows Server 2019 Core & VSBT
set VCPKG_DEFAULT_TRIPLET=x64-windows
mkdir %USERPROFILE%/work
cd %USERPROFILE%/work
git clone ...
REM launch vsbt build env
SETX PATH "%PATH%;C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\Common7\Tools"
LaunchDevCmd.bat
cd cmdr-cxx
cmake -DENABLE_AUTOMATE_TESTS=OFF -S . -B build/ -DCMAKE_TOOLCHAIN_FILE=%USERPROFILE%/work/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build/
ninja, [Optional]
We used ninja for faster building.
Other Options
BUILD_DOCUMENTATION=OFFENABLE_TESTS=OFF
Prerequisites
To run all automated tests, or, you're trying to use yaml-loader add-on, some dependencies need to prepared at first,
by youself, maybe.
Catch2
If the tests are enabled, Catch2 will be downloaded while cmake configuring and
building automatically. If you have a local cmake-findable Catch2 copy, more attentions would be appreciated.
Others
In our tests, test-app2-c1 and yaml-loader will request yaml-cpp is present.
Optional
Linux
sudo apt install -y libyaml-cpp-dev
For CentOS or RedHat:
sudo dnf install yaml-cpp yaml-cpp-devel yaml-cpp-static
macOS
brew install yaml-cpp
Windows
vcpkg install yaml-cpp
NOTE that vcpkg want to inject the control file for cmake building, see also Using vcpkg with CMake
Run the examples
The example executables can be found in ./bin after built. For example:
# print command tree (with hidden commands)
./bin/cmdr11-cli -hhh ~~tree
- ~~You will get them from release page~~.
- TODO: we will build a docker release later.
- Run me from a online CXX IDE.
Hooks in cmdr-cxx
-
auto & cli = cmdr::get_app() -
Register actions:
void register_action(opt::Action action, opt::types::on_internal_action const &fn);In your pre_invoke handler, some actions called
internal actionscould by triggered via the returnedActioncode.The
Actioncodes is extensible, followed by aon_internal_actionhandler user-customized. -
Hooks
xxx_handlersors(_externals) means you can specify it multiple times.-
set_global_on_arg_added_handlers,set_global_on_cmd_added_handlers -
set_global_on_arg_matched_handlers,set_global_on_cmd_matched_handlers -
set_global_on_loading_externals -
set_global_on_command_not_hookedcmdr prints some hitting info for a sub-command while no
on_invokehandler associated with it.Or, you can specify one yours via
set_global_on_command_not_hooked. -
set_global_on_post_run_handlers -
set_on_handle_exception_ptr -
set_global_pre_invoke_handler,set_global_post_invoke_handler
-
Thanks to JODL
Thanks to JetBrains for donating product licenses to help develop **
cmdr-cxx**
LICENSE
Apache 2.0