daemon_console/
command.rs

1use crate::{TerminalApp, get_error, get_info, get_warn};
2use async_trait::async_trait;
3use futures::future::BoxFuture;
4use tokio::task::JoinHandle;
5
6/// Result from command execution
7#[derive(Debug)]
8pub struct CommandResult {
9    pub command: String,
10    pub output: String,
11}
12
13/// Trait for synchronous command handlers that can be registered with the terminal application.
14///
15/// All synchronous commands must implement this trait to be executable within the terminal app.
16/// Handlers receive mutable access to the application state and command arguments.
17pub trait CommandHandler: Send + Sync + 'static {
18    /// Executes the command with the given application state and arguments.
19    ///
20    /// # Arguments
21    ///
22    /// * `app` - Mutable reference to the terminal application
23    /// * `args` - Slice of command arguments
24    ///
25    /// # Returns
26    ///
27    /// String output to be displayed to the user
28    fn execute(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
29}
30
31/// Trait for asynchronous command handlers that can be registered with the terminal application.
32///
33/// All asynchronous commands must implement this trait to be executable within the terminal app.
34/// Handlers receive mutable access to the application state and command arguments.
35#[async_trait]
36pub trait AsyncCommandHandler: Send + Sync + 'static {
37    /// Executes the command asynchronously with the given application state and arguments.
38    ///
39    /// # Arguments
40    ///
41    /// * `app` - Mutable reference to the terminal application
42    /// * `args` - Slice of command arguments
43    ///
44    /// # Returns
45    ///
46    /// String output to be displayed to the user
47    async fn execute_async(&mut self, app: &mut TerminalApp, args: &[&str]) -> String;
48
49    /// Creates a boxed clone of this handler for reuse
50    fn box_clone(&self) -> Box<dyn AsyncCommandHandler>;
51}
52
53/// Internal enum to hold either sync or async command handlers.
54pub enum CommandHandlerType {
55    PubSync(Box<dyn CommandHandler>),
56    PubAsync(Box<dyn AsyncCommandHandler>),
57}
58
59/// Status of running commands
60#[derive(Debug)]
61pub struct RunningCommand {
62    pub command: String,
63    pub handle: JoinHandle<String>,
64}
65
66// Re-export the variants with expected names inside crate via type aliasing
67impl CommandHandlerType {
68    pub fn as_sync(&self) -> bool {
69        matches!(self, CommandHandlerType::PubSync(_))
70    }
71}
72
73pub type UnknownCommandHandler = Box<dyn Fn(&str) -> String + Send + Sync + 'static>;
74pub type AsyncUnknownCommandHandler =
75    Box<dyn Fn(&str) -> BoxFuture<'static, String> + Send + Sync + 'static>;
76
77// Blanket implementation of `CommandHandler` for closures.
78impl<F> CommandHandler for F
79where
80    F: FnMut(&mut TerminalApp, &[&str]) -> String + Send + Sync + 'static,
81{
82    fn execute(&mut self, app: &mut TerminalApp, args: &[&str]) -> String {
83        self(app, args)
84    }
85}
86
87/// Executes a command by looking it up in the registered commands.
88///
89/// For sync commands, executes immediately and returns the result.
90/// For async commands, spawns them in the background and returns immediately.
91///
92/// # Arguments
93///
94/// * `app` - Terminal application
95/// * `command` - Full command string including arguments
96///
97/// # Returns
98///
99/// String output from the command execution (empty for async commands)
100pub async fn execute_command(app: &mut TerminalApp, command: &str) -> String {
101    let parts: Vec<&str> = command.split_whitespace().collect();
102    if parts.is_empty() {
103        return String::new();
104    }
105
106    let cmd_name = parts[0];
107    let args = &parts[1..];
108
109    if let Some(handler) = app.commands.get(cmd_name) {
110        match handler {
111            CommandHandlerType::PubSync(_) => {
112                // Remove, execute, and put back sync handler
113                if let Some(CommandHandlerType::PubSync(mut sync_handler)) =
114                    app.commands.remove(cmd_name)
115                {
116                    let result = sync_handler.execute(app, args);
117                    app.commands.insert(
118                        cmd_name.to_string(),
119                        CommandHandlerType::PubSync(sync_handler),
120                    );
121                    result
122                } else {
123                    get_error!("Internal error: sync handler not found", "CommandStatus")
124                }
125            }
126            CommandHandlerType::PubAsync(async_handler) => {
127                // Clone the async handler for execution
128                let cloned_handler = async_handler.box_clone();
129                match app
130                    .spawn_async_command(command.to_string(), cloned_handler)
131                    .await
132                {
133                    Ok(_) => {
134                        get_info!(
135                            &format!("Async command '{}' started in the background", cmd_name),
136                            "CommandStatus"
137                        )
138                    }
139                    Err(e) => {
140                        get_error!(
141                            &format!("Failed to spawn async command: {}", e),
142                            "CommandStatus"
143                        )
144                    }
145                }
146            }
147        }
148    } else if let Some(ref handler) = app.async_unknown_command_handler {
149        handler(command).await
150    } else if let Some(ref handler) = app.unknown_command_handler {
151        handler(command)
152    } else {
153        get_warn!(
154            &format!("Command not found or registered: '{}'", command),
155            "CommandStatus"
156        )
157    }
158}