luff 0.2.1

Print files with formatting
Documentation
//! Output file protection utilities
//!
//! Provides reusable predicates for detecting whether a file entry is
//! likely the output destination.  Used by both `DirectoryWalker` and
//! `FileListWalker` to skip the output file during traversal.

use log::debug;
use std::path::Path;
use std::time::{Duration, SystemTime};

use crate::fs_utils::security::FileIdentity;

/// Check whether a file matches the stdout identity (inode-based, Unix only).
///
/// Returns `true` if the file should be **skipped** because it is the
/// output destination.
///
/// On non-Unix platforms this always returns `false`.
#[allow(unused_variables)]
pub fn matches_stdout(
    entry_path: &Path,
    metadata: &std::fs::Metadata,
    stdout_id: &FileIdentity,
) -> bool {
    #[cfg(unix)]
    {
        use std::os::unix::fs::MetadataExt;

        let file_id = FileIdentity {
            dev: metadata.dev(),
            ino: metadata.ino(),
        };

        if log::log_enabled!(log::Level::Debug) {
            debug!(
                "Comparing {} (dev={}, ino={}) vs stdout (dev={}, ino={})",
                entry_path.display(),
                file_id.dev,
                file_id.ino,
                stdout_id.dev,
                stdout_id.ino
            );
        }

        if file_id == *stdout_id {
            debug!(
                "Skipping output file (inode match): {}",
                entry_path.display()
            );
            return true;
        }
    }

    false
}

/// Heuristic: skip recently-created empty files that may be the output
/// destination (not yet written to, e.g. shell redirection `> out.md`).
///
/// Returns `true` if the file should be **skipped**.
pub fn is_recently_created_empty(
    entry_path: &Path,
    metadata: &std::fs::Metadata,
    threshold: Duration,
) -> bool {
    if metadata.len() != 0 {
        return false;
    }

    let dominated = metadata
        .modified()
        .ok()
        .and_then(|modified| SystemTime::now().duration_since(modified).ok())
        .is_some_and(|elapsed| elapsed < threshold);

    if dominated {
        debug!(
            "Skipping potential output file (heuristic): {}",
            entry_path.display()
        );
    }

    dominated
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn test_recently_created_empty_skips_new_empty() {
        let temp = TempDir::new().unwrap();
        let path = temp.path().join("empty.txt");
        fs::write(&path, "").unwrap();

        let metadata = fs::metadata(&path).unwrap();
        // Very large threshold → should skip
        assert!(is_recently_created_empty(
            &path,
            &metadata,
            Duration::from_secs(3600),
        ));
    }

    #[test]
    fn test_recently_created_empty_keeps_non_empty() {
        let temp = TempDir::new().unwrap();
        let path = temp.path().join("nonempty.txt");
        fs::write(&path, "content").unwrap();

        let metadata = fs::metadata(&path).unwrap();
        assert!(!is_recently_created_empty(
            &path,
            &metadata,
            Duration::from_secs(3600),
        ));
    }

    #[test]
    fn test_recently_created_empty_keeps_old_empty() {
        let temp = TempDir::new().unwrap();
        let path = temp.path().join("old_empty.txt");
        fs::write(&path, "").unwrap();

        let metadata = fs::metadata(&path).unwrap();
        // Zero threshold → nothing is "recent"
        assert!(!is_recently_created_empty(
            &path,
            &metadata,
            Duration::from_secs(0),
        ));
    }
}