dumpfs 0.1.0

A tool for dumping codebase information for LLMs efficiently and effectively
Documentation
// ============================================================================
// --- File: src/opts.rs ---
// Purpose: Defines the configuration options for the scanner.
// ============================================================================

use std::{path::PathBuf, time::SystemTime};

use crate::tk::Model;

/// Configuration options for the filesystem scanner.
#[derive(Debug, Clone, clap::Args)]
pub struct FsScannerOpts {
    // --- Filtering & Traversal ---
    /// Whether to skip .gitignore files
    #[arg(long, default_value_t = false)]
    pub no_gitignore: bool,

    /// Whether to skip global git ignore file
    #[arg(long, default_value_t = false)]
    pub no_git_global: bool,

    /// Whether to skip git exclude file
    #[arg(long, default_value_t = false)]
    pub no_git_exclude: bool,

    /// Path to a custom ignore file
    #[arg(long)]
    pub custom_ignore_path: Option<PathBuf>,

    /// Patterns to ignore (comma separated)
    #[arg(short = 'i', long, value_delimiter = ',')]
    pub ignore_patterns: Vec<String>,

    /// Patterns to include (comma separated)
    #[arg(short = 'I', long, value_delimiter = ',')]
    pub include_patterns: Vec<String>,

    /// Whether to follow symlinks
    #[arg(short = 'L', long, default_value_t = false)]
    pub follow_symlinks: bool,

    /// Whether to include hidden files/dirs
    #[arg(short = 'a', long, default_value_t = false)]
    pub hidden: bool,

    /// Maximum depth to traverse
    #[arg(short = 'd', long)]
    pub max_depth: Option<usize>,

    /// Only include files modified after this Unix timestamp (handled via CLI-only field)
    #[arg(skip)]
    pub modified_after: Option<SystemTime>,

    /// Only include files modified before this Unix timestamp (handled via CLI-only field)
    #[arg(skip)]
    pub modified_before: Option<SystemTime>,

    /// Only include files with Unix permissions matching this mask
    #[arg(skip)]
    pub permissions_filter: Option<u32>,

    // --- Content & Heuristics ---
    /// Maximum file size in bytes to attempt reading content (default: 2MB)
    #[arg(short = 'S', long)]
    pub max_file_size_for_content: Option<u64>,

    /// Skip reading file content
    #[arg(long, default_value_t = false)]
    pub skip_content: bool,

    /// Ratio of non-control characters required to classify a file as text
    #[arg(long, default_value_t = 0.9)]
    pub text_detection_ratio: f32,

    /// Number of bytes to read for text detection heuristic
    #[arg(long, default_value_t = 8192)]
    pub text_detection_buffer_size: usize,

    // --- Performance & Concurrency ---
    /// Number of threads for filesystem walking (default: num_cpus)
    #[arg(long)]
    pub num_walker_threads: Option<usize>,

    /// Number of threads for processing (default: Rayon default)
    #[arg(long)]
    pub num_processor_threads: Option<usize>,

    /// Capacity of the channel between walker and processors
    #[arg(long, default_value_t = 1000)]
    pub channel_capacity: usize,

    // CLI-only timestamp options for modified_after/before
    /// Only include files modified after this Unix timestamp
    #[arg(long)]
    pub modified_after_timestamp: Option<u64>,

    /// Only include files modified before this Unix timestamp
    #[arg(long)]
    pub modified_before_timestamp: Option<u64>,

    /// Count tokens using the specified model (requires content scanning)
    #[arg(long, value_enum)]
    pub model: Option<Model>, // Moved from Cli
    #[arg(long, value_enum)]
    pub ignore_inline_tests: bool,
}

impl Default for FsScannerOpts {
    /// Provides sensible default values for scanner options.
    fn default() -> Self {
        Self {
            // Filtering & Traversal Defaults
            no_gitignore: false,
            no_git_global: false,
            no_git_exclude: false,
            custom_ignore_path: None,
            ignore_patterns: Vec::new(),
            include_patterns: Vec::new(),
            follow_symlinks: false,
            hidden: false, // By default, include hidden files/dirs
            max_depth: None,
            modified_after: None,
            modified_before: None,
            permissions_filter: None,
            // Content & Heuristics Defaults
            max_file_size_for_content: Some(2 * 1024 * 1024), // 2 MiB limit for content
            skip_content: false,
            text_detection_ratio: 0.9, // Require 90% printable chars for text
            text_detection_buffer_size: 8192, // Read 8KiB for heuristic
            // Performance & Concurrency Defaults
            num_walker_threads: None,    // Use default (num_cpus)
            num_processor_threads: None, // Use default (num_cpus)
            channel_capacity: 1000,      // Default channel buffer size
            // CLI-only fields
            modified_after_timestamp: None,
            modified_before_timestamp: None,
            // Tokenizer default
            model: None, // Initialize model option
            ignore_inline_tests: true,
        }
    }
}

impl FsScannerOpts {
    /// Process CLI-specific options, converting them to the appropriate internal format
    pub fn post_process(&mut self) {
        // Convert Unix timestamps to SystemTime if provided
        if let Some(ts) = self.modified_after_timestamp {
            self.modified_after = Some(
                SystemTime::UNIX_EPOCH
                    .checked_add(std::time::Duration::from_secs(ts))
                    .unwrap_or(SystemTime::UNIX_EPOCH),
            );
        }

        if let Some(ts) = self.modified_before_timestamp {
            self.modified_before = Some(
                SystemTime::UNIX_EPOCH
                    .checked_add(std::time::Duration::from_secs(ts))
                    .unwrap_or(SystemTime::now()),
            );
        }
    }
}