use serde::Deserialize;
use crate::contract::{Match, MatchKind, RankKind, Ranked, Submatch};
pub(crate) fn parse_rank(stream: &str) -> Vec<Ranked> {
stream.lines().filter_map(rank_row).collect()
}
fn rank_row(line: &str) -> Option<Ranked> {
let (kind, open, close) = ["def", "use", "gen"].iter().find_map(|tag| {
let bracket = format!("[{tag}]");
let i = line.find(&bracket)?;
Some((RankKind::parse(tag)?, i, i + bracket.len()))
})?;
let left = line[..open].trim_end();
let colon = left.rfind(':')?;
let line_number: u64 = left[colon + 1..].parse().ok()?;
let path = strip_rank_index(&left[..colon]);
if path.is_empty() {
return None;
}
let rest = line[close..].trim_start().strip_prefix('\u{00d7}')?;
let digits = rest.find(|c: char| !c.is_ascii_digit())?;
let count: u64 = rest[..digits].parse().ok()?;
Some(Ranked {
path: path.to_owned(),
line_number,
kind,
count,
snippet: rest[digits..].trim_start().to_owned(),
})
}
fn strip_rank_index(head: &str) -> &str {
let h = head.trim_start();
let digits = h.find(|c: char| !c.is_ascii_digit()).unwrap_or(h.len());
if digits == 0 {
return h;
}
h[digits..].strip_prefix('.').map_or(h, str::trim_start)
}
#[derive(Deserialize)]
struct Text {
#[serde(default)]
text: String,
}
#[derive(Deserialize)]
struct WireSubmatch {
#[serde(rename = "match")]
matched: Text,
start: usize,
end: usize,
}
#[derive(Deserialize)]
struct WireData {
path: Text,
#[serde(default)]
line_number: u64,
lines: Text,
#[serde(default)]
submatches: Vec<WireSubmatch>,
}
#[derive(Deserialize)]
struct WireRecord {
#[serde(rename = "type")]
kind: String,
data: WireData,
}
pub(crate) fn parse_json(stream: &str) -> Vec<Match> {
let mut out = Vec::new();
for line in stream.lines().filter(|l| !l.is_empty()) {
let Ok(rec) = serde_json::from_str::<WireRecord>(line) else {
continue;
};
let kind = match rec.kind.as_str() {
"match" => MatchKind::Match,
"context" => MatchKind::Context,
_ => continue,
};
out.push(Match {
path: rec.data.path.text,
line_number: rec.data.line_number,
text: rec.data.lines.text.trim_end_matches('\n').to_owned(),
kind,
submatches: rec
.data
.submatches
.into_iter()
.map(|s| Submatch {
text: s.matched.text,
start: s.start,
end: s.end,
})
.collect(),
});
}
out
}