toon_format/tui/
repl_command.rs1use anyhow::{
4 bail,
5 Result,
6};
7
8#[derive(Debug, Clone)]
10pub struct ReplCommand {
11 pub name: String,
12 pub inline_data: Option<String>,
13 pub args: Vec<String>,
14}
15
16impl ReplCommand {
17 pub fn parse(input: &str) -> Result<Self> {
24 let input = input.trim();
25 if input.is_empty() {
26 bail!("Empty command");
27 }
28
29 let parts: Vec<&str> = input.splitn(2, ' ').collect();
30 let cmd_name = parts[0].to_string();
31
32 let (inline_data, remaining_args) = if parts.len() > 1 {
33 let rest = parts[1].trim();
34
35 if rest.starts_with('{')
37 || rest.starts_with('"')
38 || rest.starts_with('$')
39 || rest.contains(':')
40 {
41 let data_end = if rest.starts_with('{') {
42 find_matching_brace(rest) } else if rest.starts_with('$') {
44 rest.find(' ').unwrap_or(rest.len()) } else {
46 rest.find(" --").unwrap_or(rest.len()) };
48
49 let data = rest[..data_end].trim().to_string();
50 let remaining = rest[data_end..].trim();
51
52 (
53 Some(data),
54 if remaining.is_empty() {
55 vec![]
56 } else {
57 remaining
58 .split_whitespace()
59 .map(|s| s.to_string())
60 .collect()
61 },
62 )
63 } else {
64 (
65 None,
66 rest.split_whitespace().map(|s| s.to_string()).collect(),
67 )
68 }
69 } else {
70 (None, vec![])
71 };
72
73 Ok(ReplCommand {
74 name: cmd_name,
75 inline_data,
76 args: remaining_args,
77 })
78 }
79
80 pub fn has_flag(&self, flag: &str) -> bool {
81 self.args.iter().any(|a| a == flag)
82 }
83
84 pub fn get_option(&self, option: &str) -> Option<&str> {
85 self.args
86 .iter()
87 .position(|a| a == option)
88 .and_then(|i| self.args.get(i + 1))
89 .map(|s| s.as_str())
90 }
91}
92
93fn find_matching_brace(s: &str) -> usize {
94 let mut depth = 0;
95 for (i, ch) in s.chars().enumerate() {
96 match ch {
97 '{' => depth += 1,
98 '}' => {
99 depth -= 1;
100 if depth == 0 {
101 return i + 1;
102 }
103 }
104 _ => {}
105 }
106 }
107 s.len()
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn test_inline_json() {
116 let cmd = ReplCommand::parse(r#"encode {"test": true}"#).unwrap();
117 assert_eq!(cmd.name, "encode");
118 assert_eq!(cmd.inline_data, Some(r#"{"test": true}"#.to_string()));
119 }
120
121 #[test]
122 fn test_inline_toon() {
123 let cmd = ReplCommand::parse("decode name: Alice").unwrap();
124 assert_eq!(cmd.name, "decode");
125 assert_eq!(cmd.inline_data, Some("name: Alice".to_string()));
126 }
127
128 #[test]
129 fn test_with_flags() {
130 let cmd = ReplCommand::parse(r#"encode {"test": true} --fold-keys"#).unwrap();
131 assert_eq!(cmd.name, "encode");
132 assert!(cmd.inline_data.is_some());
133 assert!(cmd.has_flag("--fold-keys"));
134 }
135}