1use crate::error::PickError;
2use serde_json::Value;
3
4pub fn parse(input: &str) -> Result<Value, PickError> {
5 let mut map = serde_json::Map::new();
6
7 for line in input.lines() {
8 let line = line.trim();
9 if line.is_empty() {
10 continue;
11 }
12
13 if let Some(eq_pos) = line.find('=') {
15 let key = line[..eq_pos].trim();
16 let value = line[eq_pos + 1..].trim();
17 if !key.is_empty()
18 && key
19 .chars()
20 .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
21 {
22 map.insert(key.to_string(), Value::String(value.to_string()));
23 continue;
24 }
25 }
26
27 if let Some(colon_pos) = line.find(':') {
29 let key = line[..colon_pos].trim();
30 let value = line[colon_pos + 1..].trim();
31 if !key.is_empty()
32 && !key.contains(' ')
33 && key
34 .chars()
35 .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
36 {
37 map.insert(key.to_string(), Value::String(value.to_string()));
38 continue;
39 }
40 }
41
42 if let Some(tab_pos) = line.find('\t') {
44 let key = line[..tab_pos].trim();
45 let value = line[tab_pos + 1..].trim();
46 if !key.is_empty()
47 && key
48 .chars()
49 .all(|c| c.is_alphanumeric() || c == '_' || c == '-' || c == '.')
50 {
51 map.insert(key.to_string(), Value::String(value.to_string()));
52 continue;
53 }
54 }
55 }
56
57 if !map.is_empty() {
58 Ok(Value::Object(map))
59 } else {
60 let lines: Vec<Value> = input
62 .lines()
63 .map(|l| Value::String(l.to_string()))
64 .collect();
65 Ok(Value::Array(lines))
66 }
67}
68
69pub fn search_text(input: &str, query: &str) -> Option<Value> {
72 for line in input.lines() {
74 let line = line.trim();
75 if line.is_empty() {
76 continue;
77 }
78
79 if let Some(eq_pos) = line.find('=') {
81 let key = line[..eq_pos].trim();
82 if key == query {
83 let value = line[eq_pos + 1..].trim();
84 return Some(Value::String(value.to_string()));
85 }
86 }
87
88 if let Some(colon_pos) = line.find(':') {
90 let key = line[..colon_pos].trim();
91 if key == query {
92 let value = line[colon_pos + 1..].trim();
93 return Some(Value::String(value.to_string()));
94 }
95 }
96 }
97
98 let matching: Vec<Value> = input
100 .lines()
101 .filter(|line| line.contains(query))
102 .map(|l| Value::String(l.to_string()))
103 .collect();
104
105 match matching.len() {
106 0 => None,
107 1 => Some(matching.into_iter().next().unwrap()),
108 _ => Some(Value::Array(matching)),
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115 use serde_json::json;
116
117 #[test]
118 fn parse_kv_equals() {
119 let v = parse("name=Alice\nage=30").unwrap();
120 assert_eq!(v["name"], json!("Alice"));
121 assert_eq!(v["age"], json!("30"));
122 }
123
124 #[test]
125 fn parse_kv_colon() {
126 let v = parse("name: Alice\nage: 30").unwrap();
127 assert_eq!(v["name"], json!("Alice"));
128 assert_eq!(v["age"], json!("30"));
129 }
130
131 #[test]
132 fn parse_kv_tab() {
133 let v = parse("name\tAlice\nage\t30").unwrap();
134 assert_eq!(v["name"], json!("Alice"));
135 assert_eq!(v["age"], json!("30"));
136 }
137
138 #[test]
139 fn parse_mixed_formats() {
140 let v = parse("name=Alice\nage: 30").unwrap();
141 assert_eq!(v["name"], json!("Alice"));
142 assert_eq!(v["age"], json!("30"));
143 }
144
145 #[test]
146 fn parse_plain_text_fallback() {
147 let v = parse("just some text\nanother line").unwrap();
148 assert!(v.is_array());
149 assert_eq!(v[0], json!("just some text"));
150 assert_eq!(v[1], json!("another line"));
151 }
152
153 #[test]
154 fn parse_empty_lines_skipped() {
155 let v = parse("\nname=Alice\n\nage=30\n").unwrap();
156 assert_eq!(v["name"], json!("Alice"));
157 }
158
159 #[test]
160 fn parse_key_with_dots() {
161 let v = parse("server.host=localhost").unwrap();
162 assert_eq!(v["server.host"], json!("localhost"));
163 }
164
165 #[test]
166 fn parse_key_with_hyphens() {
167 let v = parse("content-type: text/html").unwrap();
168 assert_eq!(v["content-type"], json!("text/html"));
169 }
170
171 #[test]
174 fn search_exact_key_equals() {
175 let result = search_text("name=Alice\nage=30", "name").unwrap();
176 assert_eq!(result, json!("Alice"));
177 }
178
179 #[test]
180 fn search_exact_key_colon() {
181 let result = search_text("name: Alice\nage: 30", "age").unwrap();
182 assert_eq!(result, json!("30"));
183 }
184
185 #[test]
186 fn search_substring_single() {
187 let result = search_text("hello world\ngoodbye world", "hello").unwrap();
188 assert_eq!(result, json!("hello world"));
189 }
190
191 #[test]
192 fn search_substring_multiple() {
193 let result = search_text("error in foo\nerror in bar\ninfo ok", "error").unwrap();
194 assert!(result.is_array());
195 assert_eq!(result.as_array().unwrap().len(), 2);
196 }
197
198 #[test]
199 fn search_no_match() {
200 assert!(search_text("hello world", "nothere").is_none());
201 }
202}