Skip to main content

command_stream/commands/
mod.rs

1//! Virtual command implementations
2//!
3//! This module contains implementations of shell commands that run in-process
4//! without spawning external processes. These provide faster execution and
5//! consistent behavior across platforms.
6
7mod basename;
8mod cat;
9pub(crate) mod cd;
10mod cp;
11mod dirname;
12mod echo;
13mod env;
14mod exit;
15mod r#false;
16mod fy;
17mod ls;
18mod mkdir;
19mod mv;
20mod pwd;
21mod rm;
22mod seq;
23mod sleep;
24mod tee;
25mod test;
26mod text;
27mod touch;
28mod r#true;
29mod which;
30mod yes;
31
32pub use basename::basename;
33pub use cat::cat;
34pub use cd::cd;
35pub use cp::cp;
36pub use dirname::dirname;
37pub use echo::echo;
38pub use env::env;
39pub use exit::exit;
40pub use fy::fy;
41pub use ls::ls;
42pub use mkdir::mkdir;
43pub use mv::mv;
44pub use pwd::pwd;
45pub use r#false::r#false;
46pub use r#true::r#true;
47pub use rm::rm;
48pub use seq::seq;
49pub use sleep::sleep;
50pub use tee::tee;
51pub use test::test;
52pub use text::{head, sort, tail, uniq};
53pub use touch::touch;
54pub use which::which;
55pub use yes::yes;
56
57use crate::utils::CommandResult;
58use std::collections::HashMap;
59use tokio::sync::mpsc;
60
61/// Context for virtual command execution
62pub struct CommandContext {
63    /// Command arguments (excluding the command name)
64    pub args: Vec<String>,
65    /// Standard input content
66    pub stdin: Option<String>,
67    /// Current working directory
68    pub cwd: Option<std::path::PathBuf>,
69    /// Environment variables
70    pub env: Option<HashMap<String, String>>,
71    /// Channel to send streaming output
72    pub output_tx: Option<mpsc::Sender<StreamChunk>>,
73    /// Cancellation check function
74    pub is_cancelled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
75}
76
77impl std::fmt::Debug for CommandContext {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        f.debug_struct("CommandContext")
80            .field("args", &self.args)
81            .field("stdin", &self.stdin)
82            .field("cwd", &self.cwd)
83            .field("env", &self.env)
84            .field("output_tx", &self.output_tx.is_some())
85            .field("is_cancelled", &self.is_cancelled.is_some())
86            .finish()
87    }
88}
89
90/// A chunk of streaming output
91#[derive(Debug, Clone)]
92pub enum StreamChunk {
93    Stdout(String),
94    Stderr(String),
95}
96
97impl CommandContext {
98    /// Create a new command context with arguments
99    pub fn new(args: Vec<String>) -> Self {
100        CommandContext {
101            args,
102            stdin: None,
103            cwd: None,
104            env: None,
105            output_tx: None,
106            is_cancelled: None,
107        }
108    }
109
110    /// Check if the command has been cancelled
111    pub fn is_cancelled(&self) -> bool {
112        self.is_cancelled.as_ref().map(|f| f()).unwrap_or(false)
113    }
114
115    /// Get the current working directory
116    pub fn get_cwd(&self) -> std::path::PathBuf {
117        self.cwd.clone().unwrap_or_else(|| {
118            std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("/"))
119        })
120    }
121}
122
123/// Type for virtual command handler functions
124pub type VirtualCommandHandler =
125    fn(
126        CommandContext,
127    ) -> std::pin::Pin<Box<dyn std::future::Future<Output = CommandResult> + Send>>;
128
129/// Registry of virtual commands
130pub struct VirtualCommandRegistry {
131    commands: HashMap<String, VirtualCommandHandler>,
132}
133
134impl Default for VirtualCommandRegistry {
135    fn default() -> Self {
136        Self::new()
137    }
138}
139
140impl VirtualCommandRegistry {
141    /// Create a new empty registry
142    pub fn new() -> Self {
143        VirtualCommandRegistry {
144            commands: HashMap::new(),
145        }
146    }
147
148    /// Create a registry with all built-in commands registered
149    pub fn with_builtins() -> Self {
150        let mut registry = Self::new();
151        registry.register_builtins();
152        registry
153    }
154
155    /// Register a virtual command
156    pub fn register(&mut self, name: &str, handler: VirtualCommandHandler) {
157        self.commands.insert(name.to_string(), handler);
158    }
159
160    /// Unregister a virtual command
161    pub fn unregister(&mut self, name: &str) -> bool {
162        self.commands.remove(name).is_some()
163    }
164
165    /// Get a virtual command handler
166    pub fn get(&self, name: &str) -> Option<&VirtualCommandHandler> {
167        self.commands.get(name)
168    }
169
170    /// Check if a command is registered
171    pub fn contains(&self, name: &str) -> bool {
172        self.commands.contains_key(name)
173    }
174
175    /// List all registered command names
176    pub fn list(&self) -> Vec<&str> {
177        self.commands.keys().map(|s| s.as_str()).collect()
178    }
179
180    /// Register all built-in commands
181    pub fn register_builtins(&mut self) {
182        macro_rules! register {
183            ($name:literal, $function:path) => {
184                self.register($name, |ctx| Box::pin($function(ctx)));
185            };
186        }
187
188        register!("echo", echo);
189        register!("pwd", pwd);
190        register!("cd", cd);
191        register!("true", r#true);
192        register!("false", r#false);
193        register!("sleep", sleep);
194        register!("cat", cat);
195        register!("ls", ls);
196        register!("mkdir", mkdir);
197        register!("rm", rm);
198        register!("touch", touch);
199        register!("cp", cp);
200        register!("mv", mv);
201        register!("basename", basename);
202        register!("dirname", dirname);
203        register!("env", env);
204        register!("exit", exit);
205        register!("which", which);
206        register!("yes", yes);
207        register!("seq", seq);
208        register!("tee", tee);
209        register!("test", test);
210        register!("head", head);
211        register!("tail", tail);
212        register!("sort", sort);
213        register!("uniq", uniq);
214    }
215}
216
217/// Global virtual commands enabled flag
218static VIRTUAL_COMMANDS_ENABLED: std::sync::atomic::AtomicBool =
219    std::sync::atomic::AtomicBool::new(true);
220
221/// Enable virtual commands
222pub fn enable_virtual_commands() {
223    VIRTUAL_COMMANDS_ENABLED.store(true, std::sync::atomic::Ordering::SeqCst);
224}
225
226/// Disable virtual commands
227pub fn disable_virtual_commands() {
228    VIRTUAL_COMMANDS_ENABLED.store(false, std::sync::atomic::Ordering::SeqCst);
229}
230
231/// Check if virtual commands are enabled
232pub fn are_virtual_commands_enabled() -> bool {
233    VIRTUAL_COMMANDS_ENABLED.load(std::sync::atomic::Ordering::SeqCst)
234}