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