code-moniker 0.4.0

Standalone CLI / linter for the code-moniker symbol graph: file and directory probes, project-wide architecture rules.
Documentation
use std::fs::{File, OpenOptions};
use std::io::Write;
use std::path::PathBuf;
use std::sync::{Mutex, OnceLock};
use std::time::{Duration, SystemTime, UNIX_EPOCH};

static TRACE: OnceLock<Option<Mutex<File>>> = OnceLock::new();

pub(crate) fn record(event: &str, duration: Duration, detail: impl AsRef<str>) {
	let Some(file) = trace_file() else {
		return;
	};
	let timestamp_ms = SystemTime::now()
		.duration_since(UNIX_EPOCH)
		.map(|duration| duration.as_millis())
		.unwrap_or_default();
	let duration_ms = duration.as_millis();
	let detail = sanitize(detail.as_ref());
	if let Ok(mut file) = file.lock() {
		let _ = writeln!(file, "{timestamp_ms}\t{event}\t{duration_ms}\t{detail}");
	}
}

fn trace_file() -> Option<&'static Mutex<File>> {
	TRACE
		.get_or_init(|| {
			let target = std::env::var_os("CODE_MONIKER_UI_TIMINGS")?;
			let path = trace_path(target);
			let mut file = OpenOptions::new()
				.create(true)
				.append(true)
				.open(path)
				.ok()?;
			let _ = writeln!(file, "timestamp_ms\tevent\tduration_ms\tdetail");
			Some(Mutex::new(file))
		})
		.as_ref()
}

fn trace_path(target: std::ffi::OsString) -> PathBuf {
	if matches!(target.to_str(), Some("1" | "true" | "TRUE" | "yes" | "YES")) {
		std::env::temp_dir().join("code-moniker-ui-timings.tsv")
	} else {
		PathBuf::from(target)
	}
}

fn sanitize(value: &str) -> String {
	value.replace(['\t', '\n', '\r'], " ")
}