ALib C++ Library
Library Version: 2412 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
ALib Module CLI - Programmer's Manual

1. Introduction

Note
While being included in previous versions in an alpha state, with ALib Release 2312 this module is considered to be in a rather stable state for the first time.
Still, functionality may change in the future (the ALib policy in general is open to incompatible changes with new releases), but so far ALib CLI works quite well in an actual software project.

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.

Note
This manual and library uses four terms for CLI input:
  • commands: input strings that do not start with hyphens
  • options: input strings that do start with one or two hyphens
  • parameters: input strings that are followed by commands or options or that are attached to options using for example an equal sign '='.
  • arguments: any of the three above.

1.1 The Challenge of Command Line Interfaces

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:

  • Uses an open, pragmatic code style. For example, fields are mostly public and the interface methods allow data modifications between the several steps of their invocation.
  • Is incomplete and even partly inconsistent in respect to what can be defined as commands, parameters, etc. and what action can be "automated" with the information provided.

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.

1.2 ALib Enum Records And ALib Resources

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.

1.3 Features

Module ALib CLI offers the following features:

  • Rather simple interface through central class CommandLine.
  • Resourced and optionally localized command names, output and help texts
  • If commands or options expect mandatory arguments, such arguments may be read automatically and attached to the command objects as additional information.
    Optional arguments might also be read automatically, depending on their specification.
  • Support for general help output (e.g., with –help ) as well as help output on certain topics (e.g., with –help commandXYZ).
  • CLI input - or parts of it that have not been "consumed" - may be passed forward into other parts and libraries embedded in an application.
    For example, ALib module ALib Configuration allows overwriting configuration data stored in configuration files transparently with by giving command line options. CLI input which is not processed by ALib CLI, is filtered out and can easily be forwarded to module ALib Configuration, or other parts of the software that accepts CLI input.
  • ALib Exception Handling with support for automatic "translation" of C++ exceptions to a (usually) more narrow set of application exit codes.

2. Classes and Concepts

2.1 Defining Commands, Parameters, Options and ExitCodes

As a preparation to parsing command line arguments, instances of the following structs are to be created and duly filled with data:

  1. CommandDecl
  2. ParameterDecl
  3. OptionDecl
  4. ExitCodeDecl

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.

2.2 Main Class 'CommandLine'

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.

3. Tutorial Sample

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!

Note
The full source code of the sample can be seen (and copied!) here: src/samples/CLI/sample.cpp.
Because the documentation tool used here (overall exquisite software Doxygen ) destroys some of the formatting, it is recommended to copy the source directly from the source file located at:
  ALIB_BASE_DIR/src/samples/CLI/sample.cpp
Furthermore, it is recommended to split it's contents into proper header and source files.

The sample application is a little similar to the GNU/Linux "date" command. Its functionality is described as follows:

  • Without any argument, the current date and time should be written (to the standard output stream).
  • Alternatively, if argument "now" is given, the same should be performed.
  • To receive the modification date of a file or directory, the command 'file' has to be given along with a non-optional next argument containing the filename.
  • Option '--format' allows modifying the date format of the output. The format string has to be attached to the option using equal sign '=' and may (must) be quoted either with single '\'' or double marks "\"".
  • A help output shall be available with either a command "help" or option "--help" In the first case an optional help topic may be added separated by a whitespace, in the option case, such parameter shall be added using equal sign '=' (without whitespaces).
  • Finally, more than one command should be able to be passed, for example: date now file /etc which would first print the current date and time and in a second row the modification date of directory "/etc".

With this project scope in mind, let's start coding.

3.1 Needed includes

The following includes are necessary for this sample.

// Include necessary ALib CLI headers
#include "alib/cli/commandline.hpp"
#include "alib/compatibility/std_strings_iostream.hpp" // Support to write ALib strings and boxes to cout
#include "alib/compatibility/std_strings_functional.hpp" // Support to write ALib strings and boxes to cout
#include "alib/lang/basecamp/camp_inlines.hpp" // Add missing inline functions of class Camp
#include "alib/lang/basecamp/bootstrap.hpp" // Support customized module bootstrapping
#include "alib/enums/recordbootstrap.hpp" // Support for bootstrapping resourced enum records
#include "alib/time/datetime.hpp" // ALib date/time types
#include "alib/lang/system/calendar.hpp" // ALib calendar formatting
#include <filesystem> // C++ filesystem
// namespaces to use locally
using namespace alib;
using namespace alib::cli;

3.2 Definition of Enumerations

Commands, options, parameters and exit codes of our software are easily defined with simple C++ enumerations:

// #################################################################################################
// Enumerations of Commands, Parameters, Options and ExitCodes of the CLI application
// #################################################################################################
enum class Commands
{
Now = 1, ///< Returns the current date.
File = 2, ///< returns the modification date of a file or directory.
Help = 99, ///< Prints a help text.
};
enum class Options
{
Format = 0, ///< Overwrite the default format string.
Help = 99, ///< Show help text. (We allow this as option as well a command)
};
enum class Parameters
{
Filename = 0, ///< Used with command \e file to denote the file.
Topic = 1, ///< Used with command \e help to optionally denote a help topic.
};
enum class ExitCodes
{
OK = 0, ///< Success.
ErrUnknownCommand = 100, ///< Unkown command given.
ErrUnknownOption = 101, ///< Unkown option given.
ErrMissingFilename = 102, ///< Command "file" given without a filename.
ErrUnknownHelpTopic = 103, ///< Command or option "help" given without an unknown subtopic.
ErrInternalError = 255, ///< Unspecified internal error.
/// (this demo might be incomplete :-)
};

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.

3.3 Definition of a Custom Camp

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.

Note
ALib CLI does not force you to create a custom camp. Alternatively, the creation of an object of type ResourcePool would be enough. However, when you create your own more complex software, it might be a good advantage to have such camp in place from the start. A camp solves several problems, for example the often annoying C/C++ bootstrapping issues. And, as you will see, creating a module instead of a ResourcePool is not very complicated.

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.

// #################################################################################################
// The custom ALib module, needed to define externalized resources.
// #################################################################################################
class SampleCamp : public lang::Camp
{
public:
// Constructor. Passes version number and resource name to the module class
SampleCamp()
: Camp( "DATEMOD" )
{}
protected:
// Initialization of the module.
virtual void bootstrap( BootstrapPhases phase ) override
{
if( phase == BootstrapPhases::PrepareResources)
{
// Add bulk !
resourcePool->BootstrapBulk( ResourceCategory,
#define EOS ,
// ################################### Single Strings ######################################
"AppInfo", A_CHAR( "@HL-"
"Command line tool 'date'. V. {}.{} (in fact a sample application only)\n"
"(c) 2023-{} AWorx GmbH. Published under MIT License (Open Source).\n"
"For more information see: https://alib.dev\n"
"@HL-"),
// ######################################## Commands ######################################
"Commands", A_CHAR(
//enum ident minread Params
"1," "now" ",1" "," ","
"2," "file" ",1" ",filename" ","
"99," "help" ",1" ",topic" ) EOS
"Commands<", A_CHAR("datesample::Commands::"),
"THlpCmdSht_now", A_CHAR("Reports the actual date/time"),
"THlpCmdLng_now", A_CHAR("Reports the actual date/time. May be omitted, as this is the\n"
"default if no command is given.") EOS
"THlpCmdSht_file", A_CHAR("Returns the date/time of a file. "),
"THlpCmdLng_file", A_CHAR("Returns the last modification date/time of a file.") EOS
"THlpCmdSht_help", A_CHAR("Displays usage information. "),
"THlpCmdLng_help", A_CHAR("Displays usage information. Can also be given as an "
"option '--help'.") EOS
// ######################################## Options ######################################
"Options", A_CHAR(
//enum ident minread identChar in-arg-separ. args to consume ShortcutTo
"0," "format" ",1," "f," "=" ",1," ","
"99," "help" ",1," "h," "=" ",0," ) EOS
"Options<", A_CHAR("datesample::Options::"),
"TOptUsg_format", A_CHAR("--format[=]\"placholders\""),
"TOptHlp_format", A_CHAR("Sets the output format. The format specification is given with\n"
"documentation of ALib method CalendarDateTime::Format, found here:\n"
"https://alib.dev/classaworx_1_1lib_1_1system_1_1CalendarDateTime.html" ) ,
"TOptUsg_help" , A_CHAR("--help[[=]TOPIC]"),
"TOptHlp_help" , A_CHAR("Displays usage information.")
EOS
// ######################################## Parameters ######################################
"Parameters", A_CHAR(
//enum name minIdentLen identifier in-arg-sep delim args to consume isOptional
// (if empty -> mandatory!)
"0," "FILENAME" ",1," "" "," "=" "," ",-1" ",0" ","
"1," "TOPIC" ",1," "" "," "=" "," ",-1" ",1" ) EOS
"Parameters<", A_CHAR("datesample::Parameters::"),
"THlpParSht_FILENAME", A_CHAR("Mandatory parameter of command 'file."),
"THlpParLng_FILENAME", A_CHAR("Denotes the file that is used for retrieving the modification date.\n"
"This parameter is mandatory to command file and has to be appended\n"
"to this command, separated by '='"),
"THlpParSht_TOPIC" , A_CHAR("Optional parameter of command (or option) 'help'."),
"THlpParLng_TOPIC" , A_CHAR("Denotes a specific toopic that the help command should be verbose about.")
EOS
// ######################################## ExitCodes ######################################
"ExitCodes", A_CHAR(
//enum name assoc. cli exception
"0," "OK" ",-1" ","
"100," "ErrUnknownCommand" ",-1" ","
"101," "ErrUnknownOption" ",-1" ","
"102," "ErrMissingFilename" ",-1" ","
"103," "ErrUnknownHelpTopic" ",-1" ","
"255," "ErrInternalError" ",-1" ) EOS
"ExitCodes<", A_CHAR("datesample::"),
"TExit0" , A_CHAR("Success (no error).")
,"TExit100" , A_CHAR("An unknown command was given. Valid commands are 'now' and 'file'")
,"TExit101" , A_CHAR("An unknown option was given. The only valid option is '--format='FORMATSPEC'.")
,"TExit102" , A_CHAR("Command 'file' given without a filename argument.")
,"TExit103" , A_CHAR("Command or option 'help' given without an unknown subtopic.")
,"TExit255" , A_CHAR("Unspecified internal error.")
EOS
// ################################### Help Texts ######################################
"HlpCLIAppName", A_CHAR("date"),
"HlpUsage" , A_CHAR("date [format=\"FORMATSPEC\" [now]|[file FILENAME]"),
"HlpHdlOpts" , A_CHAR("OPTIONS:" ),
"HlpHdlCmds" , A_CHAR("COMMANDS:" ),
"HlpHdlExtCds" , A_CHAR("EXIT CODES:" ),
"HlpHdlUsage" , A_CHAR("USAGE:" ),
"HlpHdlDscr" , A_CHAR( "DESCRIPTION:" ),
"HlpHdlPDscr" , A_CHAR("PARAMETER DESCRIPTION:" ),
"HlpHdlTopic" , A_CHAR("Help on {} {!Q<>}:\n" ),
"HlpGeneral", A_CHAR(
"\nABOUT date\n"
"@>>"
"This is a sample application provided with C++ library 'ALib'\n"
"to demonstrate the use of its sub-module \"ALib CLI\"."
"\n@<<\n" )
EOS
// end of AddBulk()
nullptr );
}
else if( phase == BootstrapPhases::PrepareConfig )
{
}
}
// Termination this module. (Nothing to do.)
virtual void shutdown( ShutdownPhases phase ) override
{ (void) phase; }
}; // class SampleCamp

ALib Camps are singleton objects, and as such we create one global instance:

// The module singleton object
extern SampleCamp SAMPLE_CAMP;
SampleCamp SAMPLE_CAMP;

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.

3.4 Assigning the Resources to the Enumerations

Now, as the camp is defined, a second macro is used which tells ALib where to find the resources for each enumeration:

// Specifying our custom module to hold resources of our enum records
ALIB_RESOURCED_IN_MODULE( Commands , SAMPLE_CAMP, "Commands" )
ALIB_RESOURCED_IN_MODULE( Parameters, SAMPLE_CAMP, "Parameters" )
ALIB_RESOURCED_IN_MODULE( Options , SAMPLE_CAMP, "Options" )
ALIB_RESOURCED_IN_MODULE( ExitCodes , SAMPLE_CAMP, "ExitCodes" )

3.4 Function main()

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:

// forward declaration
ExitCodes processCLI( CommandLine& cli );

With this extraction of the custom command processing itself, function main becomes quite short and straightforward. Note the 7 steps commented in the source:

// #################################################################################################
// The main() function of the CLI application
// #################################################################################################
int main( int argc, const char **argv )
{
alib::ARG_C = argc;
alib::ARG_VN = argv;
// 1. Add our custom module to the list of modules
alib::CAMPS.PushBack( &SAMPLE_CAMP );
// 2. Initialize all modules
// 3. now we start catching exceptions
Enum result= ExitCodes::ErrInternalError;
try
{
// 4. Create the central command line interface object app and perform
// mandatory initializations.
{
// Read copyright string from resources and format to current version and year
Paragraphs buffer;
buffer.LineWidth= 70;
buffer.AddMarked( SAMPLE_CAMP.GetResource( "AppInfo" ),
cli.AppInfo.Allocate(cli.GetAllocator(), buffer.Buffer);
// Initialize the CLI with the module to fetch the resources from.
cli.Init( &SAMPLE_CAMP );
// Read enum records from resources and build up corresponding object lists.
cli.DefineParameters<enum Parameters>();
cli.DefineCommands <enum Commands >();
cli.DefineOptions <enum Options >();
cli.DefineExitCodes <enum ExitCodes >();
// Read options from the command line
cli.ReadOptions();
}
// 5. check for unprocess options (not allowed with this demo. Other application might pass
// those to other libraries or parts of the software, which provide their own option
// processing.
if( cli.OptionArgsIgnored.Count() )
{
result= ExitCodes::ErrUnknownOption;
std::cerr << "Error: Unknown option given \""
<< cli.OptionArgsIgnored.Front()
<< "\"" << std::endl;
goto END;
}
// 6. Now, the truly custom part: Process commands and options
result= processCLI( cli );
}
// fetch exceptions and assign a corresponding exit code (error code)
catch( Exception& e)
{
std::cerr << e.Format() << std::endl; // print out human-readable exception information
result= e.Back().Type; // For this demo, just return the internal exception
// number as "exit code".
}
catch(std::runtime_error& e)
{
result= ExitCodes::ErrInternalError;
std::cerr << "A runtime error occurred: " << e.what()<< std::endl;
}
// 7. That's it.
END:
return int(result.Integral());
}

Finally, we can look at our custom command processing function. Also here, just note the source comments. Everything should be quite understandable:

// #################################################################################################
// The custom function to process CLI params
// #################################################################################################
ExitCodes processCLI( CommandLine& cli )
{
AString format; // The date output format
Paragraphs helpText; // A buffer for help texts
DateTime dt; // The timestamp to output
format << "yyyy-MM-dd HH:mm:ss";
//------- check for option 'format' -------
Option* option= cli.GetOption( Options::Format);
if( option )
{
format.Reset( option->Args.Front() );
}
//------- check for option 'help' -------
option= cli.GetOption( Options::Help);
if( option )
{
if( !CLIUtil::GetHelp( cli, nullptr, option, helpText ) )
{
std::cerr << "Error: Unknown help Topic \""
<< (option->Args.Count() > 0 ? option->Args.Front() : String() )
<< "\"" << std::endl
<< "Usage Information follows: " << std::endl << std::endl;
option->Args.Clear();
helpText.Clear();
CLIUtil::GetHelp( cli, nullptr, option, helpText );
}
std::cout << helpText.Buffer << std::endl;
return ExitCodes::OK;
}
//------- No command recognized? This is allowed, assuming now -------
if( cli.CommandsParsed.Count() == 0 )
{
// Still a command was given? This is not allowed
if( cli.ArgsLeft.size() > 0 )
{
std::cerr << "Error: Unknown command given \""
<< cli.ArgStrings.at(std::size_t(*cli.ArgsLeft.begin()))
<< "\"" << std::endl;
return ExitCodes::ErrUnknownCommand;
}
// No command, results in command "now"
AString printBuffer;
calendar.Format( format, printBuffer, lang::CurrentData::Clear );
std::cout << printBuffer << std::endl;
return ExitCodes::OK;
}
//------- Command loop -------
// Note: Making a loop here is optional. We do it to allow multiple commands
// with one invokation of the application.
Command* actCmd;
while ( (actCmd= cli.NextCommand()) != nullptr )
{
auto actCmdCode= actCmd->Declaration->Element();
if ( actCmdCode == Commands::Now )
{
dt= DateTime();
}
else if ( actCmdCode == Commands::File )
{
// check if filename was given as paraemter
if(actCmd->ParametersMandatory.Count() < 1)
{
std::cerr << "Error: no filename given with command 'file'" << std::endl;
std::cerr << "Usage: " << CLIUtil::GetCommandUsageFormat(cli, *actCmd->Declaration )
<< std::endl;
return ExitCodes::ErrMissingFilename;
}
// get file (or directory) modification date
String4K name( actCmd->ParametersMandatory.Front()->Args.Front() );
std::filesystem::path path( name.Terminate() );
dt.Import( std::chrono::clock_cast<std::chrono::system_clock>(
std::filesystem::last_write_time( path ) ) ) ;
}
else if ( actCmdCode == Commands::Help )
{
if( !CLIUtil::GetHelp( cli, actCmd, nullptr, helpText ) )
{
std::cerr << "Error: Unknown help topic" << std::endl;
std::cerr << "Usage: " << CLIUtil::GetCommandUsageFormat(cli, *actCmd->Declaration )
<< std::endl;
return ExitCodes::ErrUnknownHelpTopic;
}
std::cout << helpText.Buffer << std::endl;
continue;
}
// execute printing of commands "now" and "file"
AString printBuffer;
calendar.Format( format, printBuffer, lang::CurrentData::Clear );
std::cout << printBuffer << std::endl;
}
return ExitCodes::OK;
}

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.

3.5 Sample Invocations

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>
Note
In all output samples, the return code, which often is an important piece of information of CLI software, is shown as <Exitcode N>. This is not generated as output, but added here for this tutorial.

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>

4. Detail Topics

4.1 Allowing Other Parts Of The Software To Access CLI Arguments

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.