intent_engine/cli_handlers/
utils.rs1use crate::error::Result;
6use crate::tasks::TaskContext;
7use std::io::{self, Read};
8
9pub fn read_stdin() -> Result<String> {
11 #[cfg(windows)]
12 {
13 use encoding_rs::GBK;
14
15 let mut buffer = Vec::new();
16 io::stdin().read_to_end(&mut buffer)?;
17
18 if let Ok(s) = String::from_utf8(buffer.clone()) {
20 return Ok(s.trim().to_string());
21 }
22
23 let (decoded, _, had_errors) = GBK.decode(&buffer);
25 if !had_errors {
26 tracing::debug!(
27 "Successfully decoded stdin from GBK encoding (Chinese Windows detected)"
28 );
29 Ok(decoded.trim().to_string())
30 } else {
31 tracing::warn!(
33 "Failed to decode stdin from both UTF-8 and GBK, using lossy UTF-8 conversion"
34 );
35 Ok(String::from_utf8_lossy(&buffer).trim().to_string())
36 }
37 }
38
39 #[cfg(not(windows))]
40 {
41 let mut buffer = String::new();
42 io::stdin().read_to_string(&mut buffer)?;
43 Ok(buffer.trim().to_string())
44 }
45}
46
47pub fn get_status_badge(status: &str) -> &'static str {
49 match status {
50 "done" => "✓",
51 "doing" => "→",
52 "todo" => "○",
53 _ => "?",
54 }
55}
56
57pub fn print_task_context(ctx: &TaskContext) -> Result<()> {
59 let badge = get_status_badge(&ctx.task.status);
61 println!("\n{} Task #{}: {}", badge, ctx.task.id, ctx.task.name);
62 println!("Status: {}", ctx.task.status);
63
64 if let Some(spec) = &ctx.task.spec {
65 println!("\nSpec:");
66 for line in spec.lines() {
67 println!(" {}", line);
68 }
69 }
70
71 if !ctx.ancestors.is_empty() {
73 println!("\nParent Chain:");
74 for (i, ancestor) in ctx.ancestors.iter().enumerate() {
75 let indent = " ".repeat(i + 1);
76 let ancestor_badge = get_status_badge(&ancestor.status);
77 println!(
78 "{}└─ {} #{}: {}",
79 indent, ancestor_badge, ancestor.id, ancestor.name
80 );
81 }
82 }
83
84 if !ctx.children.is_empty() {
86 println!("\nChildren:");
87 for child in &ctx.children {
88 let child_badge = get_status_badge(&child.status);
89 println!(" {} #{}: {}", child_badge, child.id, child.name);
90 }
91 }
92
93 if !ctx.siblings.is_empty() {
95 println!("\nSiblings:");
96 for sibling in &ctx.siblings {
97 let sibling_badge = get_status_badge(&sibling.status);
98 println!(" {} #{}: {}", sibling_badge, sibling.id, sibling.name);
99 }
100 }
101
102 if !ctx.dependencies.blocking_tasks.is_empty() {
104 println!("\nDepends on:");
105 for dep in &ctx.dependencies.blocking_tasks {
106 let dep_badge = get_status_badge(&dep.status);
107 println!(" {} #{}: {}", dep_badge, dep.id, dep.name);
108 }
109 }
110
111 if !ctx.dependencies.blocked_by_tasks.is_empty() {
113 println!("\nBlocks:");
114 for dep in &ctx.dependencies.blocked_by_tasks {
115 let dep_badge = get_status_badge(&dep.status);
116 println!(" {} #{}: {}", dep_badge, dep.id, dep.name);
117 }
118 }
119
120 println!();
121 Ok(())
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127 use crate::db::models::{Task, TaskContext, TaskDependencies};
128
129 fn create_test_task(id: i64, name: &str, status: &str, parent_id: Option<i64>) -> Task {
131 Task {
132 id,
133 name: name.to_string(),
134 status: status.to_string(),
135 spec: None,
136 parent_id,
137 priority: Some(5),
138 complexity: None,
139 first_todo_at: None,
140 first_doing_at: None,
141 first_done_at: None,
142 active_form: None,
143 owner: "human".to_string(),
144 }
145 }
146
147 #[test]
148 fn test_get_status_badge_done() {
149 assert_eq!(get_status_badge("done"), "✓");
150 }
151
152 #[test]
153 fn test_get_status_badge_doing() {
154 assert_eq!(get_status_badge("doing"), "→");
155 }
156
157 #[test]
158 fn test_get_status_badge_todo() {
159 assert_eq!(get_status_badge("todo"), "○");
160 }
161
162 #[test]
163 fn test_get_status_badge_unknown() {
164 assert_eq!(get_status_badge("unknown"), "?");
165 assert_eq!(get_status_badge(""), "?");
166 assert_eq!(get_status_badge("invalid"), "?");
167 }
168
169 #[test]
170 fn test_print_task_context_basic() {
171 let task = create_test_task(1, "Test Task", "todo", None);
172
173 let ctx = TaskContext {
174 task,
175 ancestors: vec![],
176 children: vec![],
177 siblings: vec![],
178 dependencies: TaskDependencies {
179 blocking_tasks: vec![],
180 blocked_by_tasks: vec![],
181 },
182 };
183
184 let result = print_task_context(&ctx);
186 assert!(result.is_ok());
187 }
188
189 #[test]
190 fn test_print_task_context_with_spec() {
191 let mut task = create_test_task(2, "Task with Spec", "doing", None);
192 task.spec = Some("This is a\nmulti-line\nspecification".to_string());
193
194 let ctx = TaskContext {
195 task,
196 ancestors: vec![],
197 children: vec![],
198 siblings: vec![],
199 dependencies: TaskDependencies {
200 blocking_tasks: vec![],
201 blocked_by_tasks: vec![],
202 },
203 };
204
205 let result = print_task_context(&ctx);
206 assert!(result.is_ok());
207 }
208
209 #[test]
210 fn test_print_task_context_with_children() {
211 let task = create_test_task(3, "Parent Task", "doing", None);
212 let child1 = create_test_task(4, "Child Task 1", "todo", Some(3));
213 let child2 = create_test_task(5, "Child Task 2", "done", Some(3));
214
215 let ctx = TaskContext {
216 task,
217 ancestors: vec![],
218 children: vec![child1, child2],
219 siblings: vec![],
220 dependencies: TaskDependencies {
221 blocking_tasks: vec![],
222 blocked_by_tasks: vec![],
223 },
224 };
225
226 let result = print_task_context(&ctx);
227 assert!(result.is_ok());
228 }
229
230 #[test]
231 fn test_print_task_context_with_ancestors() {
232 let task = create_test_task(6, "Nested Task", "doing", Some(7));
233 let parent = create_test_task(7, "Parent Task", "doing", None);
234
235 let ctx = TaskContext {
236 task,
237 ancestors: vec![parent],
238 children: vec![],
239 siblings: vec![],
240 dependencies: TaskDependencies {
241 blocking_tasks: vec![],
242 blocked_by_tasks: vec![],
243 },
244 };
245
246 let result = print_task_context(&ctx);
247 assert!(result.is_ok());
248 }
249
250 #[test]
251 fn test_print_task_context_with_dependencies() {
252 let task = create_test_task(8, "Task with Dependencies", "todo", None);
253 let blocker = create_test_task(9, "Blocking Task", "doing", None);
254 let blocked = create_test_task(10, "Blocked Task", "todo", None);
255
256 let ctx = TaskContext {
257 task,
258 ancestors: vec![],
259 children: vec![],
260 siblings: vec![],
261 dependencies: TaskDependencies {
262 blocking_tasks: vec![blocker],
263 blocked_by_tasks: vec![blocked],
264 },
265 };
266
267 let result = print_task_context(&ctx);
268 assert!(result.is_ok());
269 }
270
271 #[test]
272 fn test_print_task_context_with_siblings() {
273 let task = create_test_task(11, "Task with Siblings", "doing", Some(12));
274 let sibling = create_test_task(13, "Sibling Task", "todo", Some(12));
275
276 let ctx = TaskContext {
277 task,
278 ancestors: vec![],
279 children: vec![],
280 siblings: vec![sibling],
281 dependencies: TaskDependencies {
282 blocking_tasks: vec![],
283 blocked_by_tasks: vec![],
284 },
285 };
286
287 let result = print_task_context(&ctx);
288 assert!(result.is_ok());
289 }
290}