command_stream/
command_packages.rs1use crate::commands::{CommandContext, VirtualCommandRegistry};
3use crate::{CommandBuilder, CommandResult, StdinOption};
4use serde::Deserialize;
5use std::collections::HashMap;
6use std::future::Future;
7use std::path::Path;
8use std::pin::Pin;
9use std::sync::Arc;
10
11pub type HandlerFuture = Pin<Box<dyn Future<Output = CommandResult> + Send>>;
12pub type PackageHandler = Arc<dyn Fn(CommandContext) -> HandlerFuture + Send + Sync>;
13
14#[derive(Clone)]
15pub struct CommandPackage {
16 pub name: String,
17 pub version: String,
18 pub commands: HashMap<String, PackageHandler>,
19}
20
21#[derive(Deserialize)]
22struct Manifest {
23 name: String,
24 version: String,
25 commands: HashMap<String, Specification>,
26}
27#[derive(Deserialize)]
28struct Specification {
29 file: String,
30 #[serde(default)]
31 args: Vec<String>,
32}
33
34impl CommandPackage {
35 pub async fn load(path: impl AsRef<Path>) -> std::io::Result<Self> {
37 let manifest: Manifest = serde_json::from_str(&tokio::fs::read_to_string(path).await?)?;
38 let commands = manifest
39 .commands
40 .into_iter()
41 .map(|(name, specification)| {
42 let command = CommandBuilder::new(specification.file).args(specification.args);
43 let handler: PackageHandler = Arc::new(move |context| {
44 let mut command = command
45 .clone()
46 .args(context.args)
47 .stdin(StdinOption::Content(context.stdin.unwrap_or_default()));
48 if let Some(cwd) = context.cwd {
49 command = command.cwd(cwd);
50 }
51 if let Some(env) = context.env {
52 for (key, value) in env {
53 command = command.env(key, value);
54 }
55 }
56 Box::pin(async move {
57 command.run().await.unwrap_or_else(|error| {
58 CommandResult::error_with_code(error.to_string(), 1)
59 })
60 })
61 });
62 (name, handler)
63 })
64 .collect();
65 Ok(Self {
66 name: manifest.name,
67 version: manifest.version,
68 commands,
69 })
70 }
71}
72
73#[derive(Default)]
74pub struct CommandEnvironment {
75 commands: HashMap<String, PackageHandler>,
76 owners: HashMap<String, String>,
77}
78
79impl CommandEnvironment {
80 pub fn new() -> Self {
81 Self::default()
82 }
83 pub fn create<F, Fut>(&mut self, name: &str, handler: F) -> Result<(), String>
84 where
85 F: Fn(CommandContext) -> Fut + Send + Sync + 'static,
86 Fut: Future<Output = CommandResult> + Send + 'static,
87 {
88 validate(name)?;
89 self.owners.remove(name);
90 self.commands.insert(
91 name.into(),
92 Arc::new(move |context| Box::pin(handler(context))),
93 );
94 Ok(())
95 }
96 pub fn list(&self) -> Vec<String> {
97 let mut names = self.commands.keys().cloned().collect::<Vec<_>>();
98 names.sort();
99 names
100 }
101 pub fn get(&self, name: &str) -> Option<PackageHandler> {
102 self.commands.get(name).cloned().or_else(|| {
103 let handler = *VirtualCommandRegistry::with_builtins().get(name)?;
104 Some(Arc::new(handler) as PackageHandler)
105 })
106 }
107 pub async fn run(&self, name: &str, context: CommandContext) -> CommandResult {
108 match self.get(name) {
109 Some(handler) => handler(context).await,
110 None => CommandResult::error_with_code(format!("Unknown command: {name}"), 127),
111 }
112 }
113 pub fn extend<F>(&mut self, name: &str, middleware: F) -> Result<(), String>
114 where
115 F: Fn(CommandContext, PackageHandler) -> HandlerFuture + Send + Sync + 'static,
116 {
117 let parent = self
118 .get(name)
119 .ok_or_else(|| format!("Unknown command: {name}"))?;
120 self.commands.insert(
121 name.into(),
122 Arc::new(move |context| middleware(context, parent.clone())),
123 );
124 Ok(())
125 }
126 pub fn inherit(&mut self, name: &str, parent: &str, args: Vec<String>) -> Result<(), String> {
127 let handler = self
128 .get(parent)
129 .ok_or_else(|| format!("Unknown command: {parent}"))?;
130 self.create(name, move |mut context| {
131 let mut prefix = args.clone();
132 prefix.extend(context.args);
133 context.args = prefix;
134 handler(context)
135 })
136 }
137 pub fn compose(&mut self, name: &str, names: &[&str]) -> Result<(), String> {
138 if names.is_empty() {
139 return Err("Composition requires commands".into());
140 }
141 let handlers = names
142 .iter()
143 .map(|name| {
144 self.get(name)
145 .ok_or_else(|| format!("Unknown command: {name}"))
146 })
147 .collect::<Result<Vec<_>, _>>()?;
148 self.create(name, move |context| {
149 let handlers = handlers.clone();
150 Box::pin(async move {
151 let mut result = CommandResult::success(context.stdin.unwrap_or_default());
152 for handler in handlers {
153 let next = CommandContext {
154 args: context.args.clone(),
155 stdin: Some(result.stdout.to_string()),
156 cwd: context.cwd.clone(),
157 env: context.env.clone(),
158 output_tx: context.output_tx.clone(),
159 is_cancelled: None,
160 };
161 result = handler(next).await;
162 if result.code != 0 {
163 break;
164 }
165 }
166 result
167 })
168 })
169 }
170 pub fn install(&mut self, package: CommandPackage) -> Result<(), String> {
172 if package.commands.is_empty() {
173 return Err("Empty command package".into());
174 }
175 for name in package.commands.keys() {
176 validate(name)?;
177 if self.commands.contains_key(name) && self.owners.get(name) != Some(&package.name) {
178 return Err(format!("Command already registered: {name}"));
179 }
180 }
181 self.uninstall(&package.name);
182 for (name, handler) in package.commands {
183 self.owners.insert(name.clone(), package.name.clone());
184 self.commands.insert(name, handler);
185 }
186 Ok(())
187 }
188 pub fn uninstall(&mut self, package: &str) {
189 let names = self
190 .owners
191 .iter()
192 .filter(|(_, owner)| owner.as_str() == package)
193 .map(|(name, _)| name.clone())
194 .collect::<Vec<_>>();
195 for name in names {
196 self.owners.remove(&name);
197 self.commands.remove(&name);
198 }
199 }
200}
201
202fn validate(name: &str) -> Result<(), String> {
203 if name.is_empty()
204 || !name
205 .chars()
206 .next()
207 .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
208 || !name
209 .chars()
210 .all(|c| c.is_ascii_alphanumeric() || "_:.-".contains(c))
211 {
212 return Err(format!("Invalid command: {name}"));
213 }
214 Ok(())
215}