Skip to main content

dot/
command.rs

1use std::collections::HashMap;
2use std::process::Command;
3
4use anyhow::{Context, Result};
5
6use crate::config::CommandConfig;
7
8pub struct SlashCommand {
9    pub name: String,
10    pub description: String,
11    command: String,
12    _timeout: u64,
13}
14
15impl SlashCommand {
16    pub fn from_config(name: &str, cfg: &CommandConfig) -> Self {
17        SlashCommand {
18            name: name.to_string(),
19            description: cfg.description.clone(),
20            command: cfg.command.clone(),
21            _timeout: cfg.timeout,
22        }
23    }
24
25    pub fn execute(&self, args: &str, cwd: &str) -> Result<String> {
26        let mut cmd = Command::new("/bin/sh");
27        cmd.arg("-c").arg(&self.command);
28        cmd.env("DOT_COMMAND", &self.name);
29        cmd.env("DOT_ARGS", args);
30        cmd.env("DOT_CWD", cwd);
31        cmd.current_dir(cwd);
32        let output = cmd
33            .output()
34            .with_context(|| format!("command '{}' failed to execute", self.name))?;
35        let stdout = String::from_utf8_lossy(&output.stdout);
36        let stderr = String::from_utf8_lossy(&output.stderr);
37        if !output.status.success() {
38            anyhow::bail!(
39                "command '{}' exited with {}: {}",
40                self.name,
41                output.status,
42                stderr
43            );
44        }
45        Ok(stdout.to_string())
46    }
47}
48
49pub struct CommandRegistry {
50    commands: HashMap<String, SlashCommand>,
51}
52
53impl CommandRegistry {
54    pub fn new() -> Self {
55        CommandRegistry {
56            commands: HashMap::new(),
57        }
58    }
59
60    pub fn register(&mut self, cmd: SlashCommand) {
61        tracing::info!("Registered command: /{}", cmd.name);
62        self.commands.insert(cmd.name.clone(), cmd);
63    }
64
65    pub fn execute(&self, name: &str, args: &str, cwd: &str) -> Result<String> {
66        match self.commands.get(name) {
67            Some(cmd) => cmd.execute(args, cwd),
68            None => anyhow::bail!("unknown command: /{}", name),
69        }
70    }
71
72    pub fn list(&self) -> Vec<(&str, &str)> {
73        self.commands
74            .values()
75            .map(|c| (c.name.as_str(), c.description.as_str()))
76            .collect()
77    }
78
79    pub fn has(&self, name: &str) -> bool {
80        self.commands.contains_key(name)
81    }
82
83    pub fn is_empty(&self) -> bool {
84        self.commands.is_empty()
85    }
86}
87
88impl Default for CommandRegistry {
89    fn default() -> Self {
90        Self::new()
91    }
92}