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 use std::io::Read;
15
16 let mut buffer = Vec::new();
17 io::stdin().read_to_end(&mut buffer)?;
18
19 if let Ok(s) = String::from_utf8(buffer.clone()) {
21 return Ok(s.trim().to_string());
22 }
23
24 let (decoded, _, had_errors) = GBK.decode(&buffer);
26 if !had_errors {
27 tracing::debug!(
28 "Successfully decoded stdin from GBK encoding (Chinese Windows detected)"
29 );
30 Ok(decoded.trim().to_string())
31 } else {
32 tracing::warn!(
34 "Failed to decode stdin from both UTF-8 and GBK, using lossy UTF-8 conversion"
35 );
36 Ok(String::from_utf8_lossy(&buffer).trim().to_string())
37 }
38 }
39
40 #[cfg(not(windows))]
41 {
42 let mut buffer = String::new();
43 io::stdin().read_to_string(&mut buffer)?;
44 Ok(buffer.trim().to_string())
45 }
46}
47
48pub fn get_status_badge(status: &str) -> &'static str {
50 match status {
51 "done" => "✓",
52 "doing" => "→",
53 "todo" => "○",
54 _ => "?",
55 }
56}
57
58pub fn print_task_context(ctx: &TaskContext) -> Result<()> {
60 let badge = get_status_badge(&ctx.task.status);
62 println!("\n{} Task #{}: {}", badge, ctx.task.id, ctx.task.name);
63 println!("Status: {}", ctx.task.status);
64
65 if let Some(spec) = &ctx.task.spec {
66 println!("\nSpec:");
67 for line in spec.lines() {
68 println!(" {}", line);
69 }
70 }
71
72 if !ctx.ancestors.is_empty() {
74 println!("\nParent Chain:");
75 for (i, ancestor) in ctx.ancestors.iter().enumerate() {
76 let indent = " ".repeat(i + 1);
77 let ancestor_badge = get_status_badge(&ancestor.status);
78 println!(
79 "{}└─ {} #{}: {}",
80 indent, ancestor_badge, ancestor.id, ancestor.name
81 );
82 }
83 }
84
85 if !ctx.children.is_empty() {
87 println!("\nChildren:");
88 for child in &ctx.children {
89 let child_badge = get_status_badge(&child.status);
90 println!(" {} #{}: {}", child_badge, child.id, child.name);
91 }
92 }
93
94 if !ctx.siblings.is_empty() {
96 println!("\nSiblings:");
97 for sibling in &ctx.siblings {
98 let sibling_badge = get_status_badge(&sibling.status);
99 println!(" {} #{}: {}", sibling_badge, sibling.id, sibling.name);
100 }
101 }
102
103 if !ctx.dependencies.blocking_tasks.is_empty() {
105 println!("\nDepends on:");
106 for dep in &ctx.dependencies.blocking_tasks {
107 let dep_badge = get_status_badge(&dep.status);
108 println!(" {} #{}: {}", dep_badge, dep.id, dep.name);
109 }
110 }
111
112 if !ctx.dependencies.blocked_by_tasks.is_empty() {
114 println!("\nBlocks:");
115 for dep in &ctx.dependencies.blocked_by_tasks {
116 let dep_badge = get_status_badge(&dep.status);
117 println!(" {} #{}: {}", dep_badge, dep.id, dep.name);
118 }
119 }
120
121 println!();
122 Ok(())
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::db::models::{Task, TaskContext, TaskDependencies};
129
130 fn create_test_task(id: i64, name: &str, status: &str, parent_id: Option<i64>) -> Task {
132 Task {
133 id,
134 name: name.to_string(),
135 status: status.to_string(),
136 spec: None,
137 parent_id,
138 priority: Some(5),
139 complexity: None,
140 first_todo_at: None,
141 first_doing_at: None,
142 first_done_at: None,
143 active_form: None,
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}