#[derive(Debug, Clone)]
pub struct PositionedValue {
pub value: Value,
pub line_index: u64,
pub terminal: bool,
pub framing: bool,
}
pub fn compare_positioned(a: &PositionedValue, b: &PositionedValue, shape: &OutputShape) -> std::cmp::Ordering {
use std::cmp::Ordering;
match shape.sort {
Some(SortKey::PathLine) => {
compare_field(&a.value, &b.value, &["path"])
.then_with(|| compare_field(&a.value, &b.value, LINE_KEYS))
.then_with(|| compare_field(&a.value, &b.value, &["type"]))
.then_with(|| a.line_index.cmp(&b.line_index))
}
Some(SortKey::LinePath) => {
compare_field(&a.value, &b.value, LINE_KEYS)
.then_with(|| compare_field(&a.value, &b.value, &["path"]))
.then_with(|| compare_field(&a.value, &b.value, &["type"]))
.then_with(|| a.line_index.cmp(&b.line_index))
}
Some(SortKey::Key | SortKey::KeyStable) => {
let key = shape.sort_key.as_deref().unwrap_or_default();
compare_field(&a.value, &b.value, &[key]).then_with(|| a.line_index.cmp(&b.line_index))
}
None => match a.line_index.cmp(&b.line_index) {
Ordering::Equal => Ordering::Equal,
other => other,
},
}
}
const LINE_KEYS: &[&str] = &["line_number", "line"];
fn compare_field(a: &Value, b: &Value, keys: &[&str]) -> std::cmp::Ordering {
use std::cmp::Ordering;
for key in keys {
let av = a.as_object().and_then(|o| o.get(*key));
let bv = b.as_object().and_then(|o| o.get(*key));
if av.is_none() && bv.is_none() {
continue;
}
let ordering = compare_optional(av, bv);
if ordering != Ordering::Equal {
return ordering;
}
}
Ordering::Equal
}
fn compare_optional(a: Option<&Value>, b: Option<&Value>) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(None, None) => Ordering::Equal,
(None, Some(_)) => Ordering::Greater,
(Some(_), None) => Ordering::Less,
(Some(x), Some(y)) => compare_value(x, y),
}
}
fn compare_value(a: &Value, b: &Value) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a, b) {
(Value::Number(x), Value::Number(y)) => match (x.as_f64(), y.as_f64()) {
(Some(xf), Some(yf)) => xf.partial_cmp(&yf).unwrap_or(Ordering::Equal),
_ => x.to_string().cmp(&y.to_string()),
},
(Value::String(x), Value::String(y)) => x.cmp(y),
(Value::Bool(x), Value::Bool(y)) => x.cmp(y),
(Value::Bool(_), _) => Ordering::Less,
(_, Value::Bool(_)) => Ordering::Greater,
(Value::String(_), _) => Ordering::Less,
(_, Value::String(_)) => Ordering::Greater,
(Value::Array(x), Value::Array(y)) => x.len().cmp(&y.len()),
(Value::Object(x), Value::Object(y)) => x.len().cmp(&y.len()),
(Value::Null, Value::Null) => Ordering::Equal,
(Value::Null, _) => Ordering::Greater,
(_, Value::Null) => Ordering::Less,
(_, _) => Ordering::Equal,
}
}
fn dedupe_identity(v: &Value, keys: &[String]) -> String {
let mut out = String::new();
for key in keys {
out.push('\u{1}');
match v.as_object().and_then(|o| o.get(key)) {
None => out.push_str("\u{4}missing"),
Some(val) => out.push_str(&val.to_string()),
}
}
out
}