smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
// Smart Tree Only Tools - Replacing all traditional file tools!
// "One tool to rule them all!" - The Cheet 🎸

use anyhow::{bail, Context, Result};
use std::path::{Path, PathBuf};
use std::process::Command;

/// Configuration for ST-based tools
#[derive(Debug, Clone)]
pub struct StToolsConfig {
    /// Path to st binary
    pub st_binary: PathBuf,
    /// Default mode for operations
    pub default_mode: String,
    /// Whether to use emoji
    pub use_emoji: bool,
    /// Whether to compress output
    pub compress: bool,
}

impl Default for StToolsConfig {
    fn default() -> Self {
        Self {
            st_binary: std::env::current_exe()
                .ok()
                .and_then(|p| {
                    let dir = p.parent()?;
                    let st = dir.join("st");
                    if st.exists() {
                        Some(st)
                    } else {
                        None
                    }
                })
                .unwrap_or_else(|| PathBuf::from("./target/release/st")),
            default_mode: "ai".to_string(),
            use_emoji: false,
            compress: false,
        }
    }
}

/// Main ST-only tools provider
pub struct StOnlyTools {
    config: StToolsConfig,
}

impl Default for StOnlyTools {
    fn default() -> Self {
        Self::new()
    }
}

impl StOnlyTools {
    pub fn new() -> Self {
        Self {
            config: StToolsConfig::default(),
        }
    }

    pub fn with_config(config: StToolsConfig) -> Self {
        Self { config }
    }

    /// Core ST execution
    fn run_st(&self, args: Vec<String>) -> Result<String> {
        let mut cmd = Command::new(&self.config.st_binary);

        // Add standard flags
        if !self.config.use_emoji {
            cmd.arg("--no-emoji");
        }
        if self.config.compress {
            cmd.arg("--compress");
        }

        // Add provided args
        for arg in args {
            cmd.arg(arg);
        }

        let output = cmd.output().context("Failed to execute st")?;

        if !output.status.success() {
            bail!("ST failed: {}", String::from_utf8_lossy(&output.stderr));
        }

        Ok(String::from_utf8_lossy(&output.stdout).to_string())
    }

    /// List directory contents
    pub fn list(&self, path: &Path, options: ListOptions) -> Result<String> {
        let mut args = vec![
            "--mode".to_string(),
            "ls".to_string(),
            "--depth".to_string(),
            "1".to_string(),
        ];

        if let Some(pattern) = &options.pattern {
            args.push("--find".to_string());
            args.push(pattern.clone());
        }

        if let Some(file_type) = &options.file_type {
            args.push("--type".to_string());
            args.push(file_type.clone());
        }

        if let Some(sort) = &options.sort {
            args.push("--sort".to_string());
            args.push(sort.clone());
        }

        if let Some(limit) = options.limit {
            args.push("--top".to_string());
            args.push(limit.to_string());
        }

        args.push(path.to_str().unwrap().to_string());

        self.run_st(args)
    }

    /// Search in files
    pub fn search(&self, pattern: &str, path: &Path, options: SearchOptions) -> Result<String> {
        let mut args = vec![
            "--search".to_string(),
            pattern.to_string(),
            "--mode".to_string(),
            "ai".to_string(),
            "--depth".to_string(),
            "0".to_string(),
        ];

        if let Some(file_type) = &options.file_type {
            args.push("--type".to_string());
            args.push(file_type.clone());
        }

        args.push(path.to_str().unwrap().to_string());

        self.run_st(args)
    }

    /// Get directory overview
    pub fn overview(&self, path: &Path, depth: Option<usize>) -> Result<String> {
        let args = vec![
            "--mode".to_string(),
            "summary-ai".to_string(),
            "--depth".to_string(),
            depth.unwrap_or(0).to_string(),
            path.to_str().unwrap().to_string(),
        ];

        self.run_st(args)
    }

    /// Get statistics
    pub fn stats(&self, path: &Path) -> Result<String> {
        self.run_st(vec![
            "--mode".to_string(),
            "stats".to_string(),
            "--depth".to_string(),
            "0".to_string(),
            path.to_str().unwrap().to_string(),
        ])
    }

    /// Semantic analysis
    pub fn semantic(&self, path: &Path) -> Result<String> {
        self.run_st(vec![
            "--mode".to_string(),
            "semantic".to_string(),
            "--depth".to_string(),
            "0".to_string(),
            path.to_str().unwrap().to_string(),
        ])
    }
}

// Options structures
#[derive(Default, Clone)]
pub struct ListOptions {
    pub pattern: Option<String>,
    pub file_type: Option<String>,
    pub sort: Option<String>,
    pub limit: Option<usize>,
}

#[derive(Default, Clone)]
pub struct SearchOptions {
    pub file_type: Option<String>,
    pub show_line_numbers: bool,
    pub case_sensitive: bool,
}