capsula_capture_command/
lib.rs1mod config;
2mod error;
3
4use crate::config::{CommandHookConfig, CommandHookFactory};
5use crate::error::CommandHookError;
6use capsula_core::captured::Captured;
7use capsula_core::error::CapsulaResult;
8use capsula_core::hook::{Hook, HookFactory, RuntimeParams};
9
10pub const KEY: &str = "capture-command";
11
12#[derive(Debug)]
13pub struct CommandHook {
14 pub config: CommandHookConfig,
15 pub command: Vec<String>,
16 pub abort_on_failure: bool,
17}
18
19#[derive(Debug)]
20pub struct CommandCaptured {
21 pub command: Vec<String>,
22 pub stdout: String,
23 pub stderr: String,
24 pub status: i32,
25 pub abort_requested: bool,
26}
27
28impl Hook for CommandHook {
29 type Config = CommandHookConfig;
30 type Output = CommandCaptured;
31
32 fn id(&self) -> String {
33 KEY.to_string()
34 }
35
36 fn config(&self) -> &Self::Config {
37 &self.config
38 }
39
40 fn run(&self, _params: &RuntimeParams) -> CapsulaResult<Self::Output> {
41 use std::process::Command;
42
43 if self.command.is_empty() {
44 return Err(CommandHookError::EmptyCommand.into());
45 }
46
47 let mut cmd = Command::new(&self.command[0]);
48 if self.command.len() > 1 {
49 cmd.args(&self.command[1..]);
50 }
51
52 let output = cmd
53 .output()
54 .map_err(|source| CommandHookError::ExecutionFailed {
55 command: self.command.join(" "),
56 source,
57 })?;
58
59 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
60 let stderr = String::from_utf8_lossy(&output.stderr).to_string();
61 let status = output.status.code().unwrap_or(-1);
62
63 Ok(CommandCaptured {
64 command: self.command.clone(),
65 stdout,
66 stderr,
67 status,
68 abort_requested: self.abort_on_failure && status != 0,
69 })
70 }
71}
72
73impl Captured for CommandCaptured {
74 fn to_json(&self) -> serde_json::Value {
75 serde_json::json!({
76 "stdout": self.stdout,
77 "stderr": self.stderr,
78 "status": self.status,
79 "abort_requested": self.abort_requested,
80 })
81 }
82
83 fn abort_requested(&self) -> bool {
84 self.abort_requested
85 }
86}
87
88pub fn create_factory() -> Box<dyn HookFactory> {
89 Box::new(CommandHookFactory)
90}