The Application Framework

Define the Application Class

Derive from Application when options and startup behavior belong to the executable as a whole. The ElGrepApp class owns the search workflow and a pair of private result structures used to prepare output.

<project>/elgrep/src/ElGrepApp.hpp
 1// Copyright (c) 2026 Tobias Erbsland - https://erbsland.dev
 2// SPDX-License-Identifier: Apache-2.0
 3#pragma once
 4
 5#include <erbsland/Application.hpp>
 6#include <erbsland/Path.hpp>
 7#include <erbsland/String.hpp>
 8#include <erbsland/re/CaptureRange.hpp>
 9#include <erbsland/re/RegEx.hpp>
10
11#include <cstddef>
12#include <vector>
13
14
15namespace elgrep {
16
17/// The application class for the recursive regular-expression search tool.
18class ElGrepApp final : public el::Application {
19    struct MatchingLine {
20        std::size_t number;
21        el::String text;
22        std::vector<el::re::CaptureRange> ranges;
23    };
24
25    struct FileResult {
26        el::String displayPath;
27        std::size_t matchCount{0};
28        std::vector<MatchingLine> lines;
29    };
30
31public:
32    using Application::Application;
33
34protected: // implement Application
35    void initialize() override;
36    void registerCommandLineOptions(const el::OptionsPtr &options) override;
37    [[nodiscard]] auto main() -> el::ExitCode override;
38
39private:
40    [[nodiscard]] auto searchFile(
41        const el::Path &path, const el::String &displayPath, const el::re::RegEx &expression) const -> FileResult;
42    void searchDirectory(const el::Path &path, const el::re::RegEx &expression) const;
43    void printResult(const FileResult &result) const;
44    void printMatchingLine(const MatchingLine &line) const;
45    void printText(const el::String &text) const;
46};
47
48}

The class overrides the three lifecycle methods needed by this tool:

initialize()

Sets metadata used by generated help and error output.

registerCommandLineOptions()

Describes one flag and two required positional arguments.

main()

Runs only after Core has parsed and validated the command line.

Initialize Metadata and Options

The first part of ElGrepApp.cpp supplies the application metadata and option definitions.

ElGrepApp.cpp — initialization and options
void ElGrepApp::initialize() {
    info().setApplicationName("elgrep"_el);
    info().setApplicationVersion(el::Version{0, 1, 0});
    info().setAuthorName("Erbsland Core tutorial"_el);
    info().setLicenseText("Apache-2.0"_el);
}

void ElGrepApp::registerCommandLineOptions(const el::OptionsPtr &options) {
    options->setHelpTitle("elgrep - search text with a regular expression"_el);
    options->setHelpDescription(
        "Searches a UTF-8 text file, or recursively searches all regular files below a directory."_el);
    options->addOption({"-r"_el, "--recursive"_el, "recursive"_el})
        .setType(el::OptionType::Flag)
        .setHelpDescription("Recursively searches a directory and skips symbolic links."_el);
    options->addOption("path"_el).setRequired().setHelpDescription("File or directory to search."_el);
    options->addOption("pattern"_el).setRequired().setHelpDescription("Erbsland Core regular-expression pattern."_el);
}

Core automatically adds standard help and version handling. The option lookup names without dashes, such as recursive, are the stable names used when reading parsed values.

Let Application Handle Failures

The main method converts and validates the path, compiles the expression once, and chooses between a file search and a directory walk.

ElGrepApp.cpp — main workflow
auto ElGrepApp::main() -> el::ExitCode {
    const auto values = optionValues();
    const auto path = el::Path::fromNativeOrThrow(values->getText("path"_el));
    const auto pathInfo = path.info(el::PathInfoPart::Type);
    if (!pathInfo.exists()) {
        throw el::ApplicationError{"The search path does not exist or cannot be accessed."_el};
    }

    const auto expression = el::re::RegEx::compile(values->getText("pattern"_el));
    const auto resolvedPath = pathInfo.resolvedPath();
    if (pathInfo.isRegularFile()) {
        printResult(searchFile(resolvedPath, resolvedPath.name(), *expression));
        return el::ExitCode::success();
    }
    if (!pathInfo.isDirectory()) {
        throw el::ApplicationError{"The search path is neither a regular file nor a directory."_el};
    }
    if (!values->getFlag("recursive"_el)) {
        throw el::ApplicationError{"The search path is a directory. Add --recursive to search it."_el};
    }

    searchDirectory(resolvedPath, *expression);
    return el::ExitCode::success();
}

There is deliberately no try /catch block here. Invalid paths and patterns already throw exceptions from the Erbsland Core error hierarchy. For application-specific validation, elgrep throws ApplicationError. Application::run() catches these errors, renders a consistent diagnostic, and returns the corresponding process exit code.

Search Files and Directories →