Comparison Story: Output

4.  Output — regex match + grouped stdout emission

4.  Output

Output is the class that decides whether a file matches and, if so, prints it grouped under its containing directory. In the Rust variant these two concerns live inside TextFinder and TfAppl; in the C++ variant they are collapsed into a single class exposed by the output module. Output's two public callback-shaped methods — on_dir and on_file — are the natural targets for the two std::function callbacks that DirNav fires.

4.1  Design Points

  • Lazy directory printing. on_dir records the current directory and clears dir_printed_ but only prints the directory header when hide_ is false. on_file checks the flag on every match and prints the deferred header the first time a match is found — so "hide empty" comes for free without a two-pass walk.
  • Match-anything shortcut. When the user's regex is ".", match_all_ is set and find() returns true without opening the file. This shortcut is important for the default no-/r case where every non-empty file is expected to be reported.
  • Bad regex ≠ crash. set_regex() catches std::regex_error, resets the optional, and flips match_all_ back on so subsequent find() calls degrade to "match everything" rather than throw. Any exception raised deeper in find() is also swallowed and turned into a false return.
  • Text-first, binary fallback read. The file is opened in text mode first; if that fails (or the read reports an error before EOF), a second attempt opens the file in binary mode. This mirrors the "binary file with lossy UTF-8" branch of the baseline rs_textfinder, but the C++ version keeps a single contents string throughout.
  • std::regex_search, not regex_match. regex_search reports whether the pattern matches anywhere in the string — regex_match would require the whole string to match.
  • Performance caveat. std::regex uses a backtracking NFA engine that rescans input on each match attempt, which is why the C++ variant runs noticeably slower than the Rust and Python variants even though all three do the same byte-level work (see Project Story: TextFinder — Performance).

4.2  Public API Summary

Method Purpose
Output(bool hide = true) Construct. Sets initial hide state and defaults regex_ to "." (match everything).
set_regex(const string& pattern) Compile the regex. On failure, degrades to match-all mode without throwing.
on_dir(const string& dir_path) Called by DirNav when entering a directory. Prints or defers the header depending on hide_.
on_file(const string& file_name) Called by DirNav for each pattern-matched file. Reads the file, runs the regex, and — on a match — emits the (deferred) header plus the file name.
match_count() const Number of files whose content matched the regex. Used by main in the summary line.

4.3  Source — Output/src/Output.ixx

export module output;

import std;

export class Output
{
public:
    explicit Output(bool hide = true)
        : hide_(hide)
        , dir_printed_(false)
        , match_count_(0)
        , match_all_(true)
        , regex_(std::regex("."))
    {}

    void set_regex(const std::string& pattern)
    {
        match_all_ = (pattern == ".");
        try   { regex_ = std::regex(pattern); }
        catch (const std::regex_error&) { regex_.reset(); match_all_ = true; }
    }

    void on_dir(const std::string& dir_path)
    {
        current_dir_ = dir_path;
        dir_printed_ = false;

        if (!hide_)
        {
            std::cout << "\n  " << current_dir_ << '\n' << std::flush;
            dir_printed_ = true;
        }
    }

    void on_file(const std::string& file_name)
    {
        std::filesystem::path full_path =
            std::filesystem::path(current_dir_) / file_name;

        if (!find(full_path))
            return;

        if (hide_ && !dir_printed_)
        {
            std::cout << "\n  " << current_dir_ << '\n';
            dir_printed_ = true;
        }

        std::cout << "      " << file_name << '\n' << std::flush;
        ++match_count_;
    }

    std::size_t match_count() const
    {
        return match_count_;
    }

private:
    bool                       hide_;
    bool                       dir_printed_;
    std::size_t                match_count_;
    bool                       match_all_;
    std::optional<std::regex>  regex_;
    std::string                current_dir_;

    bool find(const std::filesystem::path& file_path) const
    {
        if (match_all_) return true;
        if (!regex_) return false;
        try
        {
            std::string contents;
            bool        read_ok = false;

            {
                std::ifstream ifs(file_path);
                if (ifs)
                {
                    std::ostringstream oss;
                    oss << ifs.rdbuf();
                    if (ifs || ifs.eof())
                    {
                        contents = oss.str();
                        read_ok  = true;
                    }
                }
            }

            if (!read_ok)
            {
                std::ifstream ifs(file_path, std::ios::binary);
                if (!ifs)
                    return false;

                std::ostringstream oss;
                oss << ifs.rdbuf();
                if (!ifs && !ifs.eof())
                    return false;

                contents = oss.str();
            }

            return std::regex_search(contents, *regex_);
        }
        catch (...)
        {
            return false;
        }
    }
};