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

Todox: das ganze Ding muss überarbeitet werden. Einiges kommt auch von der AI.

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 run-time variables that can be imported from command-line arguments and the system environment and imported from or exported to configuration sources.
  • ALib Camp: Orchestrates and integrates the three modules above.
  • ALib CLI: A framework for implementing a command-line interface.

Together, these five modules form a coherent foundation. They partially depend on each other and each comes with a comprehensive user manual.

The 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.
  • Resources such as texts and translations for different locales can be shared across applications.
  • 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.

2. The General Approach Taken

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

2.1 A Simple Linear State Machine

At the heart of App is an internal program represented by CommandList Program. This 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 States and roughly follow this pattern:

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

The actual execution order is defined solely by the order of entries in CommandList Program, not by the numeric values of the enum items. For quick navigation to the lower-level bootstrap/shutdown facilities, see also BootstrapPhases and ShutdownPhases.

Custom applications may insert their own states anywhere in the linear sequence. This works by defining a custom enumeration and adding an entry built with static Command MakeCustom(Enum)  before or after any built-in state. The inserted handler is a regular member function of your derived type and does not need to be virtual.

Example:

struct MyApp : public alib::app::App {
enum class MyStates { WarmUp = 1000 };
MyApp(camp::Camp* c) : App(c) {
// Insert custom step right after built-in RunStart
machine.Program.AddAfter( States::RunStart,
StateMachine::Command::MakeCustom<
MyApp,
&MyApp::WarmUpStep
>( MyStates::WarmUp )
);
}
void WarmUpStep() {
// ... custom preparation before onRun() ...
}
void onRun() override {
// ... main logic ...
}
};
virtual void onRun()=0

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:

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

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.

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.inl). 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
ALIB_ASSERT(cOut && cErr)
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 onSdOutput()
onSdOutput();
#define ALIB_ASSERT(cond, domain)
Definition alib.inl:1143

Selective logging (only if a release Lox exists and logging was requested):

if ( /* user requested verbose */ ) {
if (auto* rlox = GetRLox()) {
// Use your ALox logger(s) for diagnostics; keep cOut/cErr clean for users
// rlox->Info( "Starting step X" ); // Pseudocode: use your configured log API
}
}

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.6 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.
  • 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(alib::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 Run. 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.6.1 Minimal usage example

void MyApp::onRun() {
// ... detect a condition that requires immediate termination ...
// 1) Choose an exit code (custom enum or one of App::BuiltInExitCodes)
machine.SetExitCode( MyExitCodes::ErrSomething );
// 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
Definition alib.inl:1105
exceptions::Exception Exception
Type alias in namespace alib.

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

3.5 Generating help pages with ALib CLI

When used together with module ALib CLI, you can half‑automatically generate rich help pages for your command-line interface and write them into cOut using Paragraphs:

  • Command and option metadata that you register with the CLI tools can be turned into formatted help text, taking localized resource strings into account.
  • This makes it straightforward to support typical entry points like --help and topic‑specific help such as --help=topic without manually assembling all text.
  • Because cOut/cErr are Paragraphs buffers, line wrapping and layout are handled for you.

The next chapter (Tutorial) will include a step‑by‑step source code example that wires up CLI commands/options, uses resources for text, and renders help output via cOut.

It's output for command 'help' looks as follows:

----------------------------------------------------------------------
Command line tool 'date'. V. 2511.0 (in fact a sample application
only)
(c) 2023-2025 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".

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[=]"placeholders"
  --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. 

4. AppCli - Combining Modules

alib::app::AppCli is a small but useful convenience on top of the base application framework. It wires in module ALib CLI and adds a few well‑placed bootstrap states so that command‑line processing, configuration handling, and user output (via cOut/cErr) form a coherent whole.

This chapter focuses on what AppCli adds over App and how to use it effectively, without repeating what was already covered in Chapters 1–3 (state machine, lifecycle hooks, and output policy).

4.1 What AppCli Provides

At a glance, AppCli contributes:

  • An instance of CommandLine exposed as field cli::CommandLine cli.
  • A simple command processing loop in void onRun() override that repeatedly parses and executes CLI commands until bool cliStop is set.
  • Three additional bootstrap states (see States):
    • CLIDefine — define commands, options, and flags before configuration import.
    • ReadDryRunOption — evaluate early flags (e.g., --dry-run) before logging is finalized.
    • ConfigureCLI — perform late CLI setup after configuration variables were imported.
  • A mapping hook Enum exceptionToExitCode(Exception&) override to convert exceptions into exit codes, extending the default mapping provided by App.

AppCli does not prescribe any specific commands. Instead, it gives you clean entry points to declare them, influence configuration file selection, and render help text using the Paragraphs-based output described in Chapter 3.

4.2 The Added Bootstrap States

As explained earlier, the actual execution order is defined by the program vector. AppCli inserts its three custom states around existing ones to achieve a natural flow:

  • CLIDefine (before ImportConfig)
    • Define commands (e.g., date now, date file <name>), options (--format), and flags.
    • Keep this phase free of configuration reads; it should be declarative.
  • ReadDryRunOption (before SetupALox)
    • Inspect very early flags that may influence later setup, e.g., a --dry-run that disables side effects or routes logs differently.
  • ConfigureCLI (after ImportConfig)
    • Now that variables were imported, you can finalize CLI defaults, localizations, or command permissions based on configuration.

All three are implemented by virtuals that you may override:

4.3 Selecting Configuration Files via CLI

Chapter 2 outlined the default strategy for configuration files (resources → absolute paths → import/export). AppCli adds a dedicated hook to let CLI parameters influence the set of files:

This design keeps resource‑based defaults in place while giving power users a standard way to redirect configuration without recompilation.

4.4 Processing Commands

The default void onRun() override implementation repeatedly asks the CLI to parse the next command and dispatches it to virtual bool cliProcessCmd(cli::Command*) . You typically override cliProcessCmd and handle your commands there. Return true if a command was recognized and executed; return false to indicate an unknown/invalid command (which you can translate into an error message and a non‑zero exit code).

Set bool cliStop to end processing — for instance after printing help text or after a command that should not be followed by further actions.

Because Chapter 3 already covered cOut/cErr, here’s only a short snippet showing typical integration with help generation from ALib CLI:

struct DateApp : public alib::app::AppCli {
void bsCLIDefine() override {
// 1) Declare commands and options
// (Pseudo API to illustrate the idea — use your actual \alib_cli builders.)
// cli.DefineCommand("now");
// cli.DefineCommand("file").Param("name");
// cli.DefineOption("format").Takes("FORMATSPEC");
}
void bsReadDryRunOption() override {
// Inspect early flags if you have any, e.g., --dry-run
}
void bsConfigureCLI() override {
// Finalize CLI based on imported variables (localization, defaults, permissions)
}
bool cliProcessCmd(alib::cli::Command* cmd) override {
// Dispatch
// if (cmd->Is("now")) { /* print current time to cOut */ return true; }
// if (cmd->Is("file")) { /* read file mtime, print to cOut */ return true; }
// if (cmd->Is("help")) { /* render help to cOut */ cliStop = true; return true; }
return false; // unknown
}
};
virtual bool cliProcessCmd(cli::Command *cmd)
Definition appcli.cpp:246
virtual void bsReadDryRunOption()
Implements #"States::ReadDryRunOption": evaluate early flags (e.g., –dry-run).
Definition appcli.cpp:109
virtual void bsCLIDefine()
Implements #"States::CLIDefine": define CLI options, flags and commands.
Definition appcli.cpp:87
virtual void bsConfigureCLI()
Implements #"States::ConfigureCLI": finalize CLI after configuration import.
Definition appcli.cpp:97

For guidance on text rendering and on keeping logs separate from user output, refer back to Chapter 3.

4.5 Exit Codes and Error Handling

AppCli inherits virtual Enum exceptionToExitCode(alib::Exception&)  and extends it. The recommended pattern is to call the base implementation first and only handle codes that the base does not know:

alib::Enum DateApp::exceptionToExitCode(alib::Exception& e) {
if (auto base = AppCli::exceptionToExitCode(e); base)
return base; // base mapped something
// Add your application-specific mappings
// if (e.InstanceOf<MyUnknownCommand>()) return ExitCodes::ErrUnknownCommand;
return {};
}
boxing::Enum Enum
Type alias in namespace alib.
Definition enum.inl:211

This preserves the standard ALib mappings while allowing application‑specific codes that you can document in your CLI help (see the example output at the end of Chapter 3).

4.6 Best Practices

  • Keep bsCLIDefine declarative: define commands and options, but do not read configuration here.
  • Use bsReadDryRunOption to pick up only flags that must influence later bootstrap steps.
  • Finalize defaults and permissions in bsConfigureCLI after variables were imported.
  • Render help and other user‑facing text into cOut/cErr using Paragraphs.
  • Avoid mixing diagnostics with user output on the console; prefer ALox for diagnostics as explained in Chapter 3.
  • Provide explicit and stable exit codes and show them in your help output.

With these conventions, AppCli turns the combination of ALib CLI, configuration variables, resources, and output facilities into a compact and predictable workflow.

5. Tutorial: A Step-by-Step Guidance To Creating An Own App

Appendix A: Resources Used By Module alib::App

todo blabla

Configuration file Exports "CFGF_EXP_<em>N</em>"

todox: taken from an older dox. It needs to be described that if less config-files are chosen than the application by default manages, the exports are added to the last config-file created.

A list of variables or tree nodes to variables, separated by a semicolon ',', which should be exported to the main INI-file.
Defaults to nulled string.

A list of variables or tree nodes to variables, separated by a semicolon ';', which should be exported to the INI-file - either the main INI-file or, if existent, to the dedicated second INI-file, which is commonly used for ALib and especially ALox variables.
Defaults to "ALIB;ALOX". system::CPathString iniFileExportsALib = A_CHAR("ALIB;ALOX");