1use crate::ast::Value;
25use serde::Serialize;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize)]
36#[serde(rename_all = "lowercase")]
37pub enum EntryType {
38 #[default]
40 Text,
41 File,
43 Directory,
45 Executable,
47 Symlink,
49}
50
51#[derive(Debug, Clone, PartialEq, Default, Serialize)]
87pub struct OutputNode {
88 pub name: String,
90 pub entry_type: EntryType,
92 pub text: Option<String>,
94 pub cells: Vec<String>,
96 pub children: Vec<OutputNode>,
98}
99
100impl OutputNode {
101 pub fn new(name: impl Into<String>) -> Self {
103 Self {
104 name: name.into(),
105 ..Default::default()
106 }
107 }
108
109 pub fn text(content: impl Into<String>) -> Self {
111 Self {
112 text: Some(content.into()),
113 ..Default::default()
114 }
115 }
116
117 pub fn with_entry_type(mut self, entry_type: EntryType) -> Self {
119 self.entry_type = entry_type;
120 self
121 }
122
123 pub fn with_cells(mut self, cells: Vec<String>) -> Self {
125 self.cells = cells;
126 self
127 }
128
129 pub fn with_children(mut self, children: Vec<OutputNode>) -> Self {
131 self.children = children;
132 self
133 }
134
135 pub fn with_text(mut self, text: impl Into<String>) -> Self {
137 self.text = Some(text.into());
138 self
139 }
140
141 pub fn is_text_only(&self) -> bool {
143 self.text.is_some() && self.name.is_empty() && self.cells.is_empty() && self.children.is_empty()
144 }
145
146 pub fn has_children(&self) -> bool {
148 !self.children.is_empty()
149 }
150
151 pub fn display_name(&self) -> &str {
153 if self.name.is_empty() {
154 self.text.as_deref().unwrap_or("")
155 } else {
156 &self.name
157 }
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Default, Serialize)]
175pub struct OutputData {
176 pub headers: Option<Vec<String>>,
178 pub root: Vec<OutputNode>,
180}
181
182impl OutputData {
183 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn text(content: impl Into<String>) -> Self {
192 Self {
193 headers: None,
194 root: vec![OutputNode::text(content)],
195 }
196 }
197
198 pub fn nodes(nodes: Vec<OutputNode>) -> Self {
200 Self {
201 headers: None,
202 root: nodes,
203 }
204 }
205
206 pub fn table(headers: Vec<String>, nodes: Vec<OutputNode>) -> Self {
208 Self {
209 headers: Some(headers),
210 root: nodes,
211 }
212 }
213
214 pub fn with_headers(mut self, headers: Vec<String>) -> Self {
216 self.headers = Some(headers);
217 self
218 }
219
220 pub fn is_simple_text(&self) -> bool {
222 self.root.len() == 1 && self.root[0].is_text_only()
223 }
224
225 pub fn is_flat(&self) -> bool {
227 self.root.iter().all(|n| !n.has_children())
228 }
229
230 pub fn is_tabular(&self) -> bool {
232 self.root.iter().any(|n| !n.cells.is_empty())
233 }
234
235 pub fn as_text(&self) -> Option<&str> {
237 if self.is_simple_text() {
238 self.root[0].text.as_deref()
239 } else {
240 None
241 }
242 }
243
244 pub fn to_canonical_string(&self) -> String {
253 if let Some(text) = self.as_text() {
254 return text.to_string();
255 }
256
257 if self.is_flat() {
259 return self.root.iter()
260 .map(|n| {
261 if n.cells.is_empty() {
262 n.display_name().to_string()
263 } else {
264 let mut parts = vec![n.display_name().to_string()];
266 parts.extend(n.cells.iter().cloned());
267 parts.join("\t")
268 }
269 })
270 .collect::<Vec<_>>()
271 .join("\n");
272 }
273
274 fn format_node(node: &OutputNode) -> String {
276 if node.children.is_empty() {
277 node.name.clone()
278 } else {
279 let children: Vec<String> = node.children.iter()
280 .map(format_node)
281 .collect();
282 format!("{}/{{{}}}", node.name, children.join(","))
283 }
284 }
285
286 self.root.iter()
287 .map(format_node)
288 .collect::<Vec<_>>()
289 .join("\n")
290 }
291
292 pub fn to_json(&self) -> serde_json::Value {
303 if let Some(text) = self.as_text() {
305 return serde_json::Value::String(text.to_string());
306 }
307
308 if let Some(ref headers) = self.headers {
310 let rows: Vec<serde_json::Value> = self.root.iter().map(|node| {
311 let mut map = serde_json::Map::new();
312 if let Some(first) = headers.first() {
314 map.insert(first.clone(), serde_json::Value::String(node.name.clone()));
315 }
316 for (header, cell) in headers.iter().skip(1).zip(node.cells.iter()) {
318 map.insert(header.clone(), serde_json::Value::String(cell.clone()));
319 }
320 serde_json::Value::Object(map)
321 }).collect();
322 return serde_json::Value::Array(rows);
323 }
324
325 if !self.is_flat() {
327 fn node_to_json(node: &OutputNode) -> serde_json::Value {
328 if node.children.is_empty() {
329 serde_json::Value::Null
330 } else {
331 let mut map = serde_json::Map::new();
332 for child in &node.children {
333 map.insert(child.name.clone(), node_to_json(child));
334 }
335 serde_json::Value::Object(map)
336 }
337 }
338
339 if self.root.len() == 1 {
341 return node_to_json(&self.root[0]);
342 }
343 let mut map = serde_json::Map::new();
345 for node in &self.root {
346 map.insert(node.name.clone(), node_to_json(node));
347 }
348 return serde_json::Value::Object(map);
349 }
350
351 let items: Vec<serde_json::Value> = self.root.iter()
353 .map(|n| serde_json::Value::String(n.display_name().to_string()))
354 .collect();
355 serde_json::Value::Array(items)
356 }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub enum OutputFormat {
366 Json,
368 Toon,
370}
371
372pub fn apply_output_format(mut result: ExecResult, format: OutputFormat) -> ExecResult {
378 if result.output.is_none() && result.out.is_empty() {
379 return result;
380 }
381 match format {
382 OutputFormat::Json => {
383 let json_str = if let Some(ref output) = result.output {
384 serde_json::to_string_pretty(&output.to_json())
385 .unwrap_or_else(|_| "null".to_string())
386 } else {
387 serde_json::to_string(&result.out)
389 .unwrap_or_else(|_| "null".to_string())
390 };
391 result.out = json_str;
392 result
393 }
394 OutputFormat::Toon => {
395 result
397 }
398 }
399}
400
401#[derive(Debug, Clone, PartialEq)]
410pub struct ExecResult {
411 pub code: i64,
413 pub out: String,
415 pub err: String,
417 pub data: Option<Value>,
419 pub output: Option<OutputData>,
421}
422
423impl ExecResult {
424 pub fn success(out: impl Into<String>) -> Self {
426 let out = out.into();
427 let data = Self::try_parse_json(&out);
428 Self {
429 code: 0,
430 out,
431 err: String::new(),
432 data,
433 output: None,
434 }
435 }
436
437 pub fn with_output(output: OutputData) -> Self {
442 let out = output.to_canonical_string();
443 let data = Self::try_parse_json(&out);
444 Self {
445 code: 0,
446 out,
447 err: String::new(),
448 data,
449 output: Some(output),
450 }
451 }
452
453 pub fn success_data(data: Value) -> Self {
455 let out = value_to_json(&data).to_string();
456 Self {
457 code: 0,
458 out,
459 err: String::new(),
460 data: Some(data),
461 output: None,
462 }
463 }
464
465 pub fn success_with_data(out: impl Into<String>, data: Value) -> Self {
474 Self {
475 code: 0,
476 out: out.into(),
477 err: String::new(),
478 data: Some(data),
479 output: None,
480 }
481 }
482
483 pub fn failure(code: i64, err: impl Into<String>) -> Self {
485 Self {
486 code,
487 out: String::new(),
488 err: err.into(),
489 data: None,
490 output: None,
491 }
492 }
493
494 pub fn from_output(code: i64, stdout: impl Into<String>, stderr: impl Into<String>) -> Self {
496 let out = stdout.into();
497 let data = if code == 0 {
498 Self::try_parse_json(&out)
499 } else {
500 None
501 };
502 Self {
503 code,
504 out,
505 err: stderr.into(),
506 data,
507 output: None,
508 }
509 }
510
511 pub fn ok(&self) -> bool {
513 self.code == 0
514 }
515
516 pub fn get_field(&self, name: &str) -> Option<Value> {
518 match name {
519 "code" => Some(Value::Int(self.code)),
520 "ok" => Some(Value::Bool(self.ok())),
521 "out" => Some(Value::String(self.out.clone())),
522 "err" => Some(Value::String(self.err.clone())),
523 "data" => self.data.clone(),
524 _ => None,
525 }
526 }
527
528 fn try_parse_json(s: &str) -> Option<Value> {
530 let trimmed = s.trim();
531 if trimmed.is_empty() {
532 return None;
533 }
534 serde_json::from_str::<serde_json::Value>(trimmed)
535 .ok()
536 .map(json_to_value)
537 }
538}
539
540impl Default for ExecResult {
541 fn default() -> Self {
542 Self::success("")
543 }
544}
545
546pub fn json_to_value(json: serde_json::Value) -> Value {
551 match json {
552 serde_json::Value::Null => Value::Null,
553 serde_json::Value::Bool(b) => Value::Bool(b),
554 serde_json::Value::Number(n) => {
555 if let Some(i) = n.as_i64() {
556 Value::Int(i)
557 } else if let Some(f) = n.as_f64() {
558 Value::Float(f)
559 } else {
560 Value::String(n.to_string())
561 }
562 }
563 serde_json::Value::String(s) => Value::String(s),
564 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Json(json),
566 }
567}
568
569pub fn value_to_json(value: &Value) -> serde_json::Value {
571 match value {
572 Value::Null => serde_json::Value::Null,
573 Value::Bool(b) => serde_json::Value::Bool(*b),
574 Value::Int(i) => serde_json::Value::Number((*i).into()),
575 Value::Float(f) => {
576 serde_json::Number::from_f64(*f)
577 .map(serde_json::Value::Number)
578 .unwrap_or(serde_json::Value::Null)
579 }
580 Value::String(s) => serde_json::Value::String(s.clone()),
581 Value::Json(json) => json.clone(),
582 Value::Blob(blob) => {
583 let mut map = serde_json::Map::new();
584 map.insert("_type".to_string(), serde_json::Value::String("blob".to_string()));
585 map.insert("id".to_string(), serde_json::Value::String(blob.id.clone()));
586 map.insert("size".to_string(), serde_json::Value::Number(blob.size.into()));
587 map.insert("contentType".to_string(), serde_json::Value::String(blob.content_type.clone()));
588 if let Some(hash) = &blob.hash {
589 let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
590 map.insert("hash".to_string(), serde_json::Value::String(hash_hex));
591 }
592 serde_json::Value::Object(map)
593 }
594 }
595}
596
597#[cfg(test)]
598mod tests {
599 use super::*;
600
601 #[test]
602 fn success_creates_ok_result() {
603 let result = ExecResult::success("hello world");
604 assert!(result.ok());
605 assert_eq!(result.code, 0);
606 assert_eq!(result.out, "hello world");
607 assert!(result.err.is_empty());
608 }
609
610 #[test]
611 fn failure_creates_non_ok_result() {
612 let result = ExecResult::failure(1, "command not found");
613 assert!(!result.ok());
614 assert_eq!(result.code, 1);
615 assert_eq!(result.err, "command not found");
616 }
617
618 #[test]
619 fn json_stdout_is_parsed() {
620 let result = ExecResult::success(r#"{"count": 42, "items": ["a", "b"]}"#);
622 assert!(result.data.is_some());
623 let data = result.data.unwrap();
624 assert!(matches!(data, Value::Json(_)));
626 if let Value::Json(json) = data {
628 assert_eq!(json.get("count"), Some(&serde_json::json!(42)));
629 assert_eq!(json.get("items"), Some(&serde_json::json!(["a", "b"])));
630 }
631 }
632
633 #[test]
634 fn non_json_stdout_has_no_data() {
635 let result = ExecResult::success("just plain text");
636 assert!(result.data.is_none());
637 }
638
639 #[test]
640 fn get_field_code() {
641 let result = ExecResult::failure(127, "not found");
642 assert_eq!(result.get_field("code"), Some(Value::Int(127)));
643 }
644
645 #[test]
646 fn get_field_ok() {
647 let success = ExecResult::success("hi");
648 let failure = ExecResult::failure(1, "err");
649 assert_eq!(success.get_field("ok"), Some(Value::Bool(true)));
650 assert_eq!(failure.get_field("ok"), Some(Value::Bool(false)));
651 }
652
653 #[test]
654 fn get_field_out_and_err() {
655 let result = ExecResult::from_output(1, "stdout text", "stderr text");
656 assert_eq!(result.get_field("out"), Some(Value::String("stdout text".into())));
657 assert_eq!(result.get_field("err"), Some(Value::String("stderr text".into())));
658 }
659
660 #[test]
661 fn get_field_data() {
662 let result = ExecResult::success(r#"{"key": "value"}"#);
663 let data = result.get_field("data");
664 assert!(data.is_some());
665 }
666
667 #[test]
668 fn get_field_unknown_returns_none() {
669 let result = ExecResult::success("");
670 assert_eq!(result.get_field("nonexistent"), None);
671 }
672
673 #[test]
674 fn success_data_creates_result_with_value() {
675 let value = Value::String("test data".into());
676 let result = ExecResult::success_data(value.clone());
677 assert!(result.ok());
678 assert_eq!(result.data, Some(value));
679 }
680
681 #[test]
682 fn entry_type_variants() {
683 assert_ne!(EntryType::File, EntryType::Directory);
685 assert_ne!(EntryType::Directory, EntryType::Executable);
686 assert_ne!(EntryType::Executable, EntryType::Symlink);
687 }
688
689 #[test]
690 fn to_json_simple_text() {
691 let output = OutputData::text("hello world");
692 assert_eq!(output.to_json(), serde_json::json!("hello world"));
693 }
694
695 #[test]
696 fn to_json_flat_list() {
697 let output = OutputData::nodes(vec![
698 OutputNode::new("file1"),
699 OutputNode::new("file2"),
700 OutputNode::new("file3"),
701 ]);
702 assert_eq!(output.to_json(), serde_json::json!(["file1", "file2", "file3"]));
703 }
704
705 #[test]
706 fn to_json_table() {
707 let output = OutputData::table(
708 vec!["NAME".into(), "SIZE".into(), "TYPE".into()],
709 vec![
710 OutputNode::new("foo.rs").with_cells(vec!["1024".into(), "file".into()]),
711 OutputNode::new("bar/").with_cells(vec!["4096".into(), "dir".into()]),
712 ],
713 );
714 assert_eq!(output.to_json(), serde_json::json!([
715 {"NAME": "foo.rs", "SIZE": "1024", "TYPE": "file"},
716 {"NAME": "bar/", "SIZE": "4096", "TYPE": "dir"},
717 ]));
718 }
719
720 #[test]
721 fn to_json_tree() {
722 let child1 = OutputNode::new("main.rs").with_entry_type(EntryType::File);
723 let child2 = OutputNode::new("utils.rs").with_entry_type(EntryType::File);
724 let subdir = OutputNode::new("lib")
725 .with_entry_type(EntryType::Directory)
726 .with_children(vec![child2]);
727 let root = OutputNode::new("src")
728 .with_entry_type(EntryType::Directory)
729 .with_children(vec![child1, subdir]);
730
731 let output = OutputData::nodes(vec![root]);
732 assert_eq!(output.to_json(), serde_json::json!({
733 "main.rs": null,
734 "lib": {"utils.rs": null},
735 }));
736 }
737
738 #[test]
739 fn to_json_tree_multiple_roots() {
740 let root1 = OutputNode::new("src")
741 .with_entry_type(EntryType::Directory)
742 .with_children(vec![OutputNode::new("main.rs")]);
743 let root2 = OutputNode::new("docs")
744 .with_entry_type(EntryType::Directory)
745 .with_children(vec![OutputNode::new("README.md")]);
746
747 let output = OutputData::nodes(vec![root1, root2]);
748 assert_eq!(output.to_json(), serde_json::json!({
749 "src": {"main.rs": null},
750 "docs": {"README.md": null},
751 }));
752 }
753
754 #[test]
755 fn to_json_empty() {
756 let output = OutputData::new();
757 assert_eq!(output.to_json(), serde_json::json!([]));
758 }
759}