use std::fs::File;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use typst::diag::{bail, StrResult};
use typst::syntax::Span;
use typst::World;
use crate::args::{CliArguments, Command};
use crate::world::SystemWorld;
pub struct Timer {
path: Option<PathBuf>,
index: usize,
}
impl Timer {
pub fn new(args: &CliArguments) -> Timer {
let record = match &args.command {
Command::Compile(command) => command.args.timings.clone(),
Command::Watch(command) => command.args.timings.clone(),
_ => None,
};
if record.is_some() {
typst_timing::enable();
}
let path =
record.map(|path| path.unwrap_or_else(|| PathBuf::from("record-{n}.json")));
Timer { path, index: 0 }
}
pub fn record<T>(
&mut self,
world: &mut SystemWorld,
f: impl FnOnce(&mut SystemWorld) -> T,
) -> StrResult<T> {
let Some(path) = &self.path else {
return Ok(f(world));
};
typst_timing::clear();
let string = path.to_str().unwrap_or_default();
let numbered = string.contains("{n}");
if !numbered && self.index > 0 {
bail!("cannot export multiple recordings without `{{n}}` in path");
}
let storage;
let path = if numbered {
storage = string.replace("{n}", &self.index.to_string());
Path::new(&storage)
} else {
path.as_path()
};
let output = f(world);
self.index += 1;
let file =
File::create(path).map_err(|e| format!("failed to create file: {e}"))?;
let writer = BufWriter::with_capacity(1 << 20, file);
typst_timing::export_json(writer, |span| {
resolve_span(world, Span::from_raw(span))
.unwrap_or_else(|| ("unknown".to_string(), 0))
})?;
Ok(output)
}
}
fn resolve_span(world: &SystemWorld, span: Span) -> Option<(String, u32)> {
let id = span.id()?;
let source = world.source(id).ok()?;
let range = source.range(span)?;
let line = source.byte_to_line(range.start)?;
Some((format!("{id:?}"), line as u32 + 1))
}