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

1. Introduction

The ALib C++ Framework offers several modules that directly support building complete applications:

  • ALib Bootstrap: Handles all fundamental initialization and shutdown tasks.
  • ALib Resources: Manages externalized data such as strings, texts, and other resources.
  • ALib Variables: Provides runtime variables that can be imported from command-line arguments and the system environment and imported from or exported to configuration sources.
  • ALib Camp: Integrates the three modules above.

Together, these four modules already form a coherent foundation for building applications. This fifth top-level module presented here brings all of them together under a unified umbrella, which offers several key benefits:

  • Correct and consistent use of the underlying modules is enforced by this module’s structure.
  • Application code becomes much smaller because many repetitive and error‑prone tasks are handled centrally.
  • New applications can be set up quickly by copying and adapting a minimal sample application.
  • Configuration and logging behavior can be standardized across tools, improving maintainability.
  • Cross-cutting aspects (like error handling, diagnostics, and environment‑dependent behavior) can be configured once and reused.

As a user of ALib, the most important effect is that you save substantial time when getting started and when maintaining your applications over the long term.

The central type of this module is class App that implements a linear state machine with pre-implemented helper facilities. For the CLI-processing, class CommandLine is provided. While its basic initialization and use is nicely abstracted in class App, the class can also be used stand-alone. Thus, this class and its helpers are documented in the later chapter 6. The Command Line Interface Implementation.

2. Class App

While most ALib modules are primarily optimized for orthogonality and performance, this module pursues a slightly different goal: it aims to make the application’s startup, run, and shutdown flow explicit and easy to follow. In other words, the code itself is structured as a step-by-step guide to what an application does when it runs.

To achieve this, the central class alib::app::App orchestrates a simple, linear state machine that drives your application through all relevant phases: bootstrap, run, and shutdown. The philosophy is to provide a clear, overridable sequence of small methods that you can extend or replace without having to re-implement infrastructure.

As common with other frameworks, class App is designed to be a base class for your application class and its method App::Main is the entry point:

struct MyApp : alib::app::App {
void onRun() override { cOut->Add("Hello ALib App!"); };
};
int main( int argc, const char** argv) {
return MyApp().Main(argc, argv);
}

2.1 A Simple Linear State Machine

At the heart of class App is inner class StateMachine with its internal "program" represented by the field App::StateMachine::Program. The fields' type StateMachine::CommandList is a vector of commands, each tying a "state" to the method that should execute at that state.
The built-in states are listed in enumeration App::States and roughly follow this pattern:

  • Bootstrap resource preparation (e.g., camps, resource pools)
  • Bootstrap configuration data (runtime variables)
  • Bootstrap finalization
  • Run phase (start → run → end)
  • Shutdown (announce → clean up → export configuration → output → finalize)

The actual execution order is defined by the underlying integral value of the field StateMachine::Command::State.

Custom applications may define their own enumeration type of custom states and add them, along with a custom method to be executed, to the program vector. Custom methods can be a regular member functions of your derived type and do not need to be virtual. Adding states has to be done before calling App::Main. The constructor of the derived application class is a good place to do this. Finally, adding a custom state can be conveniently done with StateMachine::CommandList::Add (which is using some C++20 template magic).

The following sample demonstrates this:

// My derived app
struct MyApp : public alib::app::App {
// Define an own states enumeration with one entry. The value of the state element
// defines the execution order. Here, we use RunStart as the reference. Thus the state
// will be executed after RunStart and before Run.
enum class MyStates { WarmUp = int(States::RunStart) + 1 };
// Constructor: Inserts the custom state
MyApp() {
machine.Program.Add<MyApp, &MyApp::WarmUpStep>(( MyStates::WarmUp ) );
}
// The method associated with the custom state
virtual void WarmUpStep() {
cOut->Add("I'm still warming up");
}
// Overriding the original run method
void onRun() override {
cOut->Add("Hello ALib App!");
}
};
// C++ main() calls App::Main and returns its exit code
int main( int argc, const char** argv) {
return MyApp().Main(argc, argv);
}

2.2 Everything is virtual

All built-in steps are implemented as virtual methods. You can override any of them to customize behavior or keep defaults where appropriate. Typical hooks include:

Phase Methods Description
Bootstrap onBsSetCamps Calls alib::BootstrapAddDefaultCamps (see ALib Camp).
  onBsPrepareResources Initialize resource pools and output buffers.
  onBsSetNameVersionAndInfo Collects name/version/info strings(optionally via resources).
  onBsPrepareConfig Calls alib::Bootstrap passing BootstrapPhases::PrepareConfig.
  onBsPreloadVariables Allows preloading variables which should later be written to a configuration file, even if they have not been used during the execution of the application.
  onBsCLIDefine Defines command-line processing arguments.
  onBsCLIReadOptions Read command-line options. This is done early to allow influencing the configuration sources in the next state.
  onBsImportConfig Load configuration data.
  onBsConfigureCLI Finalizes CLI after configuration import.
  onBsFinalizeBootstrap Finalize common bootstrap.
  onBsReadDryRunOption Read dry-run option and set field dryrun.
  onBsSetupALox Set up logging. (The default behaviour is manipulateable with Flags.)
Run onRunStart Optional pre-step around an application's main logic.
  onRun Provides the default CLI processing loop. May be overridden if an application omits CLI processing.
  onRunEnd Optional post-step around an application's main logic.
Shutdown onSdAnnounceShutdown Calls Shutdown passing ShutdownPhases::Announce.
  onSdCleanALox Cleans the logging subsystem as established in onBsSetupALox.
  onSdExportConfig Exports the configuration data (if wanted).
  onSdOutput Write collected application output.
  onSdFinalizeShutdown Calls Shutdown passing ShutdownPhases::Destruct.

Because these methods are virtual, you can keep the structure but replace the implementation of any step, or even insert/remove steps by editing the program vector as described above.

2.3 Lifecycle Conveniences And Cross-Cutting Aspects

  • Protected Access
    • All fields and methods of class App are protected. Derived classes may publish functionality via getter-methods.
    • Only rather small applications should rely on the derived application class to be the main provider of the application logic. If an application gets bigger, the derived app class should be seen as only the holder of those features that are built-in, like resource management, configuration data, etc. Thus, in their onRun method, the application should call entry methods of other custom types.
  • Camps and resources
    • An application does not need to create an own, dedicated Camp class. It is absolutely OK to feed own resources and configuration data into the predefined singleton alib::APP.
    • An application might replace those resources that are placed in the singleton as defaults, for example, to perform language translations. As with other ALib Camps, with configuration macro ALIB_CAMP_OMIT_DEFAULT_RESOURCES, the predefined resources can also be excluded from the compilation
  • Configuration files
    The default flow

    On shutdown, only new values or variables with writeback-flag are exported, honoring per-file export lists.

  • Logging
    If enabled with Flags::CreateReleaseLox, a release Lox and a release logger are created (names configurable via fields documented in the class). Flag UseReleaseLoggerForDebugLogging attaches the release logger to the debug Lox as well.
  • Buffered output and early flush
    During execution, user-facing text is collected in App::cOut and error text in App::cErr. The shutdown step onSdOutput writes both to std::cout/std::cerr, flushes, and clears the buffers.
    Besides this automatic invocation, you may call this method at any time (e.g., after milestones or before/after long operations) to emit partial output early.
  • Exceptions and exit codes
    The method exceptionToExitCode maps exceptions to exit codes and can be overridden. Implementations should call the base first and extend only if the base returns a nulled enum; this preserves standard ALib mappings while allowing application-specific codes.
  • Entry point and singleton
    Use App::Main from your main() to pass CLI arguments and run the program. The application instance is available via the global variable alib::app::APP_SINGLETON while the app runs. The templated convenience method Get may be used to statically cast the instance to the derived type.
  • Integrated CLI
    App always owns a CommandLine instance in the field CommandLine cli. The dedicated CLI chapter below explains the default help/version/info commands, the bootstrap hooks, and how to keep or disable CLI behavior in derived applications.

3. App::cOut/cErr vs. ALox Output

This module offers two complementary channels for producing text:

The key idea is to keep user-facing console output minimal and deterministic, while providing rich diagnostics when needed or verbosity is requested. This is especially important for command-line tools that print results for end users or for further processing (e.g., piping into other tools).

3.1 Purpose of cOut and cErr

Buffers Paragraphs * cOut (for standard output) and Paragraphs * cErr (for error output) are foremost designed for command-line tools that print results for end users or for further processing (e.g., piping into other tools).

  • Both buffers are instances of type Paragraphs (see src/alib/format/paragraphs.inc). Paragraphs is tailored to composing longer, well‑formatted texts (wrapping, indentation, headings, lists, etc.) as commonly needed for CLI help screens and summaries.
  • They do not add meta information such as timestamps, log levels, or thread IDs.
  • Output is collected during execution and emitted in a controlled fashion by virtual void onSdOutput() , which writes to std::cout and std::cerr, flushes, and clears the buffers. You may call this method manually (e.g., after milestones) to emit partial output early.

In contrast, ALox log output is primarily for diagnostics and typically includes meta-information that is not suitable for machine consumption via pipes.

3.2 When to use logging vs. cOut/cErr

For classic CLI-style applications, the recommended policy is:

  • Prefer cOut/cErr for the program’s “canonical” output that users expect on the console.
  • Use ALox logging only
    • in debug builds, or
    • when the user explicitly requests it (e.g., with an option like --verbose), or
    • to write logs into separate text files (e.g., /var/tmp/yourapp.log), keeping them separate from cOut/cErr.

Other applications may still mix the two: use cOut/cErr for user-visible results and employ logging for additional detail where formatted log output is helpful. Just be deliberate to avoid confusing users with interleaved formats on the console.

3.3 Practical guidelines

  • Default channels
    • Write user-facing messages to cOut, and user-visible warnings/errors to cErr.
    • Avoid writing directly to std::cout/std::cerr; use the buffers and call virtual void onSdOutput()  so output remains deterministic and nicely grouped.
  • Flushing
    • onSdOutput runs late during shutdown by default. You can also invoke it at any time (e.g., after an important milestone or before a long-running step) to emit partial results.
  • Logging setup
    • If CreateReleaseLox is set, a release Lox and a release logger are created. With UseReleaseLoggerForDebugLogging, the same logger is also attached to the debug Lox.
    • Use ALox for diagnostics and detailed traces. Increase verbosity only when the user asks for it (e.g., --verbose) or in debug builds.
    • To keep console output clean, attach a file-based text logger and direct diagnostic logs there, leaving cOut/cErr for user-facing text.
  • Mixing is allowed, but be intentional
    • It is fine to use both channels in one application. As a rule of thumb, prefer cOut/cErr for information users explicitly asked for (results, summaries, help text) and reserve logging for developers and advanced users.

3.4 Small examples

Buffered user output:

// Collect user-visible messages
cOut->Add("Summary: processed 42 items\n");
cErr->Add("Warning: 2 items were skipped due to validation errors\n");
// Emit now (optional). Otherwise this happens in the corresponding state.
onSdOutput();

Selective logging (release/debug-logging, different verbosity levels):

Lox_Verbose("Additional detail: {}", 42) // Release-logging (remains active in release-builds)
Log_Info("Calculating results") // Debug-logging (pruned in release-builds)
#define Lox_Verbose(...)
#define Log_Info(...)

With these conventions, command-line tools remain predictable and composable (clean stdout/stderr), while developers and power users can still access detailed diagnostics via logging when needed.

3.5 Controlled Early Exit (Exceptions::ControlledEarlyExit)

Some applications want to stop processing “right now” and return a well‑defined exit code without plumbing return values through many layers of code. For this common scenario, the module provides the exception type ControlledEarlyExit.

In short:

  • While in the run phase, you set the desired exit code on the state machine using SetExitCode.
  • You typically print user output to either cErr or cOut.
  • You then throw an Exception with the enum element ControlledEarlyExit.
  • The framework catches this specific exception at the top level of the run loop.,
  • In debug-builds, the default implementation of the method exceptionDisplay will print the call site to allow developers to quickly navigate to the throwing code.
  • The state machine simply proceeds with the next state, which is usually the shutdown phase.
  • No mapping via virtual Enum exceptionToExitCode(Exception&)  is performed for this case; because the paradigm explicitly asks for setting the exit code already at the throw site!

This replaces scattered “if (err) return err;” checks with a single, explicit signal that is understood by the application framework. Because the exception is caught internally, user code does not need to add handlers unless it wants to perform cleanup or logging at the throw site.

Important details:

  • Valid context: The exception is meant to be thrown during the built‑in state RunStart, Run, or RunEnd. In debug builds, ALib asserts if it is thrown from any other state.
  • Exit code required: You must call machine.SetExitCode(...) before throwing. A debug assertion checks this as well.
  • No payload: The exception carries no records or arguments. In debug builds, the default Exception display adds the CallerInfo so developers see the source location where it was thrown.
  • Output prior to exit: If you want to print user‑facing messages, write them to cOut/cErr before throwing. They will be emitted by virtual void onSdOutput()  later in the shutdown phase.

3.5.1 Minimal usage example

void MyApp::onRun() {
// ... detect a condition that requires immediate termination ...
// 1) Choose an exit code
machine.SetExitCode( MyExitCodes::FileNotFound );
// 2) Optionally inform the user
cErr->Add("Fatal: input file not readable\n");
// 3) Abort processing in a controlled way
throw alib::Exception( ALIB_CALLER_NULLED, App::Exceptions::ControlledEarlyExit );
}
#define ALIB_CALLER_NULLED
exceptions::Exception Exception
Type alias in namespace #"%alib".

3.5.2 When to use it - and when not

  • Use ControlledEarlyExit when a condition deep in your logic should end the run phase immediately with a defined exit code, and you prefer not to bubble error codes up through every call.
  • Prefer regular exceptions mapped via exceptionToExitCode when you want to centralize the mapping of many exception types to exit codes or when the failure should be treated like any other error (with stack‑dependent diagnostics, etc.).

Both approaches work together: mapping handles the "usual" errors, while ControlledEarlyExit covers the intentional, well-known early-termination path without return-code plumbing.

4. CLI Integration In App

CLI support is implemented with a few separated types placed in this namespace/module. While these classes may be used directly (standalone), class App makes their use much easier. All details of the CLI interface are given in the later chapter 6. The Command Line Interface Implementation.

4.1 CLI Facilities Built Into Class App

Every instance of App contains:

  • a CommandLine parser in field CommandLine cli,
  • a stop flag bool cliStop,
  • bootstrap states dedicated to CLI setup and early option handling, and
  • some default implementations that connect CLI parsing and even provides some features like –version, –dryrun, or –help=topic.

4.2 CLI-Related States And Hooks

The built-in state machine contains these CLI-related steps:

All four hooks are virtual. Applications commonly override the first three of them by invoking the default implementation first and then performing the similar steps adding/handling the custom commands and options.
Then method processCLICmd is to be implemented to perform the actual work.

Applications that do not want CLI behavior can replace the methods with empty implementations and directly override the method onRun.

4.3 Built-In CLI Behavior

The default CLI bootstrap already defines a small standard vocabulary:

  • commands help, version and info,
  • options --help, --version, --config and --dryrun,
  • the help-topic parameter TOPIC, and
  • a set of standard exit codes for parsing and configuration failures.

The default run path then provides these behaviors:

This is intentionally modest. Derived applications are expected to add their own commands and options and to override virtual bool processCLICmd(Command*)  for the real work.

4.4 Declaring Additional Commands And Options

The default virtual void onBsCLIDefine()  registers the built-in enums with class CommandLine. Derived applications typically extend that definition step with their own declarations. For this purpose, custom enumeration types have to be provided, which are resourced accordingly.

The best approach to do this, is to extend the sample application shown in the chapter 5. Sample Application. Otherwise, all details are given in chapter 6. The Command Line Interface Implementation.

4.5 Configuration Files Selected From The CLI

Configuration file selection a further feature of the regular App workflow:

The default implementation of virtual void getConfigFilePathsFromCLIParam(StdVectorMA<ConfigFileDescriptor>&)  interprets --config as a comma-separated list of file names. Entries left as Default... keep the resource-defined path at that position. Descendants may override the method to support different CLI syntax.

This preserves resource-based defaults while still giving users a straightforward way to redirect configuration storage.

4.6 Processing Commands

The default virtual void onRun()  is a simple CLI loop:

This gives derived types two common extension patterns:

4.7 Generating Help Pages

The class CLIUtil provided with this module can generate rich help pages and write them into cOut:

  • Command and option metadata registered with the CLI tools can be turned into formatted help text, taking localized resource strings into account.
  • This makes entry points like --help and topic-specific help such as --help=topic straightforward.
  • Because cOut/cErr are buffers of type Paragraphs, line wrapping and layout are handled to suite terminal output.

Its output for command help may look as follows:

----------------------------------------------------------------------
ALib Resource Compiler V. 2605.0
(c) 2023-2026 AWorx GmbH. Published under MIT License (Open Source).
For more information, see: https://alib.dev/alib_mod_resources.html
----------------------------------------------------------------------

ABOUT ALibRC
  This is a tool provided by the C++ Framework ALib that compiles external resources and inserts a corresponding code
  snippet into a C++ source file. The resources are specified in a resource file, while the C++ source file must exist
  and contain special markers for the insertion position.
  All details are given in the documentation of the ALib C++ Framework at:
    https://alib.dev/ 

USAGE:
  alibrc resourcefile cppfile 

COMMANDS:
  * ALibRC help [TOPIC]
    Shows general help or help on topics
  * ALibRC version
    Shows the version of this software.

OPTIONS:
  --help[[=]TOPIC]
  --version
  -d|--dryrun[=yes|no]
  --verbose[="level"]
  --validate resourcefile [cppfile]
EXIT-CODES:
      1: ErrNoCmdGiven
         No command given.
      3: ErrParsingOption
         Unknown option {!Q} was given.
      4: ErrParsingCommand
         An error occured when parsing the command {!Q}:
      5: ErrMissingCmdParam
         Missing a mandatory parameter {!Q} of the given command {!Q}.
      6: ErrMissingOptParam
         Missing a mandatory parameter {!Q} of the given option {!Q}.
      7: ErrBadParamValue
         Parameter {!Q} is not suitable for command or option {!Q}.
    101: ErrMissingRCFilename
         No input resource file name given.
    102: ErrRCFileNotFound
         Given resource file not found.
    103: ErrRCFileNotAccessible
         Resource file is not readable.
    104: ErrMissingCPPFilename
         No C++ source file to patch given.
    105: ErrCPPFileNotFound
         Given C++ resource file not found.
    106: ErrCPPFileNotAccessible
         C++ file is not writable.
    107: ErrInResources
         The resource file is erroneous.

Note that for creating this output, not a single line of custom code has to be written. Instead, the help texts are dynamically generated from the defined commands, options, parameters and exit codes and their according resource strings.

4.8 Applications Without A CLI

Even though the class App includes CLI support by default, an application that does not expect commands or options is easy to create:

This keeps the common case convenient without forcing a command model onto every application.

5. Sample Application

The various small details of classes that need to be reflected when using App and CommandLine, is best handled by using a sample application as a jump-start.
A very small sample application is provided with the ALib C++ Framework which implements a command-line tool that reports the current date or the modification date of a file. The declaration of the class looks like this:

class Sample : public alib::app::App {
enum class DateCommands {
Now = 1, ///< Returns the current date.
File = 2, ///< returns the modification date of a file or directory.
};
enum class DateOptions {
Format = 0, ///< Overwrite the default format string.
};
enum class DateParameters {
Filename = 0, ///< Used with command \e file to denote the file.
};
enum class DateExitCodes {
ErrMissingFilename = 101, ///< Command "file" given without a filename.
ErrFileNotFound = 102, ///< File given with command "file" not found.
ErrPermissionDenied = 103, ///< File given with command "file" not found.
};
alib::String64 format= "yyyy-MM-dd HH:mm:ss";
void onBsCLIDefine() override;
void onBsPrepareConfig() override;
bool processCLICmd(alib::app::Command* cmd ) override;
void bulkloadResources();
};

The full header file can be seen here: src.samples/App/sample.hpp. Its implementation is provided in the file src.samples/App/sample.cpp.

When an application becomes more complex, especially by the additional burden of defining some resource strings, we recomment to use the sources of the ResourceCompiler Tool as a jump-start.
From here, your custom application can be developed step by step, and things should become relatively easy and clear.
The compiler is a rather simple tool whose parsing and code-generation logic is encapsulated in the the module ALib Camp and hence just a little application-specific code is contained in the project.

This tool is found in the folder:

ALIB_BASE_DIR/tools/ResourceCompiler

6. The Command Line Interface Implementation

6.1 Introduction

Note
The types described here may be used stand-alone from class App. Nevertheless, it is recommended to use the App class for the most common use cases, because it provides a convenient way to set up the CLI and to perform the actual work.
Also, even reading this manual chapter may not be required, if the sample application described in the previous chapter 5. Sample Application is used as a jump-start.
Command-line parsing is a tricky and - due to the lack of strict standards - a rather cumbersome task. We tried our best to provide a simple and robust implementation that enables the most common command-line tools requirements.

The types introduced in this chapter support 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 receives arguments, may it be daemons or GUI-driven applications.

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 following commands or options or that are attached to options using, for example, an equal sign '='.
  • arguments: any of the three above.

6.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 of 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 options/parameters are often allowed to be given in arbitrary order. Finally, options 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 types 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 programmer 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 the provision of an ALib Camp and thus leveraging inner namespace alib::resources of the module ALib Resources.

6.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 good advice to get familiar with the features provided by inner namespace alib::resources of the module ALib Resources as a start. That module in turn uses so-called Resourced ALib Enum Records which are provided with the module ALib EnumRecords.

Or, the other way round: The tutorial found below might also be a good starter to understand externalized resources and ALib EnumRecords in their basics, and therefore the better advice might be to just continue reading.

6.1.3 Features

The CLI-types of this module ALib App offer 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 Variables allows overwriting configuration data stored in configuration files transparently with by giving command-line options. CLI input which is not processed by this module, is filtered out and can easily be forwarded to module ALib Variables, or other parts of the software that accept CLI input.

6.2. Classes and Paradigms

6.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 is usually stored in so-called resources. How resourced data is used to automatically create and fill the structs is shown in the next chapter 6.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 ResourcedTraits, 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 ERSerializable "TExit_NN": A format string returned by method const String & FormatString() .

6.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.

6.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:

Because the documentation tool used here (overall exquisite software Doxygen ) destroys some formatting, it is recommended to copy the sources directly from your filesystem instead of from this tutorial chapter.

However, as already mentioned, the even better approach is to use the CLI-types embedded in class App as sampled in chapter 5. Sample Application.

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 the 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 quotes.
  • 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 a 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.

6.3.1 Definition of Enumerations

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

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, ///< Unknown command given.
ErrUnknownOption = 101, ///< Unknown 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.

6.3.2 Definition of a Custom Camp

The enum records that just have been 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 a custom ALib Camp.

Note
The CLI system does not force you to create a custom camp. Alternatively, the creation of a ResourcePool instance would be enough. However, when you create your own, more complex software, it might be a good advantage to have such a 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.

This is the declaration of the camp in the header file:

class SampleCamp : public alib::camp::Camp {
public:
// Constructor. Passes version number and resource name to the module class
SampleCamp() : Camp("DATEMOD") {}
protected:
// Initialization of the module.
virtual void Bootstrap() override;
// Terminate this module. (Nothing to do.)
virtual void Shutdown( alib::ShutdownPhases phase ) override;
}; // class SampleCamp

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

// The module singleton object
extern SampleCamp SAMPLE_CAMP;

6.3.3 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_CAMP( Commands , SAMPLE_CAMP, "Commands" )
ALIB_RESOURCED_IN_CAMP( Parameters, SAMPLE_CAMP, "Parameters" )
ALIB_RESOURCED_IN_CAMP( Options , SAMPLE_CAMP, "Options" )
ALIB_RESOURCED_IN_CAMP( ExitCodes , SAMPLE_CAMP, "ExitCodes" )

6.3.4 Implementation of the Camp

The code shown above, was all from the header file. Now we turn to the implementation part. Let's start with the implementation of the custom Camp. It is full of resource strings!

void SampleCamp::Bootstrap() {
if( GetBootstrapState() == 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("datesample::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
"TOptUsg_format", A_CHAR("--format[=]\"placeholders\""),
"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/classalib_1_1strings_1_1util_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 topic that the help command should be verbose about.")
EOS
// #################### ExitCodes ###################
"ExitCodes", A_CHAR(
//enum name
"0," "OK" ","
"100," "ErrUnknownCommand" ","
"101," "ErrUnknownOption" ","
"102," "ErrMissingFilename" ","
"103," "ErrUnknownHelpTopic" ","
"255," "ErrInternalError" ) 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 module \"ALib CLI\"."
"\n@<<\n" )
EOS
// end of BootstrapBulk()
nullptr );
}
else if( GetBootstrapState() == BootstrapPhases::PrepareConfig ) {
}
}
// Terminate this module. (Nothing to do.)
void SampleCamp::Shutdown( ShutdownPhases ) {}

Being an experienced programmer, if you have a brief look at the string data defined in the code above, the general idea about the sort of information that is provided should become clear. Together with the macros used in the headers, parts of the resources are now "attached" to the C++ enums.

6.3.5 Function main()

Now, we are 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.

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.push_back( &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.size() )
{
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());
}
Note
You may have noticed the numbering of 7 steps in the comment lines. Again we want to hint to the higher-level module ALib App. Using that, separates these steps (and more!) in a simple little state-machine, with many already built-in features.

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

ExitCodes processCLI( CommandLine& cli )
{
String64 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' -------
app::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, option, helpText ) )
{
std::cerr << "Error: Unknown help Topic \""
<< (option->Args.size() > 0 ? option->Args.front() : String() )
<< "\"" << std::endl
<< "Usage Information follows: " << std::endl << std::endl;
option->Args.Clear();
helpText.Clear();
CLIUtil::GetHelp( cli, option, helpText );
}
std::cout << helpText.Buffer << std::endl;
return ExitCodes::OK;
}
//------- No command recognized? This is allowed, assuming now -------
if( cli.CommandsParsed.size() == 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::Keep );
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 invocation of the application.
app::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.size() < 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, 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::Keep );
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.

6.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:

2026-05-13 15:19:26<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:

2026-05-13 15:19:26<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 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:

May 13, 2026<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:

2026-04-30 13:10:43<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: <app::MissingOptionValue>
    Missing argument(s) of option <format> found with CLI argument[0]="--format".
    Expected 1, given 0.
    [@ /hub/projects/ALib/src/alib/app/cliargtypes.cpp:90 from 'Option::Read()' by 'MAIN_THREAD(-1,0x00007F1FDA4247C0)']
E2: <app::ParsingOptions>
    Error parsing option. Usage:
    --format[=]"placeholders"
    [@ /hub/projects/ALib/src/alib/app/cli.cpp:87 from 'CommandLine::ReadOptions()' by 'MAIN_THREAD(-1,0x00007F1FDA4247C0)']<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 CLI system 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 analyze 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 of the more general exit codes that 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. 2511.0 (in fact a sample application
only)
(c) 2023-2026 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 module "ALib CLI".
USAGE:
  date [format="FORMATSPEC" [now]|[file FILENAME]
COMMANDS:
  * date now
    Reports the actual date/time
  * date file FILENAME
    Returns the date/time of a file. 
  * date help [TOPIC]
    Displays usage information. 
OPTIONS:
  --format[=]"placeholders"
  --help[[=]TOPIC]
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.<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. 2511.0 (in fact a sample application
only)
(c) 2023-2026 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.<Exit-code  0>
Sample --help file
----------------------------------------------------------------------
Command line tool 'date'. V. 2511.0 (in fact a sample application
only)
(c) 2023-2026 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. 2511.0 (in fact a sample application
only)
(c) 2023-2026 AWorx GmbH. Published under MIT License (Open Source).
For more information, see: https://alib.dev
----------------------------------------------------------------------
Help on option <format>:
  USAGE:  --format[=]"placeholders"
  DESCRIPTION:
    Sets the output format. The format specification is given with
    documentation of ALib method CalendarDateTime::Format, found here:
    https://alib.dev/classalib_1_1strings_1_1util_1_1CalendarDateTime.html<Exit-code  0>

6.4. Detail Topics

6.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 alib::ARG_C, alib::ARG_VN and alib::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. 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 software is using..

With the use of camp ALib Variables such use of CLI arguments outside 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 virtual void ReadOptions() .

6.4.2 Undefine CLI Elements

For each component type, class CommandLine provides a method to undefine single elements, namely UndefineCommand, UndefineOption, UndefineParameter, and UndefineExitCode. By using these methods, such components are not recognized by the CLI processing and will not be displayed in the generated help messages.

This can be used, for example, if software consists of different building blocks where some foundational elements like exit codes or options are provided, while some derived components want to disable a part of these. As an example, class App of module ALib App offers some built-in features like option –dryrun, that a using code may want to disable and/or redefine.