rivet_logger/processors/
mercurial.rs1use std::collections::BTreeMap;
2use std::process::Command;
3use std::sync::OnceLock;
4
5use crate::logger::{BoxError, Context, Level, LogRecord, LogValue, Processor};
6
7static HG_INFO_CACHE: OnceLock<Context> = OnceLock::new();
8
9pub struct Mercurial {
10 min_level: Level,
11}
12
13impl Mercurial {
14 pub fn new(min_level: Level) -> Self {
15 Self { min_level }
16 }
17}
18
19impl Default for Mercurial {
20 fn default() -> Self {
21 Self::new(Level::Debug)
22 }
23}
24
25impl Processor for Mercurial {
26 fn process(&self, mut record: LogRecord) -> Result<LogRecord, BoxError> {
27 if record.level < self.min_level {
28 return Ok(record);
29 }
30
31 let info = HG_INFO_CACHE.get_or_init(mercurial_info).clone();
32 record.extra.insert("hg".to_string(), LogValue::Map(info));
33
34 Ok(record)
35 }
36}
37
38fn mercurial_info() -> Context {
39 let output = Command::new("hg").args(["id", "-nb"]).output();
40 let Ok(output) = output else {
41 return BTreeMap::new();
42 };
43 if !output.status.success() {
44 return BTreeMap::new();
45 }
46
47 let stdout = String::from_utf8_lossy(&output.stdout);
48 parse_hg_output(stdout.trim()).unwrap_or_default()
49}
50
51fn parse_hg_output(output: &str) -> Option<Context> {
52 let parts: Vec<&str> = output.split_whitespace().collect();
53 match parts.as_slice() {
54 [revision, branch] => {
55 let mut info = BTreeMap::new();
56 info.insert(
57 "branch".to_string(),
58 LogValue::String((*branch).to_string()),
59 );
60 info.insert(
61 "revision".to_string(),
62 LogValue::String((*revision).to_string()),
63 );
64 Some(info)
65 }
66 [_, branch, revision, ..] => {
67 let mut info = BTreeMap::new();
68 info.insert(
69 "branch".to_string(),
70 LogValue::String((*branch).to_string()),
71 );
72 info.insert(
73 "revision".to_string(),
74 LogValue::String((*revision).to_string()),
75 );
76 Some(info)
77 }
78 _ => None,
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use crate::logger::LogValue;
85
86 use super::*;
87
88 #[test]
89 fn parses_two_token_hg_output() {
90 let parsed = parse_hg_output("abc123 default").expect("output should parse");
91 assert!(matches!(
92 parsed.get("branch"),
93 Some(LogValue::String(value)) if value == "default"
94 ));
95 assert!(matches!(
96 parsed.get("revision"),
97 Some(LogValue::String(value)) if value == "abc123"
98 ));
99 }
100
101 #[test]
102 fn parses_three_token_hg_output() {
103 let parsed = parse_hg_output("123 default 456").expect("output should parse");
104 assert!(matches!(
105 parsed.get("revision"),
106 Some(LogValue::String(value)) if value == "456"
107 ));
108 }
109}