use std::collections::HashSet;
use fig::Value;
pub use fig_schema::Seg;
pub fn to_fig(path: &[Seg]) -> Vec<fig::Segment<'_>> {
path.iter()
.map(|s| match s {
Seg::Key(k) => fig::Segment::Key(k.as_str()),
Seg::Index(i) => fig::Segment::Index(*i),
})
.collect()
}
pub fn value_at<'v>(root: &'v Value, path: &[Seg]) -> Option<&'v Value> {
let mut cur = root;
for seg in path {
cur = match (seg, cur) {
(Seg::Key(k), Value::Map(entries)) => {
&entries
.iter()
.find(|(mk, _)| matches!(mk, Value::Str(s) if s == k))?
.1
}
(Seg::Index(i), Value::Seq(items)) => items.get(*i)?,
_ => return None,
};
}
Some(cur)
}
pub fn seq_len(root: &Value, path: &[Seg]) -> Option<usize> {
match value_at(root, path)? {
Value::Seq(items) => Some(items.len()),
_ => None,
}
}
pub fn map_keys(root: &Value, path: &[Seg]) -> Option<Vec<String>> {
match value_at(root, path)? {
Value::Map(entries) => Some(
entries
.iter()
.filter_map(|(k, _)| match k {
Value::Str(s) => Some(s.clone()),
_ => None,
})
.collect(),
),
_ => None,
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VKind {
Null,
Bool,
Int,
Float,
Str,
Ext,
Map,
Seq,
}
impl VKind {
pub(crate) fn of(v: &Value) -> Self {
match v {
Value::Null => VKind::Null,
Value::Bool(_) => VKind::Bool,
Value::Int(_) | Value::Uint(_) => VKind::Int,
Value::Float(_) => VKind::Float,
Value::Str(_) => VKind::Str,
Value::Extended { .. } => VKind::Ext,
Value::Map(_) => VKind::Map,
Value::Seq(_) => VKind::Seq,
}
}
}
#[derive(Clone, Debug)]
pub struct Row {
pub depth: usize,
pub label: String,
pub vkind: VKind,
pub preview: String,
pub expanded: bool,
pub path: Vec<Seg>,
}
impl Row {
pub fn is_container(&self) -> bool {
matches!(self.vkind, VKind::Map | VKind::Seq)
}
pub fn is_scalar(&self) -> bool {
!self.is_container()
}
pub fn can_rename(&self) -> bool {
matches!(self.path.last(), Some(Seg::Key(_)))
}
}
pub(crate) fn key_to_string(k: &Value) -> String {
match k {
Value::Str(s) => s.clone(),
Value::Int(i) => i.to_string(),
Value::Uint(u) => u.to_string(),
Value::Bool(b) => b.to_string(),
other => format!("{other:?}"),
}
}
pub fn preview(v: &Value) -> String {
match v {
Value::Null => "null".to_string(),
Value::Bool(b) => b.to_string(),
Value::Int(i) => i.to_string(),
Value::Uint(u) => u.to_string(),
Value::Float(f) => f.to_string(),
Value::Str(s) => first_line(s),
Value::Extended { text, .. } => first_line(text),
Value::Map(entries) => format!("{{{}}}", entries.len()),
Value::Seq(items) => format!("[{}]", items.len()),
}
}
fn first_line(s: &str) -> String {
match s.split_once('\n') {
Some((head, _)) => format!("{} …", head.trim_end()),
None => s.to_string(),
}
}
pub fn edit_seed(v: &Value) -> String {
match v {
Value::Str(s) => s.clone(),
other => preview(other),
}
}
pub fn build_rows(
root: &Value,
collapsed: &HashSet<Vec<Seg>>,
hidden_top_level: &HashSet<String>,
) -> Vec<Row> {
let mut rows = Vec::new();
match root {
Value::Map(entries) => {
for (k, v) in entries {
let key = key_to_string(k);
if hidden_top_level.contains(&key) {
continue;
}
push_node(
&key,
v,
vec![Seg::Key(key.clone())],
0,
collapsed,
&mut rows,
);
}
}
Value::Seq(items) => {
for (i, v) in items.iter().enumerate() {
push_node(
&format!("[{i}]"),
v,
vec![Seg::Index(i)],
0,
collapsed,
&mut rows,
);
}
}
other => push_node("", other, Vec::new(), 0, collapsed, &mut rows),
}
rows
}
fn push_node(
label: &str,
v: &Value,
path: Vec<Seg>,
depth: usize,
collapsed: &HashSet<Vec<Seg>>,
rows: &mut Vec<Row>,
) {
let vkind = VKind::of(v);
let is_container = matches!(vkind, VKind::Map | VKind::Seq);
let expanded = is_container && !collapsed.contains(&path);
rows.push(Row {
depth,
label: label.to_string(),
vkind,
preview: preview(v),
expanded,
path: path.clone(),
});
if expanded {
match v {
Value::Map(entries) => {
for (k, child) in entries {
let key = key_to_string(k);
let mut p = path.clone();
p.push(Seg::Key(key.clone()));
push_node(&key, child, p, depth + 1, collapsed, rows);
}
}
Value::Seq(items) => {
for (i, child) in items.iter().enumerate() {
let mut p = path.clone();
p.push(Seg::Index(i));
push_node(&format!("[{i}]"), child, p, depth + 1, collapsed, rows);
}
}
_ => {}
}
}
}
pub fn parse_scalar(s: &str) -> Value {
let t = s.trim();
match t {
"true" => return Value::Bool(true),
"false" => return Value::Bool(false),
"null" => return Value::Null,
_ => {}
}
if let Ok(i) = t.parse::<i64>() {
return Value::Int(i);
}
if let Ok(u) = t.parse::<u64>() {
return Value::Uint(u);
}
if let Ok(f) = t.parse::<f64>() {
return Value::Float(f);
}
Value::Str(s.to_string())
}