command_stream/commands/
mod.rs1mod basename;
8mod cat;
9mod 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 test;
24mod touch;
25mod r#true;
26mod which;
27mod yes;
28
29pub use basename::basename;
30pub use cat::cat;
31pub use cd::cd;
32pub use cp::cp;
33pub use dirname::dirname;
34pub use echo::echo;
35pub use env::env;
36pub use exit::exit;
37pub use ls::ls;
38pub use mkdir::mkdir;
39pub use mv::mv;
40pub use pwd::pwd;
41pub use r#false::r#false;
42pub use r#true::r#true;
43pub use rm::rm;
44pub use seq::seq;
45pub use sleep::sleep;
46pub use test::test;
47pub use touch::touch;
48pub use which::which;
49pub use yes::yes;
50
51use crate::utils::CommandResult;
52use std::collections::HashMap;
53use std::path::Path;
54use tokio::sync::mpsc;
55
56pub struct CommandContext {
58 pub args: Vec<String>,
60 pub stdin: Option<String>,
62 pub cwd: Option<std::path::PathBuf>,
64 pub env: Option<HashMap<String, String>>,
66 pub output_tx: Option<mpsc::Sender<StreamChunk>>,
68 pub is_cancelled: Option<Box<dyn Fn() -> bool + Send + Sync>>,
70}
71
72impl std::fmt::Debug for CommandContext {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 f.debug_struct("CommandContext")
75 .field("args", &self.args)
76 .field("stdin", &self.stdin)
77 .field("cwd", &self.cwd)
78 .field("env", &self.env)
79 .field("output_tx", &self.output_tx.is_some())
80 .field("is_cancelled", &self.is_cancelled.is_some())
81 .finish()
82 }
83}
84
85#[derive(Debug, Clone)]
87pub enum StreamChunk {
88 Stdout(String),
89 Stderr(String),
90}
91
92impl CommandContext {
93 pub fn new(args: Vec<String>) -> Self {
95 CommandContext {
96 args,
97 stdin: None,
98 cwd: None,
99 env: None,
100 output_tx: None,
101 is_cancelled: None,
102 }
103 }
104
105 pub fn is_cancelled(&self) -> bool {
107 self.is_cancelled.as_ref().map(|f| f()).unwrap_or(false)
108 }
109
110 pub fn get_cwd(&self) -> std::path::PathBuf {
112 self.cwd.clone().unwrap_or_else(|| {
113 std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("/"))
114 })
115 }
116}
117
118pub type VirtualCommandHandler =
120 fn(
121 CommandContext,
122 ) -> std::pin::Pin<Box<dyn std::future::Future<Output = CommandResult> + Send>>;
123
124pub struct VirtualCommandRegistry {
126 commands: HashMap<String, VirtualCommandHandler>,
127}
128
129impl Default for VirtualCommandRegistry {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl VirtualCommandRegistry {
136 pub fn new() -> Self {
138 VirtualCommandRegistry {
139 commands: HashMap::new(),
140 }
141 }
142
143 pub fn with_builtins() -> Self {
145 let mut registry = Self::new();
146 registry.register_builtins();
147 registry
148 }
149
150 pub fn register(&mut self, name: &str, handler: VirtualCommandHandler) {
152 self.commands.insert(name.to_string(), handler);
153 }
154
155 pub fn unregister(&mut self, name: &str) -> bool {
157 self.commands.remove(name).is_some()
158 }
159
160 pub fn get(&self, name: &str) -> Option<&VirtualCommandHandler> {
162 self.commands.get(name)
163 }
164
165 pub fn contains(&self, name: &str) -> bool {
167 self.commands.contains_key(name)
168 }
169
170 pub fn list(&self) -> Vec<&str> {
172 self.commands.keys().map(|s| s.as_str()).collect()
173 }
174
175 pub fn register_builtins(&mut self) {
177 }
181}
182
183static VIRTUAL_COMMANDS_ENABLED: std::sync::atomic::AtomicBool =
185 std::sync::atomic::AtomicBool::new(true);
186
187pub fn enable_virtual_commands() {
189 VIRTUAL_COMMANDS_ENABLED.store(true, std::sync::atomic::Ordering::SeqCst);
190}
191
192pub fn disable_virtual_commands() {
194 VIRTUAL_COMMANDS_ENABLED.store(false, std::sync::atomic::Ordering::SeqCst);
195}
196
197pub fn are_virtual_commands_enabled() -> bool {
199 VIRTUAL_COMMANDS_ENABLED.load(std::sync::atomic::Ordering::SeqCst)
200}