This ALib Module supports parsing and processing command line parameters. Such processing is not only useful with "pure CLI commands" (shell applications) but also with software that only optionally receive arguments, may it be daemons or GUI-driven applications.
Command line parameter "languages" are in most cases not defined in very consistent ways, for example in comparison to programming languages. In contrast, they are very much dependent on the field of application. In favor to simplicity of usage, in most cases no consistent grammar and syntax definition exists. Different "commands" sometimes share "parameters", others do not and often "parameters" may be given without a command. Then, commands and parameters are often allowed to be given in arbitrary order, parameters may allow to omit the hyphens and so forth.
Therefore, by definition, helper libraries like this one, can never support each and every use case and any unforeseen tweak that a programmer wants to implement for his specific CLI.
As a result, the software found in this module:
This means, that using the module provides basic tools and probably a guideline on how to implement the CLI processing, but not a full-featured CLI processor that just needs a grammar definition input and some callback functions.
This sort of "incompleteness" can also positively phrased: The implementable grammar and syntax of the CLI interface is open for implementing any weird interface and not limited to a certain scheme.
One major burden that this ALib Module may relieve a user from, is that CLI command names and other syntax tokens, and probably more important the error messages and help texts that the CLI interface produces, are duly externalized and thus can be easily translated into other human languages. This is achieved by being a ALib Camp and thus leveraging inner namespace alib::lang::resources of module ALib BaseCamp.
The classes found in this namespace make a lot of use of ALib Enum Records retrieved from an instance of type ResourcePool, usually the one found in the singleton of the associated Camp.
Therefore, it is a good advice to get familiar with the features provided by inner namespace alib::lang::resources of module ALib BaseCamp as a start. That module in turn uses so called Resourced ALib Enum Records which are provided with module ALib Enums.
Or other way round: The tutorial found below might also be a good starter to understand externalized resources and ALib Enums in their basics and for therefore the better better advice might be to just continue reading.
Module ALib CLI offers the following features:
As a preparation to parsing command line arguments, instances of the following structs are to be created and duly filled with data:
Please use the links above and quickly read the general descriptions of the types.
Such struct definition is considered static data, and as such are usually stored in so called resources. How resourced data is used to automatically create and fill the structs is shown in the next chapter 3. Tutorial Sample.
All enums which are using resourced ALib Enum Records are demanding a specific record type to be associated to a custom enumeration.
In addition, for such custom enum types, a specialization of T_Resourced, has to be provided, which is used to load further resource strings.
The following table summarizes the types, the associated ALib Enum Record type and the additional resource strings that need to be available:
Object Type | Record Type To Use | Additional Resource Strings |
---|---|---|
CommandDecl | ERCommandDecl | "THlpCmdSht_NN": Short version of help text for each parameter. "THlpCmdLng_NN": Long version of help text for each parameter |
ParameterDecl | ERParameterDecl | "THlpParSht_NN": Short version of help text for each parameter. "THlpParLng_NN": Long version of help text for each parameter. |
OptionDecl | EROptionDecl | "TOptUsg_NN": Help text for each option describing its usage. "TOptHlp_NN": Help text for each Option. |
ExitCodeDecl | ERExitCodeDecl | "TExit_NN": A format string returned by method ExitCodeDecl::FormatString. |
With the above resourced definitions in place, class CommandLine is used to bootstrap all enum records to be ready for parsing the command line.
To avoid cluttering this types' interface, various helper methods have been put into friend class CLIUtil.
See the reference documentation of types CommandLine and CLIUtil for details.
Alternatively, just follow the tutorial provided in the next chapter.
Due to the usage of resourced strings, and even more due to the usage of Resourced ALib Enum Records, to start a new project using the command line facilities of this module becomes more complex than one might think.
The best way out is the provision of a "template" project, which can be used for jump starting into an own development.
Such template is provided with this tutorial chapter!
ALIB_BASE_DIR/src/samples/CLI/sample.cpp
The sample application is a little similar to the GNU/Linux "date" command. Its functionality is described as follows:
With this project scope in mind, let's start coding.
The following includes are necessary for this sample.
Commands, options, parameters and exit codes of our software are easily defined with simple C++ enumerations:
The little magic comes with assigning ALib Enum Records to these enumerations using the helper macros. For each of the enumerations, a corresponding record type is defined with this module:
This assignment, entitles the main class of this module to "know" how to parse the objects from the command line.
The enum records that were just assigned to the enums, need to be filled with values. Such values can be automatically loaded from resources. To define such resources, the easiest way is to create just another ALib Camp. Of course using this term here does not mean that this will be a part of ALib. But ALib exports its camp concept to the user and here we make use of this.
In this simple sample, all that this camp does is defining and parsing string data which comprises the enum records plus some additional help and information strings.
ALib Camps are singleton objects, and as such we create one global instance:
If you only have a brief look at the string data defined in the code above, as an experienced programmer, you should easily grasp the general idea of what sort of information is provided with the resourced ALib Enum Records of this camp.
Now, as the camp is defined, a second macro is used which tells ALib where to find the resources for each enumeration:
Now, we are already set to implement function main()!
To make this easier to read, we pull the command processing into a separate function, which is forward-declared like this:
With this extraction of the custom command processing itself, function main becomes quite short and straightforward. Note the 7 steps commented in the source:
Finally, we can look at our custom command processing function. Also here, just note the source comments. Everything should be quite understandable:
You might have noticed that this function uses static utility methods of class CLIUtil to generate help texts for general use, or specifically for commands, options and parameters.
After compiling this sample to binary "Sample" it can be invoked from the command line. Lets have some tries:
1. Invocation without parameters
This is already a special case that method processCLI explicitly handles with the same code as if parameter now was given.
Sample
The result shown on the console is:
2024-12-15 11:41:12 <Exit code 0>
2. Command 'now'
With command now
Sample now
the same result is shown:
2024-12-15 11:41:12 <Exit code 0>
(Note that the date displayed in this tutorial is when the unit tests were run the last time before creating the documentation with doxygen.)
3. With option 'format'
Option format allows specifying a date format, as documented with CalendarDateTime::Format. The format string can be given concatenated to the option name using an equal sign '=' or just as a next argument (with a whitespace instead of the equl sign):
Sample --format="dd MMM YYYY"
Outputs:
Dec 15, 2024 <Exit code 0>
4. Command 'file'
The command file expects a next command line argument (separated by whitespaces), that specifies a file or directory name. We choose "/home" here and hope that this is available on the machine where this documentation was generated:
Sample file /home
Outputs:
2023-11-03 09:07:31 <Exit code 0>
5. Erroneous input
Lets see what happens if we omit the mandatory parameter of command file:
Sample file
Outputs:
Error: no filename given with command 'file' Usage: date file FILENAME <Exit code 102>
Similar to this, we might omit the parameter of option format :
Sample --format
Outputs:
E1: <cli::MissingOptionValue> Missing argument(s) of option <format> found with CLI argument[0]="--format". Expected 1, given 0. [@ /home/dev/A-Worx/ALib/src/alib/cli/arguments.cpp:118 from 'Option::Read()' by 'TID=0x0000770F13DD6740'] E2: <cli::ParsingOptions> Error parsing option. Usage: --format[=]"placholders" [@ /home/dev/A-Worx/ALib/src/alib/cli/commandline.cpp:121 from 'CommandLine::ReadOptions()' by 'TID=0x0000770F13DD6740'] <Exit code 10>
In contrast to the previous output, this one shows a very different error message. The reason for this is as follows: While the missing parameter of command file is detected by our simple sample program (which generates a quick error message), the missing mandatory parameter of option –format can be detected by the ALib CLI internally and automatically. Here, the library throws an Exception, which is "annotated" with further information while it is passed through the unwinded execution stack.
Consequently, this sample should be extended to fetch and analyse the internal exceptions and create a similar output as in the first case. We kept this here as it is to a) keep the sample code simple and b) demonstrate the power of ALib Exceptions in respect to the generation of human-readable, formatted error output.
More complex projects that use this library should follow the approach to use and throw ALib exceptions anywhere possible. Only in the very end (for example at the end of the main() method), such exceptions should get "translated" to one one of the more general exit codes that a command line software usually returns. The benefits: Error handling is then all in one place and internally it is all done nicely with C++ exception handling.
A last erroneous input we want to test here is the provision of unknown commands or options:
Sample unknown
outputs:
Error: Unknown command given "unknown" <Exit code 100>
while
Sample --unknown
shows:
Error: Unknown option given "--unknown" <Exit code 101>
6. Help texts
The sample of this tutorial does not show too much code that generates help messages. The reader might be surprised about the output of
Sample --help
The library is able to generate the following output:
---------------------------------------------------------------------- Command line tool 'date'. V. 2412.0 (in fact a sample application only) (c) 2023-2024 AWorx GmbH. Published under MIT License (Open Source). For more information see: https://alib.dev ---------------------------------------------------------------------- ABOUT date This is a sample application provided with C++ library 'ALib' to demonstrate the use of its sub-module "ALib CLI". EXIT CODES: 0: OK Success (no error). 100: ErrUnknownCommand An unknown command was given. Valid commands are 'now' and 'file' 101: ErrUnknownOption An unknown option was given. The only valid option is '--format='FORMATSPEC'. 102: ErrMissingFilename Command 'file' given without a filename argument. 103: ErrUnknownHelpTopic Command or option 'help' given without an unknown subtopic. 255: ErrInternalError Unspecified internal error. USAGE: date [format="FORMATSPEC" [now]|[file FILENAME] OPTIONS: --format[=]"placholders" --help[[=]TOPIC] COMMANDS: * date now Reports the actual date/time * date file FILENAME Returns the date/time of a file. * date help [TOPIC] Displays usage information. <Exit code 0>
Furthermore, help topics are supported. Possible topics are commands, options and parameters as shown in these final samples:
Sample --help now
---------------------------------------------------------------------- Command line tool 'date'. V. 2412.0 (in fact a sample application only) (c) 2023-2024 AWorx GmbH. Published under MIT License (Open Source). For more information see: https://alib.dev ---------------------------------------------------------------------- Help on command <now>: USAGE: date now DESCRIPTION: Reports the actual date/time. May be omitted, as this is the default if no command is given. PARAMETER DESCRIPTION: <Exit code 0>
Sample --help file
---------------------------------------------------------------------- Command line tool 'date'. V. 2412.0 (in fact a sample application only) (c) 2023-2024 AWorx GmbH. Published under MIT License (Open Source). For more information see: https://alib.dev ---------------------------------------------------------------------- Help on command <file>: USAGE: date file FILENAME DESCRIPTION: Returns the last modification date/time of a file. PARAMETER DESCRIPTION: * FILENAME Mandatory parameter of command 'file. <Exit code 0>
Sample --help format
---------------------------------------------------------------------- Command line tool 'date'. V. 2412.0 (in fact a sample application only) (c) 2023-2024 AWorx GmbH. Published under MIT License (Open Source). For more information see: https://alib.dev ---------------------------------------------------------------------- Help on option <format>: USAGE: --format[=]"placholders" DESCRIPTION: Sets the output format. The format specification is given with documentation of ALib method CalendarDateTime::Format, found here: https://alib.dev/classaworx_1_1lib_1_1system_1_1CalendarDateTime.html <Exit code 0>
Independent of the use of this ALib Module, the original command line strings are always available through the global ALib variables ARG_C, ARG_VN and ARG_VW (if set by the user's main()
-function properly).
Class CommandLine exposes field OptionArgsIgnored, that hold all CLI arguments that start with a hyphen '-'
and are not recognized by OptionDecl. A software might either generate an error if unrecognized options are left (just like the tutorial-sample of the previous chapter does), or pass this list to other parts and libraries that a software is using..
With the use of camp ALib Configuration such use of CLI arguments outside of this camp already occurs: Configuration variables can be defined using CLI parameters the same as by using environment variables, configuration files or other data sources. Such use is still be possible, independent of the CLI interface.
The other way round, arguments may be removed from the internal list held by this class, prior to having this class processing the arguments! Note that this is a must for arguments that do not start with a hyphen (and are addressing other software parts), because such arguments would lead to "unknown command" errors. Such removal has to be done after the invocations of the various Define methods and prior to invoking CommandLine::ReadOptions.