ricecoder_hooks/cli/
formatter.rs1use crate::error::{HooksError, Result};
4use crate::types::{Action, Hook};
5
6pub fn format_hook_table(hook: &Hook) -> String {
8 let status = if hook.enabled {
9 "✓ Enabled"
10 } else {
11 "✗ Disabled"
12 };
13 let action_type = match &hook.action {
14 Action::Command(_) => "Command",
15 Action::ToolCall(_) => "Tool Call",
16 Action::AiPrompt(_) => "AI Prompt",
17 Action::Chain(_) => "Chain",
18 };
19
20 let mut output = String::new();
21 output.push_str(&format!("ID: {}\n", hook.id));
22 output.push_str(&format!("Name: {}\n", hook.name));
23 if let Some(desc) = &hook.description {
24 output.push_str(&format!("Description: {}\n", desc));
25 }
26 output.push_str(&format!("Event: {}\n", hook.event));
27 output.push_str(&format!("Action: {}\n", action_type));
28 output.push_str(&format!("Status: {}\n", status));
29 if !hook.tags.is_empty() {
30 output.push_str(&format!("Tags: {}\n", hook.tags.join(", ")));
31 }
32
33 output
34}
35
36pub fn format_hooks_table(hooks: &[Hook]) -> String {
38 if hooks.is_empty() {
39 return "No hooks found".to_string();
40 }
41
42 let mut output = String::new();
43 output.push_str("ID | Name | Event | Status | Action\n");
44 output.push_str("-------------------------------------|--------------------------|--------------------|---------|-----------\n");
45
46 for hook in hooks {
47 let status = if hook.enabled { "Enabled" } else { "Disabled" };
48 let action_type = match &hook.action {
49 Action::Command(_) => "Command",
50 Action::ToolCall(_) => "Tool Call",
51 Action::AiPrompt(_) => "AI Prompt",
52 Action::Chain(_) => "Chain",
53 };
54
55 let id = if hook.id.len() > 36 {
56 format!("{}...", &hook.id[..33])
57 } else {
58 hook.id.clone()
59 };
60
61 let name = if hook.name.len() > 24 {
62 format!("{}...", &hook.name[..21])
63 } else {
64 hook.name.clone()
65 };
66
67 let event = if hook.event.len() > 18 {
68 format!("{}...", &hook.event[..15])
69 } else {
70 hook.event.clone()
71 };
72
73 output.push_str(&format!(
74 "{:<36} | {:<24} | {:<18} | {:<8} | {}\n",
75 id, name, event, status, action_type
76 ));
77 }
78
79 output
80}
81
82pub fn format_hook_json(hook: &Hook) -> Result<String> {
84 serde_json::to_string_pretty(hook)
85 .map_err(|e| HooksError::InvalidConfiguration(format!("Failed to serialize hook: {}", e)))
86}
87
88pub fn format_hooks_json(hooks: &[Hook]) -> Result<String> {
90 serde_json::to_string_pretty(hooks)
91 .map_err(|e| HooksError::InvalidConfiguration(format!("Failed to serialize hooks: {}", e)))
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::types::CommandAction;
98
99 fn create_test_hook(id: &str, name: &str) -> Hook {
100 Hook {
101 id: id.to_string(),
102 name: name.to_string(),
103 description: Some("Test hook".to_string()),
104 event: "test_event".to_string(),
105 action: Action::Command(CommandAction {
106 command: "echo".to_string(),
107 args: vec!["test".to_string()],
108 timeout_ms: Some(5000),
109 capture_output: true,
110 }),
111 enabled: true,
112 tags: vec!["test".to_string()],
113 metadata: serde_json::json!({}),
114 condition: None,
115 }
116 }
117
118 #[test]
119 fn test_format_hook_table() {
120 let hook = create_test_hook("hook1", "Test Hook");
121 let output = format_hook_table(&hook);
122
123 assert!(output.contains("hook1"));
124 assert!(output.contains("Test Hook"));
125 assert!(output.contains("test_event"));
126 assert!(output.contains("Enabled"));
127 assert!(output.contains("Command"));
128 }
129
130 #[test]
131 fn test_format_hooks_table_empty() {
132 let hooks: Vec<Hook> = vec![];
133 let output = format_hooks_table(&hooks);
134
135 assert_eq!(output, "No hooks found");
136 }
137
138 #[test]
139 fn test_format_hooks_table() {
140 let hook1 = create_test_hook("hook1", "Hook 1");
141 let hook2 = create_test_hook("hook2", "Hook 2");
142 let hooks = vec![hook1, hook2];
143
144 let output = format_hooks_table(&hooks);
145
146 assert!(output.contains("hook1"));
147 assert!(output.contains("hook2"));
148 assert!(output.contains("Hook 1"));
149 assert!(output.contains("Hook 2"));
150 }
151
152 #[test]
153 fn test_format_hook_json() {
154 let hook = create_test_hook("hook1", "Test Hook");
155 let json = format_hook_json(&hook).unwrap();
156
157 assert!(json.contains("\"id\":\"hook1\"") || json.contains("\"id\": \"hook1\""));
158 assert!(json.contains("Test Hook"));
159 }
160
161 #[test]
162 fn test_format_hooks_json() {
163 let hook1 = create_test_hook("hook1", "Hook 1");
164 let hook2 = create_test_hook("hook2", "Hook 2");
165 let hooks = vec![hook1, hook2];
166
167 let json = format_hooks_json(&hooks).unwrap();
168
169 assert!(json.contains("hook1"));
170 assert!(json.contains("hook2"));
171 }
172
173 #[test]
174 fn test_format_hook_table_disabled() {
175 let mut hook = create_test_hook("hook1", "Test Hook");
176 hook.enabled = false;
177 let output = format_hook_table(&hook);
178
179 assert!(output.contains("Disabled"));
180 }
181
182 #[test]
183 fn test_format_hooks_table_truncation() {
184 let mut hook = create_test_hook("a".repeat(50).as_str(), "b".repeat(50).as_str());
185 hook.event = "c".repeat(50);
186 let output = format_hooks_table(&[hook]);
187
188 assert!(output.contains("..."));
190 }
191}