Skip to main content

st/
tools_st_only.rs

1// Smart Tree Only Tools - Replacing all traditional file tools!
2// "One tool to rule them all!" - The Cheet 🎸
3
4use anyhow::{bail, Context, Result};
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8/// Configuration for ST-based tools
9#[derive(Debug, Clone)]
10pub struct StToolsConfig {
11    /// Path to st binary
12    pub st_binary: PathBuf,
13    /// Default mode for operations
14    pub default_mode: String,
15    /// Whether to use emoji
16    pub use_emoji: bool,
17    /// Whether to compress output
18    pub compress: bool,
19}
20
21impl Default for StToolsConfig {
22    fn default() -> Self {
23        Self {
24            st_binary: std::env::current_exe()
25                .ok()
26                .and_then(|p| {
27                    let dir = p.parent()?;
28                    let st = dir.join("st");
29                    if st.exists() {
30                        Some(st)
31                    } else {
32                        None
33                    }
34                })
35                .unwrap_or_else(|| PathBuf::from("./target/release/st")),
36            default_mode: "ai".to_string(),
37            use_emoji: false,
38            compress: false,
39        }
40    }
41}
42
43/// Main ST-only tools provider
44pub struct StOnlyTools {
45    config: StToolsConfig,
46}
47
48impl Default for StOnlyTools {
49    fn default() -> Self {
50        Self::new()
51    }
52}
53
54impl StOnlyTools {
55    pub fn new() -> Self {
56        Self {
57            config: StToolsConfig::default(),
58        }
59    }
60
61    pub fn with_config(config: StToolsConfig) -> Self {
62        Self { config }
63    }
64
65    /// Core ST execution
66    fn run_st(&self, args: Vec<String>) -> Result<String> {
67        let mut cmd = Command::new(&self.config.st_binary);
68
69        // Add standard flags
70        if !self.config.use_emoji {
71            cmd.arg("--no-emoji");
72        }
73        if self.config.compress {
74            cmd.arg("--compress");
75        }
76
77        // Add provided args
78        for arg in args {
79            cmd.arg(arg);
80        }
81
82        let output = cmd.output().context("Failed to execute st")?;
83
84        if !output.status.success() {
85            bail!("ST failed: {}", String::from_utf8_lossy(&output.stderr));
86        }
87
88        Ok(String::from_utf8_lossy(&output.stdout).to_string())
89    }
90
91    /// List directory contents
92    pub fn list(&self, path: &Path, options: ListOptions) -> Result<String> {
93        let mut args = vec![
94            "--mode".to_string(),
95            "ls".to_string(),
96            "--depth".to_string(),
97            "1".to_string(),
98        ];
99
100        if let Some(pattern) = &options.pattern {
101            args.push("--find".to_string());
102            args.push(pattern.clone());
103        }
104
105        if let Some(file_type) = &options.file_type {
106            args.push("--type".to_string());
107            args.push(file_type.clone());
108        }
109
110        if let Some(sort) = &options.sort {
111            args.push("--sort".to_string());
112            args.push(sort.clone());
113        }
114
115        if let Some(limit) = options.limit {
116            args.push("--top".to_string());
117            args.push(limit.to_string());
118        }
119
120        args.push(path.to_str().unwrap().to_string());
121
122        self.run_st(args)
123    }
124
125    /// Search in files
126    pub fn search(&self, pattern: &str, path: &Path, options: SearchOptions) -> Result<String> {
127        let mut args = vec![
128            "--search".to_string(),
129            pattern.to_string(),
130            "--mode".to_string(),
131            "ai".to_string(),
132            "--depth".to_string(),
133            "0".to_string(),
134        ];
135
136        if let Some(file_type) = &options.file_type {
137            args.push("--type".to_string());
138            args.push(file_type.clone());
139        }
140
141        args.push(path.to_str().unwrap().to_string());
142
143        self.run_st(args)
144    }
145
146    /// Get directory overview
147    pub fn overview(&self, path: &Path, depth: Option<usize>) -> Result<String> {
148        let args = vec![
149            "--mode".to_string(),
150            "summary-ai".to_string(),
151            "--depth".to_string(),
152            depth.unwrap_or(0).to_string(),
153            path.to_str().unwrap().to_string(),
154        ];
155
156        self.run_st(args)
157    }
158
159    /// Get statistics
160    pub fn stats(&self, path: &Path) -> Result<String> {
161        self.run_st(vec![
162            "--mode".to_string(),
163            "stats".to_string(),
164            "--depth".to_string(),
165            "0".to_string(),
166            path.to_str().unwrap().to_string(),
167        ])
168    }
169
170    /// Semantic analysis
171    pub fn semantic(&self, path: &Path) -> Result<String> {
172        self.run_st(vec![
173            "--mode".to_string(),
174            "semantic".to_string(),
175            "--depth".to_string(),
176            "0".to_string(),
177            path.to_str().unwrap().to_string(),
178        ])
179    }
180}
181
182// Options structures
183#[derive(Default, Clone)]
184pub struct ListOptions {
185    pub pattern: Option<String>,
186    pub file_type: Option<String>,
187    pub sort: Option<String>,
188    pub limit: Option<usize>,
189}
190
191#[derive(Default, Clone)]
192pub struct SearchOptions {
193    pub file_type: Option<String>,
194    pub show_line_numbers: bool,
195    pub case_sensitive: bool,
196}