tgrep 1.6.15

Toy grep that honors .gitignore
Documentation
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

struct TempDir {
    path: PathBuf,
}

impl TempDir {
    fn new() -> Self {
        let mut path = std::env::temp_dir();
        let unique = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos();
        path.push(format!("tgrep-cli-test-{}", unique));
        fs::create_dir_all(&path).unwrap();
        Self { path }
    }

    fn path(&self) -> &Path {
        &self.path
    }

    fn write(&self, relative: &str, contents: &str) {
        let path = self.path.join(relative);
        if let Some(parent) = path.parent() {
            fs::create_dir_all(parent).unwrap();
        }
        fs::write(path, contents).unwrap();
    }
}

impl Drop for TempDir {
    fn drop(&mut self) {
        let _ = fs::remove_dir_all(&self.path);
    }
}

fn tgrep() -> Command {
    Command::new(env!("CARGO_BIN_EXE_tgrep"))
}

#[test]
fn incompatible_files_without_match_and_invert_match_flags_fail() {
    let temp = TempDir::new();

    let output = tgrep()
        .current_dir(temp.path())
        .args(["-L", "-v", "--no-color", "needle", "."])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(String::from_utf8_lossy(&output.stderr).contains("incompatible flags: -L and -v"));
}

#[test]
fn incompatible_count_and_invert_match_flags_fail() {
    let temp = TempDir::new();

    let output = tgrep()
        .current_dir(temp.path())
        .args(["-c", "-v", "--no-color", "needle", "."])
        .output()
        .unwrap();

    assert!(!output.status.success());
    assert!(String::from_utf8_lossy(&output.stderr).contains("incompatible flags: -c and -v"));
}

#[test]
fn file_type_filter_limits_results() {
    let temp = TempDir::new();
    temp.write("match.rs", "needle\n");
    temp.write("match.txt", "needle\n");

    let output = tgrep()
        .current_dir(temp.path())
        .args(["-t", "rs", "--no-color", "needle", "."])
        .output()
        .unwrap();

    let stdout = String::from_utf8_lossy(&output.stdout);

    assert!(output.status.success());
    assert!(stdout.contains("./match.rs:1: needle"));
    assert!(!stdout.contains("match.txt"));
}

#[test]
fn files_without_match_lists_only_non_matching_files() {
    let temp = TempDir::new();
    temp.write("match.txt", "needle\n");
    temp.write("miss.txt", "plain text\n");

    let output = tgrep()
        .current_dir(temp.path())
        .args(["-L", "--no-color", "needle", "."])
        .output()
        .unwrap();

    let stdout = String::from_utf8_lossy(&output.stdout);

    assert!(output.status.success());
    assert!(stdout.contains("./miss.txt"));
    assert!(!stdout.contains("match.txt"));
}