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_toon(&self) -> String {
297 let json_value = self.to_json();
298 toon_format::encode_default(&json_value)
299 .unwrap_or_else(|_| self.to_canonical_string())
300 }
301
302 pub fn to_json(&self) -> serde_json::Value {
313 if let Some(text) = self.as_text() {
315 return serde_json::Value::String(text.to_string());
316 }
317
318 if let Some(ref headers) = self.headers {
320 let rows: Vec<serde_json::Value> = self.root.iter().map(|node| {
321 let mut map = serde_json::Map::new();
322 if let Some(first) = headers.first() {
324 map.insert(first.clone(), serde_json::Value::String(node.name.clone()));
325 }
326 for (header, cell) in headers.iter().skip(1).zip(node.cells.iter()) {
328 map.insert(header.clone(), serde_json::Value::String(cell.clone()));
329 }
330 serde_json::Value::Object(map)
331 }).collect();
332 return serde_json::Value::Array(rows);
333 }
334
335 if !self.is_flat() {
337 fn node_to_json(node: &OutputNode) -> serde_json::Value {
338 if node.children.is_empty() {
339 serde_json::Value::Null
340 } else {
341 let mut map = serde_json::Map::new();
342 for child in &node.children {
343 map.insert(child.name.clone(), node_to_json(child));
344 }
345 serde_json::Value::Object(map)
346 }
347 }
348
349 if self.root.len() == 1 {
351 return node_to_json(&self.root[0]);
352 }
353 let mut map = serde_json::Map::new();
355 for node in &self.root {
356 map.insert(node.name.clone(), node_to_json(node));
357 }
358 return serde_json::Value::Object(map);
359 }
360
361 let items: Vec<serde_json::Value> = self.root.iter()
363 .map(|n| serde_json::Value::String(n.display_name().to_string()))
364 .collect();
365 serde_json::Value::Array(items)
366 }
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
375pub enum OutputFormat {
376 Json,
378 Toon,
380}
381
382pub fn apply_output_format(mut result: ExecResult, format: OutputFormat) -> ExecResult {
388 if result.output.is_none() && result.out.is_empty() {
389 return result;
390 }
391 match format {
392 OutputFormat::Json => {
393 let json_str = if let Some(ref output) = result.output {
394 serde_json::to_string_pretty(&output.to_json())
395 .unwrap_or_else(|_| "null".to_string())
396 } else {
397 serde_json::to_string(&result.out)
399 .unwrap_or_else(|_| "null".to_string())
400 };
401 result.out = json_str;
402 result.output = None;
404 result
405 }
406 OutputFormat::Toon => {
407 let toon_str = if let Some(ref output) = result.output {
408 output.to_toon()
409 } else {
410 toon_format::encode_default(&result.out)
412 .unwrap_or_else(|_| result.out.clone())
413 };
414 result.out = toon_str;
415 result.output = None;
417 result
418 }
419 }
420}
421
422#[derive(Debug, Clone, PartialEq)]
431pub struct ExecResult {
432 pub code: i64,
434 pub out: String,
436 pub err: String,
438 pub data: Option<Value>,
440 pub output: Option<OutputData>,
442}
443
444impl ExecResult {
445 pub fn success(out: impl Into<String>) -> Self {
447 let out = out.into();
448 let data = Self::try_parse_json(&out);
449 Self {
450 code: 0,
451 out,
452 err: String::new(),
453 data,
454 output: None,
455 }
456 }
457
458 pub fn with_output(output: OutputData) -> Self {
463 let out = output.to_canonical_string();
464 let data = Self::try_parse_json(&out);
465 Self {
466 code: 0,
467 out,
468 err: String::new(),
469 data,
470 output: Some(output),
471 }
472 }
473
474 pub fn success_data(data: Value) -> Self {
476 let out = value_to_json(&data).to_string();
477 Self {
478 code: 0,
479 out,
480 err: String::new(),
481 data: Some(data),
482 output: None,
483 }
484 }
485
486 pub fn success_with_data(out: impl Into<String>, data: Value) -> Self {
495 Self {
496 code: 0,
497 out: out.into(),
498 err: String::new(),
499 data: Some(data),
500 output: None,
501 }
502 }
503
504 pub fn failure(code: i64, err: impl Into<String>) -> Self {
506 Self {
507 code,
508 out: String::new(),
509 err: err.into(),
510 data: None,
511 output: None,
512 }
513 }
514
515 pub fn from_output(code: i64, stdout: impl Into<String>, stderr: impl Into<String>) -> Self {
517 let out = stdout.into();
518 let data = if code == 0 {
519 Self::try_parse_json(&out)
520 } else {
521 None
522 };
523 Self {
524 code,
525 out,
526 err: stderr.into(),
527 data,
528 output: None,
529 }
530 }
531
532 pub fn ok(&self) -> bool {
534 self.code == 0
535 }
536
537 pub fn get_field(&self, name: &str) -> Option<Value> {
539 match name {
540 "code" => Some(Value::Int(self.code)),
541 "ok" => Some(Value::Bool(self.ok())),
542 "out" => Some(Value::String(self.out.clone())),
543 "err" => Some(Value::String(self.err.clone())),
544 "data" => self.data.clone(),
545 _ => None,
546 }
547 }
548
549 fn try_parse_json(s: &str) -> Option<Value> {
551 let trimmed = s.trim();
552 if trimmed.is_empty() {
553 return None;
554 }
555 serde_json::from_str::<serde_json::Value>(trimmed)
556 .ok()
557 .map(json_to_value)
558 }
559}
560
561impl Default for ExecResult {
562 fn default() -> Self {
563 Self::success("")
564 }
565}
566
567pub fn json_to_value(json: serde_json::Value) -> Value {
572 match json {
573 serde_json::Value::Null => Value::Null,
574 serde_json::Value::Bool(b) => Value::Bool(b),
575 serde_json::Value::Number(n) => {
576 if let Some(i) = n.as_i64() {
577 Value::Int(i)
578 } else if let Some(f) = n.as_f64() {
579 Value::Float(f)
580 } else {
581 Value::String(n.to_string())
582 }
583 }
584 serde_json::Value::String(s) => Value::String(s),
585 serde_json::Value::Array(_) | serde_json::Value::Object(_) => Value::Json(json),
587 }
588}
589
590pub fn value_to_json(value: &Value) -> serde_json::Value {
592 match value {
593 Value::Null => serde_json::Value::Null,
594 Value::Bool(b) => serde_json::Value::Bool(*b),
595 Value::Int(i) => serde_json::Value::Number((*i).into()),
596 Value::Float(f) => {
597 serde_json::Number::from_f64(*f)
598 .map(serde_json::Value::Number)
599 .unwrap_or(serde_json::Value::Null)
600 }
601 Value::String(s) => serde_json::Value::String(s.clone()),
602 Value::Json(json) => json.clone(),
603 Value::Blob(blob) => {
604 let mut map = serde_json::Map::new();
605 map.insert("_type".to_string(), serde_json::Value::String("blob".to_string()));
606 map.insert("id".to_string(), serde_json::Value::String(blob.id.clone()));
607 map.insert("size".to_string(), serde_json::Value::Number(blob.size.into()));
608 map.insert("contentType".to_string(), serde_json::Value::String(blob.content_type.clone()));
609 if let Some(hash) = &blob.hash {
610 let hash_hex: String = hash.iter().map(|b| format!("{:02x}", b)).collect();
611 map.insert("hash".to_string(), serde_json::Value::String(hash_hex));
612 }
613 serde_json::Value::Object(map)
614 }
615 }
616}
617
618#[cfg(test)]
619mod tests {
620 use super::*;
621
622 #[test]
623 fn success_creates_ok_result() {
624 let result = ExecResult::success("hello world");
625 assert!(result.ok());
626 assert_eq!(result.code, 0);
627 assert_eq!(result.out, "hello world");
628 assert!(result.err.is_empty());
629 }
630
631 #[test]
632 fn failure_creates_non_ok_result() {
633 let result = ExecResult::failure(1, "command not found");
634 assert!(!result.ok());
635 assert_eq!(result.code, 1);
636 assert_eq!(result.err, "command not found");
637 }
638
639 #[test]
640 fn json_stdout_is_parsed() {
641 let result = ExecResult::success(r#"{"count": 42, "items": ["a", "b"]}"#);
643 assert!(result.data.is_some());
644 let data = result.data.unwrap();
645 assert!(matches!(data, Value::Json(_)));
647 if let Value::Json(json) = data {
649 assert_eq!(json.get("count"), Some(&serde_json::json!(42)));
650 assert_eq!(json.get("items"), Some(&serde_json::json!(["a", "b"])));
651 }
652 }
653
654 #[test]
655 fn non_json_stdout_has_no_data() {
656 let result = ExecResult::success("just plain text");
657 assert!(result.data.is_none());
658 }
659
660 #[test]
661 fn get_field_code() {
662 let result = ExecResult::failure(127, "not found");
663 assert_eq!(result.get_field("code"), Some(Value::Int(127)));
664 }
665
666 #[test]
667 fn get_field_ok() {
668 let success = ExecResult::success("hi");
669 let failure = ExecResult::failure(1, "err");
670 assert_eq!(success.get_field("ok"), Some(Value::Bool(true)));
671 assert_eq!(failure.get_field("ok"), Some(Value::Bool(false)));
672 }
673
674 #[test]
675 fn get_field_out_and_err() {
676 let result = ExecResult::from_output(1, "stdout text", "stderr text");
677 assert_eq!(result.get_field("out"), Some(Value::String("stdout text".into())));
678 assert_eq!(result.get_field("err"), Some(Value::String("stderr text".into())));
679 }
680
681 #[test]
682 fn get_field_data() {
683 let result = ExecResult::success(r#"{"key": "value"}"#);
684 let data = result.get_field("data");
685 assert!(data.is_some());
686 }
687
688 #[test]
689 fn get_field_unknown_returns_none() {
690 let result = ExecResult::success("");
691 assert_eq!(result.get_field("nonexistent"), None);
692 }
693
694 #[test]
695 fn success_data_creates_result_with_value() {
696 let value = Value::String("test data".into());
697 let result = ExecResult::success_data(value.clone());
698 assert!(result.ok());
699 assert_eq!(result.data, Some(value));
700 }
701
702 #[test]
703 fn entry_type_variants() {
704 assert_ne!(EntryType::File, EntryType::Directory);
706 assert_ne!(EntryType::Directory, EntryType::Executable);
707 assert_ne!(EntryType::Executable, EntryType::Symlink);
708 }
709
710 #[test]
711 fn to_json_simple_text() {
712 let output = OutputData::text("hello world");
713 assert_eq!(output.to_json(), serde_json::json!("hello world"));
714 }
715
716 #[test]
717 fn to_json_flat_list() {
718 let output = OutputData::nodes(vec![
719 OutputNode::new("file1"),
720 OutputNode::new("file2"),
721 OutputNode::new("file3"),
722 ]);
723 assert_eq!(output.to_json(), serde_json::json!(["file1", "file2", "file3"]));
724 }
725
726 #[test]
727 fn to_json_table() {
728 let output = OutputData::table(
729 vec!["NAME".into(), "SIZE".into(), "TYPE".into()],
730 vec![
731 OutputNode::new("foo.rs").with_cells(vec!["1024".into(), "file".into()]),
732 OutputNode::new("bar/").with_cells(vec!["4096".into(), "dir".into()]),
733 ],
734 );
735 assert_eq!(output.to_json(), serde_json::json!([
736 {"NAME": "foo.rs", "SIZE": "1024", "TYPE": "file"},
737 {"NAME": "bar/", "SIZE": "4096", "TYPE": "dir"},
738 ]));
739 }
740
741 #[test]
742 fn to_json_tree() {
743 let child1 = OutputNode::new("main.rs").with_entry_type(EntryType::File);
744 let child2 = OutputNode::new("utils.rs").with_entry_type(EntryType::File);
745 let subdir = OutputNode::new("lib")
746 .with_entry_type(EntryType::Directory)
747 .with_children(vec![child2]);
748 let root = OutputNode::new("src")
749 .with_entry_type(EntryType::Directory)
750 .with_children(vec![child1, subdir]);
751
752 let output = OutputData::nodes(vec![root]);
753 assert_eq!(output.to_json(), serde_json::json!({
754 "main.rs": null,
755 "lib": {"utils.rs": null},
756 }));
757 }
758
759 #[test]
760 fn to_json_tree_multiple_roots() {
761 let root1 = OutputNode::new("src")
762 .with_entry_type(EntryType::Directory)
763 .with_children(vec![OutputNode::new("main.rs")]);
764 let root2 = OutputNode::new("docs")
765 .with_entry_type(EntryType::Directory)
766 .with_children(vec![OutputNode::new("README.md")]);
767
768 let output = OutputData::nodes(vec![root1, root2]);
769 assert_eq!(output.to_json(), serde_json::json!({
770 "src": {"main.rs": null},
771 "docs": {"README.md": null},
772 }));
773 }
774
775 #[test]
776 fn to_json_empty() {
777 let output = OutputData::new();
778 assert_eq!(output.to_json(), serde_json::json!([]));
779 }
780
781 #[test]
784 fn to_toon_text() {
785 let output = OutputData::text("hello world");
786 let toon = output.to_toon();
787 let decoded = toon_format::decode_default::<serde_json::Value>(&toon).expect("valid TOON");
789 assert_eq!(decoded, serde_json::json!("hello world"));
790 }
791
792 #[test]
793 fn to_toon_table() {
794 let output = OutputData::table(
795 vec!["NAME".into(), "SIZE".into()],
796 vec![
797 OutputNode::new("foo.rs").with_cells(vec!["1024".into()]),
798 OutputNode::new("bar/").with_cells(vec!["4096".into()]),
799 ],
800 );
801 let toon = output.to_toon();
802 let decoded = toon_format::decode_default::<serde_json::Value>(&toon).expect("valid TOON");
803 assert_eq!(decoded, serde_json::json!([
804 {"NAME": "foo.rs", "SIZE": "1024"},
805 {"NAME": "bar/", "SIZE": "4096"},
806 ]));
807 }
808
809 #[test]
810 fn to_toon_flat_list() {
811 let output = OutputData::nodes(vec![
812 OutputNode::new("alpha"),
813 OutputNode::new("bravo"),
814 OutputNode::new("charlie"),
815 ]);
816 let toon = output.to_toon();
817 let decoded = toon_format::decode_default::<serde_json::Value>(&toon).expect("valid TOON");
818 assert_eq!(decoded, serde_json::json!(["alpha", "bravo", "charlie"]));
819 }
820
821 #[test]
822 fn apply_output_format_toon() {
823 let output = OutputData::table(
824 vec!["KEY".into(), "VALUE".into()],
825 vec![
826 OutputNode::new("HOME").with_cells(vec!["/home/user".into()]),
827 ],
828 );
829 let result = ExecResult::with_output(output);
830 let formatted = apply_output_format(result, OutputFormat::Toon);
831 let decoded = toon_format::decode_default::<serde_json::Value>(&formatted.out).expect("valid TOON");
833 assert_eq!(decoded, serde_json::json!([
834 {"KEY": "HOME", "VALUE": "/home/user"},
835 ]));
836 }
837
838 #[test]
839 fn apply_output_format_toon_text_only() {
840 let result = ExecResult::success("just text");
841 let formatted = apply_output_format(result, OutputFormat::Toon);
842 let decoded = toon_format::decode_default::<serde_json::Value>(&formatted.out).expect("valid TOON");
843 assert_eq!(decoded, serde_json::json!("just text"));
844 }
845
846 #[test]
847 fn apply_output_format_clears_sentinel() {
848 let output = OutputData::table(
849 vec!["NAME".into()],
850 vec![OutputNode::new("test")],
851 );
852 let result = ExecResult::with_output(output);
853 assert!(result.output.is_some(), "before: sentinel present");
854
855 let formatted = apply_output_format(result, OutputFormat::Json);
856 assert!(formatted.output.is_none(), "after Json: sentinel cleared");
857
858 let output = OutputData::nodes(vec![OutputNode::new("a")]);
859 let result = ExecResult::with_output(output);
860 let formatted = apply_output_format(result, OutputFormat::Toon);
861 assert!(formatted.output.is_none(), "after Toon: sentinel cleared");
862 }
863
864 #[test]
865 fn apply_output_format_no_double_encoding() {
866 let output = OutputData::nodes(vec![
869 OutputNode::new("file1"),
870 OutputNode::new("file2"),
871 ]);
872 let result = ExecResult::with_output(output);
873
874 let after_json = apply_output_format(result, OutputFormat::Json);
876 let json_out = after_json.out.clone();
877 assert!(after_json.output.is_none(), "sentinel cleared by Json");
878
879 assert!(after_json.output.is_none());
884 let parsed: serde_json::Value = serde_json::from_str(&json_out).expect("valid JSON");
886 assert_eq!(parsed, serde_json::json!(["file1", "file2"]));
887 }
888}