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}