Comparison Story: Output (TfAppl)

5.  TfApplDirEvent implementor / output layer

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: