1use crate::text::{Spans, write_string};
7use crate::value::{Map, Value};
8
9const ONE_LINE: usize = 72;
11const INDENT: &str = " ";
13const DECIMALS: f64 = 10_000.0;
15
16pub fn format(value: &Value, level: usize) -> String {
20 if let Some(one) = inline(value) {
21 return one;
22 }
23 let pad = INDENT.repeat(level + 1);
24 let end = INDENT.repeat(level);
25 match value {
26 Value::List(items) => {
27 let body: Vec<String> = items
28 .iter()
29 .map(|v| format!("{pad}{}", format(v, level + 1)))
30 .collect();
31 format!("[\n{}\n{end}]", body.join(",\n"))
32 }
33 Value::Map(map) => {
34 let body: Vec<String> = map
35 .iter()
36 .map(|(k, v)| format!("{pad}{}: {}", quoted(k), format(v, level + 1)))
37 .collect();
38 format!("{{\n{}\n{end}}}", body.join(",\n"))
39 }
40 other => scalar(other).unwrap_or_default(),
41 }
42}
43
44fn quoted(s: &str) -> String {
45 let mut out = String::with_capacity(s.len() + 2);
46 write_string(s, &mut out);
47 out
48}
49
50fn number(n: f64) -> String {
51 let rounded = (n * DECIMALS).round() / DECIMALS;
52 if rounded.fract() == 0.0 && rounded.abs() < 1e15 {
53 format!("{}", rounded as i64)
54 } else {
55 rounded.to_string()
56 }
57}
58
59fn scalar(v: &Value) -> Option<String> {
60 match v {
61 Value::Null => Some("null".into()),
62 Value::Bool(b) => Some(b.to_string()),
63 Value::Int(n) => Some(n.to_string()),
64 Value::Float(f) if f.is_finite() => Some(number(*f)),
65 Value::Float(_) => Some("null".into()),
66 Value::List(_) | Value::Map(_) => None,
67 Value::Str(s) => Some(quoted(s)),
69 other => Some(crate::text::to_string(other)),
70 }
71}
72
73fn inline(v: &Value) -> Option<String> {
74 if let Some(s) = scalar(v) {
75 return Some(s);
76 }
77 let out = match v {
78 Value::List(items) if items.is_empty() => return Some("[]".into()),
79 Value::List(items) => {
80 if !items
81 .iter()
82 .all(|x| matches!(x, Value::Int(_) | Value::Float(_) | Value::Bool(_)))
83 {
84 return None;
85 }
86 format!(
87 "[{}]",
88 items
89 .iter()
90 .filter_map(scalar)
91 .collect::<Vec<_>>()
92 .join(", ")
93 )
94 }
95 Value::Map(map) if map.is_empty() => return Some("{}".into()),
96 Value::Map(map) => {
97 let mut parts = Vec::with_capacity(map.len());
98 for (k, x) in map.iter() {
99 if matches!(x, Value::Map(_)) {
100 return None;
101 }
102 parts.push(format!("{}: {}", quoted(k), inline(x)?));
103 }
104 format!("{{ {} }}", parts.join(", "))
105 }
106 _ => return None,
107 };
108 (out.len() <= ONE_LINE).then_some(out)
109}
110
111pub fn to_json(value: &Value) -> String {
114 let mut out = String::new();
115 json_into(value, &mut out);
116 out
117}
118
119fn json_into(value: &Value, out: &mut String) {
120 match value {
121 Value::List(items) => {
122 out.push('[');
123 for (i, v) in items.iter().enumerate() {
124 if i > 0 {
125 out.push(',');
126 }
127 json_into(v, out);
128 }
129 out.push(']');
130 }
131 Value::Map(map) => {
132 out.push('{');
133 for (i, (k, v)) in map.iter().enumerate() {
134 if i > 0 {
135 out.push(',');
136 }
137 write_string(k, out);
138 out.push(':');
139 json_into(v, out);
140 }
141 out.push('}');
142 }
143 Value::Str(s) => write_string(s, out),
144 Value::Float(f) if f.is_finite() => out.push_str(&f.to_string()),
145 Value::Float(_) => out.push_str("null"),
146 other => out.push_str(&crate::text::to_string(other)),
147 }
148}
149
150pub fn same(a: &Value, b: &Value) -> bool {
152 match (a, b) {
153 (Value::Int(_) | Value::Float(_), Value::Int(_) | Value::Float(_)) => {
154 a.as_f64() == b.as_f64()
155 }
156 (Value::List(x), Value::List(y)) => {
157 x.len() == y.len() && x.iter().zip(y).all(|(a, b)| same(a, b))
158 }
159 (Value::Map(x), Value::Map(y)) => {
160 x.len() == y.len()
161 && x.iter()
162 .zip(y.iter())
163 .all(|((ka, a), (kb, b))| ka == kb && same(a, b))
164 }
165 _ => a == b,
166 }
167}
168
169pub fn patch(text: &str, next: &Value) -> String {
173 let Ok(doc) = crate::text::parse(text) else {
174 return format!("{}\n", format(next, 0));
175 };
176 let (start, end) = range(&doc.spans);
177 format!(
178 "{}{}{}",
179 &text[..start],
180 rewrite(text, &doc.value, &doc.spans, next),
181 &text[end..]
182 )
183}
184
185fn range(s: &Spans) -> (usize, usize) {
186 (s.span.start as usize, s.span.end as usize)
187}
188
189fn line_indent(text: &str, at: usize) -> &str {
191 let start = text[..at].rfind('\n').map_or(0, |i| i + 1);
192 let line = &text[start..at];
193 &line[..line.len() - line.trim_start_matches([' ', '\t']).len()]
194}
195
196fn level_of(indent: &str) -> usize {
197 indent
198 .chars()
199 .map(|c| if c == '\t' { INDENT.len() } else { 1 })
200 .sum::<usize>()
201 .div_ceil(INDENT.len())
202}
203
204fn skip_blank(text: &str, mut i: usize) -> usize {
206 let b = text.as_bytes();
207 loop {
208 match (b.get(i), b.get(i + 1)) {
209 (Some(b' ' | b'\t' | b'\n' | b'\r'), _) => i += 1,
210 (Some(b'/'), Some(b'/')) => i = text[i..].find('\n').map_or(text.len(), |n| i + n),
211 (Some(b'/'), Some(b'*')) => {
212 i = text[i + 2..]
213 .find("*/")
214 .map_or(text.len(), |n| i + 2 + n + 2)
215 }
216 _ => return i,
217 }
218 }
219}
220
221struct Member {
223 body: String,
224 trail: String,
225}
226
227fn join(out: &[Member], trailing_comma: bool) -> String {
228 let mut s = String::new();
229 for (i, m) in out.iter().enumerate() {
230 s.push_str(&m.body);
231 if i + 1 < out.len() || trailing_comma {
232 s.push(',');
233 }
234 s.push_str(&m.trail);
235 }
236 s
237}
238
239struct Members {
243 segments: Vec<(String, String)>,
244 comma: bool,
245 tail: String,
246}
247
248fn members(text: &str, node: (usize, usize), spans: &[(usize, usize)]) -> Members {
249 let mut segments = Vec::with_capacity(spans.len());
250 let mut at = node.0 + 1;
251 let mut comma = false;
252 for &(start, end) in spans {
253 let lead = text[at..start].to_string();
254 let after = skip_blank(text, end);
255 comma = text.as_bytes().get(after) == Some(&b',');
256 let eol = text[end..].find('\n').map(|n| end + n);
258 let stop = if comma {
259 after
260 } else {
261 eol.map_or(after, |e| after.min(e))
262 };
263 let mut trail = text[end..stop].to_string();
264 at = if comma { after + 1 } else { stop };
265 if comma {
266 let eol = text[at..].find('\n').map_or(text.len(), |n| at + n);
267 let rest = &text[at..eol];
268 let t = rest.trim();
269 let comment = t.starts_with("//")
270 || (t.starts_with("/*") && t.ends_with("*/") && t.matches("*/").count() == 1);
271 if comment && eol < node.1 {
272 trail.push_str(rest);
273 at = eol;
274 }
275 }
276 segments.push((lead, trail));
277 }
278 Members {
279 segments,
280 comma,
281 tail: text[at..node.1 - 1].to_string(),
282 }
283}
284
285fn rewrite(text: &str, old: &Value, spans: &Spans, next: &Value) -> String {
287 let node = range(spans);
288 if same(old, next) {
289 return text[node.0..node.1].to_string();
290 }
291 let level = level_of(line_indent(text, node.0));
292 let fresh = || format(next, level);
293 if !text[node.0..node.1].contains('\n') {
294 return fresh();
295 }
296 match (old, next) {
297 (Value::Map(old), Value::Map(next))
298 if !old.is_empty() && spans.children.len() == old.len() =>
299 {
300 rewrite_map(text, node, old, spans, next)
301 }
302 (Value::List(old), Value::List(next))
303 if !old.is_empty() && spans.children.len() == old.len() =>
304 {
305 rewrite_list(text, node, old, spans, next)
306 }
307 _ => fresh(),
308 }
309}
310
311fn rewrite_map(text: &str, node: (usize, usize), old: &Map, spans: &Spans, next: &Map) -> String {
312 let keys: Vec<&str> = old.keys().collect();
313 let key_start = |c: &Spans| c.key.map_or(c.span.start, |k| k.start) as usize;
314 let ranges: Vec<(usize, usize)> = spans
315 .children
316 .iter()
317 .map(|c| (key_start(c), c.span.end as usize))
318 .collect();
319 let parts = members(text, node, &ranges);
320 let pad = line_indent(text, ranges[0].0);
321 let gone = |i: usize| i < keys.len() && next.get(keys[i]).is_none();
322 let mut out = Vec::with_capacity(next.len());
323 let mut last: Option<usize> = None;
325 for (k, v) in next.iter() {
326 let after = last.map_or(0, |l| l + 1);
327 let found = keys.iter().position(|o| *o == k);
328 let renamed = found.is_none() && gone(after);
329 let Some(i) = found.or(renamed.then_some(after)) else {
330 out.push(Member {
331 body: format!("\n{pad}{}: {}", quoted(k), format(v, level_of(pad))),
332 trail: String::new(),
333 });
334 continue;
335 };
336 last = Some(i);
337 let child = &spans.children[i];
338 let value_start = child.span.start as usize;
339 let key = if renamed {
340 let key_end = child.key.map_or(value_start, |s| s.end as usize);
341 format!("{}{}", quoted(k), &text[key_end..value_start])
342 } else {
343 text[ranges[i].0..value_start].to_string()
344 };
345 let old_value = old.value_at(i).unwrap_or(&Value::Null);
346 let (lead, trail) = &parts.segments[i];
347 out.push(Member {
348 body: format!("{lead}{key}{}", rewrite(text, old_value, child, v)),
349 trail: trail.clone(),
350 });
351 }
352 format!("{{{}{}}}", join(&out, parts.comma), parts.tail)
353}
354
355fn rewrite_list(
356 text: &str,
357 node: (usize, usize),
358 old: &[Value],
359 spans: &Spans,
360 next: &[Value],
361) -> String {
362 let ranges: Vec<(usize, usize)> = spans.children.iter().map(range).collect();
363 let parts = members(text, node, &ranges);
364 let pad = line_indent(text, ranges[0].0);
365 let item = |i: usize, v: &Value| {
366 let (lead, trail) = &parts.segments[i];
367 Member {
368 body: format!("{lead}{}", rewrite(text, &old[i], &spans.children[i], v)),
369 trail: trail.clone(),
370 }
371 };
372 let out: Vec<Member> = if next.len() == old.len() {
373 next.iter().enumerate().map(|(i, v)| item(i, v)).collect()
374 } else {
375 let mut from = 0;
377 next.iter()
378 .map(|v| match (from..old.len()).find(|&k| same(&old[k], v)) {
379 Some(i) => {
380 from = i + 1;
381 item(i, v)
382 }
383 None => Member {
384 body: format!("\n{pad}{}", format(v, level_of(pad))),
385 trail: String::new(),
386 },
387 })
388 .collect()
389 };
390 format!("[{}{}]", join(&out, parts.comma), parts.tail)
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use crate::text::parse_value;
397
398 fn edit(text: &str, change: impl FnOnce(&mut Map)) -> String {
399 let Value::Map(mut v) = parse_value(text).unwrap() else {
400 panic!("a map")
401 };
402 change(&mut v);
403 let next = Value::Map(v);
404 let out = patch(text, &next);
405 assert!(same(&parse_value(&out).unwrap(), &next), "{out}");
406 out
407 }
408
409 const FILE: &str = r#"// A block.
410{
411 "id": "lamp", // its id
412 /* What it is. */
413 "block": {
414 "hardness": 4.0,
415 "light": 15
416 },
417 "states": {
418 "properties": {
419 // On or off.
420 "lit": ["false", "true"],
421 },
422 },
423}
424"#;
425
426 fn map_at<'a>(m: &'a mut Map, key: &str) -> &'a mut Map {
427 match m.get_mut(key) {
428 Some(Value::Map(inner)) => inner,
429 _ => panic!("no map `{key}`"),
430 }
431 }
432
433 #[test]
434 fn what_didnt_change_keeps_its_text() {
435 assert_eq!(edit(FILE, |_| {}), FILE);
436 let out = edit(FILE, |m| {
437 map_at(m, "block").insert("light", Value::Int(7));
438 });
439 assert_eq!(out, FILE.replace("\"light\": 15", "\"light\": 7"));
440 }
441
442 #[test]
443 fn a_new_key_goes_after_the_others() {
444 let out = edit(FILE, |m| {
445 map_at(m, "block").insert("sound", Value::Str("stone".into()));
446 });
447 assert!(
448 out.contains("\"light\": 15,\n \"sound\": \"stone\"\n },"),
449 "{out}"
450 );
451 assert!(out.contains("// its id"));
452 }
453
454 #[test]
455 fn a_removed_key_takes_its_comments() {
456 let out = edit(FILE, |m| {
457 m.remove("block");
458 });
459 assert!(!out.contains("What it is."), "{out}");
460 assert!(
461 out.contains("\"id\": \"lamp\", // its id") && out.contains("// On or off."),
462 "{out}"
463 );
464 assert_eq!(
465 edit("{\n \"a\": 1, // one\n \"b\": 2\n}", |m| {
466 m.remove("b");
467 }),
468 "{\n \"a\": 1 // one\n}"
469 );
470 assert_eq!(
471 edit("{\n \"a\": 1 // one\n}", |m| {
472 m.insert("b", Value::Int(2));
473 }),
474 "{\n \"a\": 1, // one\n \"b\": 2\n}"
475 );
476 }
477
478 #[test]
479 fn a_renamed_key_keeps_its_place_and_comments() {
480 let out = edit(FILE, |m| {
481 let props = map_at(map_at(m, "states"), "properties");
482 let lit = props.remove("lit").unwrap_or(Value::Null);
483 props.insert("on", lit);
484 });
485 assert!(
486 out.contains("// On or off.\n \"on\": [\"false\", \"true\"],"),
487 "{out}"
488 );
489 let text = "{\n a: 1,\n b: 2,\n c: 3\n}";
490 let mut next = Map::new();
491 for (k, v) in [("a", 1), ("x", 2), ("c", 3)] {
492 next.insert(k, Value::Int(v));
493 }
494 assert_eq!(
495 patch(text, &Value::Map(next)),
496 "{\n a: 1,\n \"x\": 2,\n c: 3\n}"
497 );
498 }
499
500 #[test]
501 fn list_items_added_and_removed_keep_the_others() {
502 let text = "{\n \"list\": [\n // first\n { \"a\": 1 },\n { \"b\": 2 }\n ]\n}";
503 let list = |m: &mut Map| match m.get_mut("list") {
504 Some(Value::List(l)) => std::mem::take(l),
505 _ => Vec::new(),
506 };
507 let out = edit(text, |m| {
508 let mut l = list(m);
509 l.push(parse_value("{ c: 3 }").unwrap());
510 m.insert("list", Value::List(l));
511 });
512 assert!(out.contains("// first"), "{out}");
513 let out = edit(text, |m| {
514 let mut l = list(m);
515 l.remove(0);
516 m.insert("list", Value::List(l));
517 });
518 assert!(
519 !out.contains("first") && out.contains("{ \"b\": 2 }"),
520 "{out}"
521 );
522 }
523
524 #[test]
525 fn number_keys_keep_their_order() {
526 let text = "{\n \"rotation\": {\n \"0\": [0, 0, 0], // rest\n \"0.5\": [45, 0, 0],\n \"1\": [0, 0, 0]\n }\n}";
527 let out = edit(text, |m| {
528 map_at(m, "rotation").insert("0.5", parse_value("[90, 0, 0]").unwrap());
529 });
530 assert_eq!(out, text.replace("[45, 0, 0]", "[90, 0, 0]"));
531 }
532
533 #[test]
534 fn json_keeps_order_and_source_strings() {
535 let v =
536 parse_value(r#"{ "1": "@item:minecraft:stick", "0.5": [1.5, null, true] }"#).unwrap();
537 assert_eq!(
538 to_json(&v),
539 r#"{"1":"@item:minecraft:stick","0.5":[1.5,null,true]}"#
540 );
541 }
542
543 #[test]
544 fn short_values_go_on_one_line() {
545 let v =
546 parse_value(r#"{ "uv": [0, 0, 16, 16.5], "faces": { "north": { "texture": "x" } } }"#)
547 .unwrap();
548 assert_eq!(
549 format(&v, 0),
550 "{\n \"uv\": [0, 0, 16, 16.5],\n \"faces\": {\n \"north\": { \"texture\": \"x\" }\n }\n}"
551 );
552 assert_eq!(patch("{ oops", &Value::Int(1)), "1\n");
553 }
554}