Building Procedural Applications

A procedural application performs one synchronous sequence after its inputs have been validated. Deriving from Application gives each lifecycle concern a natural method while an overridden main() keeps the actual work easy to read.

Separating Setup, Options, and Work

Initialization establishes process-wide metadata and services. Command-line registration describes input without parsing it manually. The main method receives control only after parsing succeeds and returns the final ExitCode.

/// Override `Application::main()` for a synchronous workflow with one clear result.
///
/// `Application` initializes the process, parses the command line, and handles framework errors before and after this
/// method. The overridden main method reads the validated values and returns the process exit code.
class ReportApplication final : public el::Application {
public:
    using Application::Application;

protected: // implement Application
    void initialize() override {
        info().setApplicationName("Deployment Report"_el);
        info().setApplicationVersion(el::Version{1, 0, 0});
    }
    void registerCommandLineOptions(const el::OptionsPtr &options) override {
        options->addOption("environment"_el)
            .setRequired()
            .setHelpDescription("Environment summarized by the report."_el);
        options->addOption({"-d"_el, "--dry-run"_el, "dry-run"_el})
            .setHelpDescription("Marks the report as a simulation."_el);
    }
    [[nodiscard]] auto main() -> el::ExitCode override {
        el::io::printLine("environment: "_el, optionValues()->getText("environment"_el));
        el::io::printLine("mode: "_el, optionValues()->getFlag("dry-run"_el) ? "simulation"_el : "deployment"_el);
        return el::ExitCode::success();
    }
};

/// Create the procedural application and run its complete lifecycle.
auto main(const int argc, char *argv[]) -> int {
    auto app = ReportApplication{argc, argv};
    return app.run();
}

$ core/core_procedural_application --dry-run staging

environment: staging
mode: simulation

$ core/core_procedural_application --help

Usage:
core_procedural_application [options] <environment>
Options:
-d, --dry-run[=<boolean>] Marks the report as a simulation.
<environment>             Environment summarized by the report.
-h, --help[=<boolean>]    Display this help.
    --version[=<boolean>] Display version information.

Using Parsed Values

optionValues() contains validated values, defaults, and the selected option module. The framework has already handled --help, --version, missing required values, and validation failures before the override is reached.

For a larger workflow, keep main() as the readable top-level sequence and move individual operations into private methods or collaborating classes. This preserves the lifecycle boundary without turning the application subclass into every subsystem at once.

Understanding the Override

The procedural override replaces the default main dispatch. It therefore does not automatically call a selected option-module main function, a function installed with setMainFn(), start application parts, or enter the event loop.

This is the desired behavior for a synchronous tool. If the method needs one of those facilities, call the corresponding base or protected operation explicitly rather than assuming it still happens in the background.

Cleanup and Errors

Application::run() calls cleanup() after the returned exit code and after handled Erbsland Core exceptions. Cleanup must not block or throw. Use ordinary local ownership within main() for resources that should be released before application cleanup, and use the cleanup override only for application-lifetime state.