use super::Adapter;
use crate::lane::pack_lanes;
use crate::span::{Category, Span, Status};
use anyhow::{anyhow, Context};
use serde::Deserialize;
use std::collections::BTreeMap;
pub struct Cargo;
#[derive(Debug, Deserialize)]
struct CargoUnit {
name: String,
version: String,
mode: String,
target: String,
start: f64,
duration: f64,
}
impl Adapter for Cargo {
fn name(&self) -> &'static str {
"cargo"
}
fn parse(&self, input: &[u8]) -> anyhow::Result<Vec<Span>> {
let html = std::str::from_utf8(input)?;
let marker = "const UNIT_DATA = ";
let after_marker = html
.find(marker)
.map(|i| &html[i + marker.len()..])
.ok_or_else(|| {
anyhow!("no UNIT_DATA found — is this a cargo --timings HTML report?")
})?;
let json_slice = extract_balanced_array(after_marker)
.ok_or_else(|| anyhow!("UNIT_DATA array in cargo timing report is not well-formed"))?;
let units: Vec<CargoUnit> =
serde_json::from_str(json_slice).context("parsing cargo timing UNIT_DATA as JSON")?;
let mut spans: Vec<Span> = units
.into_iter()
.map(|u| {
let suffix = u.target.trim();
let name = if suffix.is_empty() {
format!("{} v{}", u.name, u.version)
} else {
format!("{} v{} {}", u.name, u.version, suffix)
};
let mut args = BTreeMap::new();
args.insert("version".to_string(), u.version);
Span {
name,
category: classify(&u.mode),
status: Status::Success, track: "cargo".to_string(),
lane: 0, start_us: (u.start * 1_000_000.0).round() as i64,
dur_us: (u.duration * 1_000_000.0).round() as i64,
args,
}
})
.collect();
pack_lanes(&mut spans);
Ok(spans)
}
}
fn classify(mode: &str) -> Category {
match mode {
"todo" => Category::Compile,
other => Category::Other(other.to_string()),
}
}
fn extract_balanced_array(s: &str) -> Option<&str> {
let start = s.find('[')?;
let mut depth = 0i32;
let mut in_string = false;
let mut escaped = false;
for (i, ch) in s.char_indices() {
if i < start {
continue;
}
if in_string {
if escaped {
escaped = false;
} else if ch == '\\' {
escaped = true;
} else if ch == '"' {
in_string = false;
}
continue;
}
match ch {
'"' => in_string = true,
'[' => depth += 1,
']' => {
depth -= 1;
if depth == 0 {
return Some(&s[start..=i]);
}
}
_ => {}
}
}
None
}