robinpath_modules/modules/
diff_mod.rs1use robinpath::{RobinPath, Value};
2
3pub fn register(rp: &mut RobinPath) {
4 rp.register_builtin("diff.lines", |args, _| {
5 let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
6 let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
7 let a_tokens: Vec<&str> = a.lines().collect();
8 let b_tokens: Vec<&str> = b.lines().collect();
9 Ok(Value::Array(diff_tokens(&a_tokens, &b_tokens)))
10 });
11
12 rp.register_builtin("diff.chars", |args, _| {
13 let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
14 let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
15 let a_chars: Vec<String> = a.chars().map(|c| c.to_string()).collect();
16 let b_chars: Vec<String> = b.chars().map(|c| c.to_string()).collect();
17 let a_refs: Vec<&str> = a_chars.iter().map(|s| s.as_str()).collect();
18 let b_refs: Vec<&str> = b_chars.iter().map(|s| s.as_str()).collect();
19 Ok(Value::Array(diff_tokens(&a_refs, &b_refs)))
20 });
21
22 rp.register_builtin("diff.words", |args, _| {
23 let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
24 let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
25 let a_words: Vec<&str> = a.split_whitespace().collect();
26 let b_words: Vec<&str> = b.split_whitespace().collect();
27 Ok(Value::Array(diff_tokens(&a_words, &b_words)))
28 });
29
30 rp.register_builtin("diff.objects", |args, _| {
31 let a = args.first().cloned().unwrap_or(Value::Null);
32 let b = args.get(1).cloned().unwrap_or(Value::Null);
33 Ok(Value::Array(diff_objects(&a, &b, String::new())))
34 });
35
36 rp.register_builtin("diff.arrays", |args, _| {
37 let a = args.first().cloned().unwrap_or(Value::Null);
38 let b = args.get(1).cloned().unwrap_or(Value::Null);
39 if let (Value::Array(arr_a), Value::Array(arr_b)) = (&a, &b) {
40 let a_keys: Vec<String> = arr_a.iter().map(|v| v.to_json_string()).collect();
41 let b_keys: Vec<String> = arr_b.iter().map(|v| v.to_json_string()).collect();
42
43 let added: Vec<Value> = arr_b
44 .iter()
45 .filter(|v| !a_keys.contains(&v.to_json_string()))
46 .cloned()
47 .collect();
48 let removed: Vec<Value> = arr_a
49 .iter()
50 .filter(|v| !b_keys.contains(&v.to_json_string()))
51 .cloned()
52 .collect();
53 let common: Vec<Value> = arr_a
54 .iter()
55 .filter(|v| b_keys.contains(&v.to_json_string()))
56 .cloned()
57 .collect();
58
59 let mut obj = indexmap::IndexMap::new();
60 obj.insert("added".to_string(), Value::Array(added));
61 obj.insert("removed".to_string(), Value::Array(removed));
62 obj.insert("common".to_string(), Value::Array(common));
63 Ok(Value::Object(obj))
64 } else {
65 let mut obj = indexmap::IndexMap::new();
66 obj.insert("added".to_string(), Value::Array(vec![]));
67 obj.insert("removed".to_string(), Value::Array(vec![]));
68 obj.insert("common".to_string(), Value::Array(vec![]));
69 Ok(Value::Object(obj))
70 }
71 });
72
73 rp.register_builtin("diff.patch", |args, _| {
74 let diff_arr = args.first().cloned().unwrap_or(Value::Null);
75 if let Value::Array(items) = &diff_arr {
76 let mut result = String::new();
77 for item in items {
78 if let Value::Object(obj) = item {
79 let typ = obj
80 .get("type")
81 .map(|v| v.to_display_string())
82 .unwrap_or_default();
83 let val = obj
84 .get("value")
85 .map(|v| v.to_display_string())
86 .unwrap_or_default();
87 if typ != "removed" {
88 result.push_str(&val);
89 }
90 }
91 }
92 Ok(Value::String(result))
93 } else {
94 Ok(Value::String(String::new()))
95 }
96 });
97
98 rp.register_builtin("diff.unified", |args, _| {
99 let a = args.first().map(|v| v.to_display_string()).unwrap_or_default();
100 let b = args.get(1).map(|v| v.to_display_string()).unwrap_or_default();
101 let a_lines: Vec<&str> = a.lines().collect();
102 let b_lines: Vec<&str> = b.lines().collect();
103 let tokens = diff_tokens(&a_lines, &b_lines);
104
105 let mut output = String::new();
106 output.push_str("--- a\n");
107 output.push_str("+++ b\n");
108
109 for token in &tokens {
110 if let Value::Object(obj) = token {
111 let typ = obj
112 .get("type")
113 .map(|v| v.to_display_string())
114 .unwrap_or_default();
115 let val = obj
116 .get("value")
117 .map(|v| v.to_display_string())
118 .unwrap_or_default();
119 match typ.as_str() {
120 "added" => output.push_str(&format!("+{}\n", val)),
121 "removed" => output.push_str(&format!("-{}\n", val)),
122 _ => output.push_str(&format!(" {}\n", val)),
123 }
124 }
125 }
126 Ok(Value::String(output.trim_end().to_string()))
127 });
128
129 rp.register_builtin("diff.isEqual", |args, _| {
130 let a = args.first().cloned().unwrap_or(Value::Null);
131 let b = args.get(1).cloned().unwrap_or(Value::Null);
132 Ok(Value::Bool(a.deep_eq(&b)))
133 });
134
135 rp.register_builtin("diff.stats", |args, _| {
136 let diff_arr = args.first().cloned().unwrap_or(Value::Null);
137 let mut additions = 0u64;
138 let mut deletions = 0u64;
139 let mut unchanged = 0u64;
140 if let Value::Array(items) = &diff_arr {
141 for item in items {
142 if let Value::Object(obj) = item {
143 let typ = obj
144 .get("type")
145 .map(|v| v.to_display_string())
146 .unwrap_or_default();
147 match typ.as_str() {
148 "added" => additions += 1,
149 "removed" => deletions += 1,
150 _ => unchanged += 1,
151 }
152 }
153 }
154 }
155 let mut obj = indexmap::IndexMap::new();
156 obj.insert("additions".to_string(), Value::Number(additions as f64));
157 obj.insert("deletions".to_string(), Value::Number(deletions as f64));
158 obj.insert("unchanged".to_string(), Value::Number(unchanged as f64));
159 Ok(Value::Object(obj))
160 });
161}
162
163fn diff_tokens(a: &[&str], b: &[&str]) -> Vec<Value> {
165 let m = a.len();
166 let n = b.len();
167
168 let mut dp = vec![vec![0usize; n + 1]; m + 1];
170 for i in 1..=m {
171 for j in 1..=n {
172 if a[i - 1] == b[j - 1] {
173 dp[i][j] = dp[i - 1][j - 1] + 1;
174 } else {
175 dp[i][j] = dp[i - 1][j].max(dp[i][j - 1]);
176 }
177 }
178 }
179
180 let mut result = Vec::new();
182 let mut i = m;
183 let mut j = n;
184 while i > 0 || j > 0 {
185 if i > 0 && j > 0 && a[i - 1] == b[j - 1] {
186 result.push(make_diff_entry("unchanged", a[i - 1]));
187 i -= 1;
188 j -= 1;
189 } else if j > 0 && (i == 0 || dp[i][j - 1] >= dp[i - 1][j]) {
190 result.push(make_diff_entry("added", b[j - 1]));
191 j -= 1;
192 } else {
193 result.push(make_diff_entry("removed", a[i - 1]));
194 i -= 1;
195 }
196 }
197 result.reverse();
198 result
199}
200
201fn make_diff_entry(typ: &str, value: &str) -> Value {
202 let mut obj = indexmap::IndexMap::new();
203 obj.insert("type".to_string(), Value::String(typ.to_string()));
204 obj.insert("value".to_string(), Value::String(value.to_string()));
205 Value::Object(obj)
206}
207
208fn diff_objects(a: &Value, b: &Value, prefix: String) -> Vec<Value> {
209 let mut changes = Vec::new();
210 if let (Value::Object(obj_a), Value::Object(obj_b)) = (a, b) {
211 for (k, v_a) in obj_a {
212 let path = if prefix.is_empty() {
213 k.clone()
214 } else {
215 format!("{}.{}", prefix, k)
216 };
217 match obj_b.get(k) {
218 Some(v_b) => {
219 if let (Value::Object(_), Value::Object(_)) = (v_a, v_b) {
220 changes.extend(diff_objects(v_a, v_b, path));
221 } else if !v_a.deep_eq(v_b) {
222 let mut obj = indexmap::IndexMap::new();
223 obj.insert("path".to_string(), Value::String(path));
224 obj.insert("type".to_string(), Value::String("changed".to_string()));
225 obj.insert("oldValue".to_string(), v_a.clone());
226 obj.insert("newValue".to_string(), v_b.clone());
227 changes.push(Value::Object(obj));
228 }
229 }
230 None => {
231 let mut obj = indexmap::IndexMap::new();
232 obj.insert("path".to_string(), Value::String(path));
233 obj.insert("type".to_string(), Value::String("removed".to_string()));
234 obj.insert("oldValue".to_string(), v_a.clone());
235 changes.push(Value::Object(obj));
236 }
237 }
238 }
239 for (k, v_b) in obj_b {
240 if !obj_a.contains_key(k) {
241 let path = if prefix.is_empty() {
242 k.clone()
243 } else {
244 format!("{}.{}", prefix, k)
245 };
246 let mut obj = indexmap::IndexMap::new();
247 obj.insert("path".to_string(), Value::String(path));
248 obj.insert("type".to_string(), Value::String("added".to_string()));
249 obj.insert("newValue".to_string(), v_b.clone());
250 changes.push(Value::Object(obj));
251 }
252 }
253 }
254 changes
255}