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;
}
}
};