aboutsummaryrefslogtreecommitdiff
path: root/mingling_ci/src/res/collect_logs.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_ci/src/res/collect_logs.rs')
-rw-r--r--mingling_ci/src/res/collect_logs.rs203
1 files changed, 0 insertions, 203 deletions
diff --git a/mingling_ci/src/res/collect_logs.rs b/mingling_ci/src/res/collect_logs.rs
deleted file mode 100644
index 6017168..0000000
--- a/mingling_ci/src/res/collect_logs.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-//! IO side of the report command: reads the collect directory once and keeps
-//! the parsed data in a resource, so chains only do computation.
-
-use std::collections::BTreeMap;
-
-use mingling::{Program, macros::program_setup};
-
-use crate::ThisProgram;
-use crate::reporter::COLLECT_DIR;
-
-/// Git commit date and short hash for the report.
-#[derive(Default, Clone, Debug)]
-pub struct GitInfo {
- pub date: String,
- pub commit_hash: String,
-}
-
-/// Parsed contents of the collect directory.
-#[derive(Default, Clone)]
-pub struct ResCollectLogs {
- /// `(task, item) -> os -> ok`
- pub statuses: BTreeMap<(String, String), BTreeMap<String, bool>>,
- /// `(task, item) -> location`
- pub locations: BTreeMap<(String, String), String>,
- /// `(task, os, item) -> stripped error output (location line removed)`
- pub err_outputs: BTreeMap<(String, String, String), String>,
- pub git: GitInfo,
-}
-
-impl ResCollectLogs {
- /// Reads the flat `collect/` directory — aggregate `{task}.{os}.ok` files
- /// (`item` or `item = location` per line) and per-item
- /// `{task}.{os}.{item}.err` files (first line is the location) — plus the
- /// git info.
- #[must_use]
- pub fn read() -> Self {
- let mut logs = Self::default();
-
- if let Ok(entries) = std::fs::read_dir(COLLECT_DIR) {
- for entry in entries.flatten() {
- let file_name = entry.file_name().to_string_lossy().into_owned();
- if let Some((task, os)) = parse_ok_name(&file_name) {
- // Aggregate success file: `item` or `item = location` per line.
- if let Ok(content) = std::fs::read_to_string(entry.path()) {
- for line in content.lines().filter(|l| !l.is_empty()) {
- let (item, location) = line
- .split_once('=')
- .map_or((line, ""), |(name, loc)| (name.trim(), loc.trim()));
- logs.statuses
- .entry((task.clone(), item.to_string()))
- .or_default()
- .insert(os.clone(), true);
- logs.locations
- .insert((task.clone(), item.to_string()), location.to_string());
- }
- }
- } else if let Some((task, os, item)) = parse_err_name(&file_name) {
- let content = std::fs::read_to_string(entry.path()).unwrap_or_default();
- let mut lines = content.splitn(2, '\n');
- let location = lines.next().unwrap_or_default().to_string();
- let output = lines.next().unwrap_or_default().to_string();
- logs.statuses
- .entry((task.clone(), item.clone()))
- .or_default()
- .insert(os.clone(), false);
- logs.locations
- .insert((task.clone(), item.clone()), location);
- logs.err_outputs
- .insert((task, os, item), strip_ansi(&output));
- }
- }
- }
-
- logs.git = git_info();
- logs
- }
-}
-
-/// Parses a `{task}.{os}.ok` file name.
-fn parse_ok_name(file_name: &str) -> Option<(String, String)> {
- let name = file_name.strip_suffix(".ok")?;
- let mut parts = name.rsplitn(2, '.');
- let os = parts.next()?.to_string();
- let task = parts.next()?.to_string();
- Some((task, os))
-}
-
-/// Parses a `{task}.{os}.{package}.err` file name.
-///
-/// Split from the right: package names cannot contain dots (cargo forbids
-/// them), while task names may.
-fn parse_err_name(file_name: &str) -> Option<(String, String, String)> {
- let name = file_name.strip_suffix(".err")?;
- let mut parts = name.rsplitn(3, '.');
- let package = parts.next()?.to_string();
- let os = parts.next()?.to_string();
- let task = parts.next()?.to_string();
- Some((task, os, package))
-}
-
-#[program_setup]
-pub fn report_setup(p: &mut Program<ThisProgram>) {
- p.with_resource(ResCollectLogs::read());
-}
-
-/// Strips ANSI escape sequences from `input`.
-///
-/// Handles CSI (`ESC [ ...`), OSC (`ESC ] ...` terminated by BEL or `ESC \`)
-/// and other single-character escapes, while preserving UTF-8 text. Literal
-/// `^[` (caret-bracket, produced by some terminal captures) is normalized to
-/// `ESC` first.
-fn strip_ansi(input: &str) -> String {
- // Normalize literal `^[` (0x5E 0x5B) to a real ESC byte.
- let normalized = input.replace("^[", "\u{1b}");
- let mut out = String::with_capacity(normalized.len());
- let mut rest = normalized.as_str();
- while let Some(idx) = rest.find('\u{1b}') {
- out.push_str(&rest[..idx]);
- rest = &rest[idx..];
- rest = &rest[ansi_len(rest)..];
- }
- out.push_str(rest);
- out
-}
-
-/// Byte length of the ANSI escape sequence starting at `s[0]` (`s[0]` is `ESC`).
-fn ansi_len(s: &str) -> usize {
- let b = s.as_bytes();
- match b.get(1) {
- Some(b'[') => {
- // CSI: `ESC [` params/intermediates (0x20-0x3F) then a final byte (0x40-0x7E).
- let mut i = 2;
- while i < b.len() {
- let byte = b[i];
- i += 1;
- if (0x40..=0x7E).contains(&byte) {
- break;
- }
- if !(0x20..=0x3F).contains(&byte) {
- break;
- }
- }
- i
- }
- Some(b']') => {
- // OSC: `ESC ]` ... terminated by BEL (0x07) or `ESC \`.
- let mut i = 2;
- while i < b.len() {
- let byte = b[i];
- i += 1;
- if byte == 0x07 {
- break;
- }
- if byte == 0x1b {
- if b.get(i) == Some(&b'\\') {
- i += 1;
- }
- break;
- }
- }
- i
- }
- Some(_) => 2.min(b.len()),
- None => 1,
- }
-}
-
-/// Commit date (`YYYY-MM-DD`) and short commit hash; empty on failure.
-fn git_info() -> GitInfo {
- let run = |args: &[&str]| {
- std::process::Command::new("git")
- .args(args)
- .output()
- .ok()
- .filter(|o| o.status.success())
- .map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string())
- .unwrap_or_default()
- };
- GitInfo {
- date: run(&["log", "-1", "--format=%cs"]),
- commit_hash: run(&["rev-parse", "--short", "HEAD"]),
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::strip_ansi;
-
- #[test]
- fn strips_csi_and_osc_and_literal_caret() {
- let input =
- "\u{1b}[1m\u{1b}[92mok\u{1b}[0m \u{1b}]8;;https://x\u{1b}\\done\u{1b}]8;;\u{1b}\\\n";
- assert_eq!(strip_ansi(input), "ok done\n");
-
- // Literal `^[` (caret-bracket) captured by some terminals.
- assert_eq!(strip_ansi("^[[31mred^[[0m"), "red");
- }
-
- #[test]
- fn preserves_utf8() {
- assert_eq!(strip_ansi("你好\u{1b}[1m世界!\u{1b}[0m"), "你好世界!");
- }
-}