5. Output — TfAppl
TfAppl is the glue struct that lets DirNav
drive the search without knowing anything about regex or text
finding. It is the Rust analogue of the C++ variant's
Output class — the object that receives directory /
file events from the walker, delegates matching to
TextFinder, and prints results grouped by directory.
Because the Rust walker's callback contract is a trait
rather than two std::function members,
TfAppl supplies the two required methods by writing
impl dir_nav_lib::DirEvent for TfAppl.
5.1 Design Points
-
Composition, not inheritance.
TfAppl
has a TextFinder (the tf
field) rather than being one. Regex compilation and the actual
find(path) call are delegated to
tf; TfAppl only handles per-directory
printing state.
-
Deferred directory header.
do_dir records the incoming directory name in
curr_dir and, if hide is off, prints
it immediately. If hide is on, printing is
deferred: the first successful do_file match in a
new directory prints the header (via
tf.get_last_path() != self.curr_dir) and updates
tf.last_path so subsequent files in the same
directory skip that step.
-
Match count is a first-class field.
match_count increments on every successful
find(), so main can print the summary
without re-scanning stdout.
-
Hide and recurse mirror
DirNav.
TfAppl keeps its own copy of the hide
and recurse flags. main writes both
copies at startup so DirNav and TfAppl
never disagree — the walker uses hide to decide
whether to invoke do_dir for empty directories,
and TfAppl uses the same flag to decide whether
to print immediately or defer.
-
Regex delegation.
TfAppl::regex(s) forwards to
self.tf.regex(s); there is no local copy of the
pattern. This preserves the single-source-of-truth for the
regex string.
-
Fully qualified path per file. Because
DirEvent::do_file receives only the bare file
name, do_file reconstructs the fully qualified
path by concatenating curr_dir with
f before calling tf.find.
5.2 Public API Summary
| Method |
Purpose |
new() |
Construct with an empty TextFinder, hide=true, recurse=true, and match_count=0. |
do_dir(&mut self, d) |
DirEvent callback — record current directory; print immediately if hide is off. |
do_file(&mut self, f) |
DirEvent callback — build fully qualified path, call tf.find, print header + file name on match. |
regex(s) / get_regex() |
Delegate to the embedded TextFinder. |
hide(p) / get_hide() |
Read/write the hide-empty-directories flag. |
recurse(p) / get_recurse() |
Read/write the recursion flag (mirrors DirNav). |
get_match_count() |
Number of files whose content matched the regex; used by main in the summary line. |
5.3 Source — EntryPoint/src/tf_appl.rs
TfAppl lives in its own module,
tf_appl.rs, alongside text_finder.rs in
the crate's src/ directory. text_finder.rs
declares the module with mod tf_appl; and brings the
type into scope with use tf_appl::TfAppl;.
TfAppl reaches back into the crate root for the
TextFinder type via use crate::TextFinder;.
/////////////////////////////////////////////////////////////
// tf_appl.rs - DirEvent implementor for TextFinder //
// //
// Jim Fawcett, https://JimFawcett.github.io //
/////////////////////////////////////////////////////////////
use crate::TextFinder;
/*-- TfAppl is an application specific proxy for TextFinder --*/
#[derive(Debug, Default)]
pub struct TfAppl {
tf: TextFinder,
curr_dir: String,
hide: bool,
recurse: bool,
match_count: usize,
}
impl dir_nav_lib::DirEvent for TfAppl {
fn do_dir(&mut self, d:&str) {
/*-- save dir name for use in do_file --*/
self.curr_dir = d.to_string();
/*-- print directory name if H(ide) is false --*/
if !self.get_hide() {
print!("\n--{}", d);
}
}
fn do_file(&mut self, f:&str) {
/*-- build fully qualified path to file --*/
let mut fqf = self.curr_dir.clone();
fqf.push('/');
fqf.push_str(f);
/*-- look for file text that matches regex --*/
if self.tf.find(&fqf) {
self.match_count += 1;
/*-- print directory for first file if H(ide) is true --*/
let pred =
self.tf.get_last_path() != self.curr_dir
&& self.get_hide();
if pred {
print!("\n\n {}", self.curr_dir);
self.tf.last_path(&self.curr_dir);
}
/*-- print name of file with matching text --*/
print!("\n {:?}", f);
}
}
}
impl TfAppl {
pub fn new() -> Self {
Self {
tf: TextFinder::new(),
curr_dir: String::default(),
hide: true,
recurse: true,
match_count: 0,
}
}
pub fn get_match_count(&self) -> usize {
self.match_count
}
pub fn recurse(&mut self, p:bool) {
self.recurse = p;
}
pub fn get_recurse(&self) -> bool {
self.recurse
}
pub fn hide(&mut self, p:bool) {
self.hide = p;
}
pub fn get_hide(&self) -> bool {
self.hide
}
pub fn regex(&mut self, s:&str) {
self.tf.regex(s);
}
pub fn get_regex(&self) -> &str {
self.tf.get_regex()
}
}
5.4 Unit Tests
TfAppl's white-box unit tests live in
tf_appl.rs's own #[cfg(test)] mod tests
block — they moved with the type when the module was split out of
text_finder.rs. The five TA-prefixed
tests below cover TfAppl construction defaults and
two round-trip setters.
/*-- REQ-TA-01: TfAppl::new() initial field values --*/
#[test]
fn ta_new_hide_is_true() {
let ta = TfAppl::new();
assert!(ta.hide);
}
#[test]
fn ta_new_recurse_is_true() {
let ta = TfAppl::new();
assert!(ta.recurse);
}
#[test]
fn ta_new_curr_dir_is_empty() {
let ta = TfAppl::new();
assert_eq!(ta.curr_dir, "");
}
/*-- REQ-TA-04: hide()/get_hide() round-trip --*/
#[test]
fn ta_hide_round_trip() {
let mut ta = TfAppl::new();
ta.hide(false);
assert!(!ta.get_hide());
ta.hide(true);
assert!(ta.get_hide());
}
/*-- REQ-TA-06: TfAppl::regex() delegates to embedded TextFinder --*/
#[test]
fn ta_regex_delegates_to_text_finder() {
let mut ta = TfAppl::new();
ta.regex("hello");
assert_eq!(ta.get_regex(), "hello");
}
5.5 See Also
TfAppl depends on two sibling types that are documented
on their own pages in this thread: