Skip to main content

xil_core/
banner.rs

1//! Start/finish frame around a command run. Port of `sfx_common.run_banner`.
2//!
3//! The decorative bars are console-only; the `BEGIN`/`END` records are
4//! file-only and give log parsers a per-invocation boundary.
5
6use std::env;
7use std::process;
8use std::time::Instant;
9
10use chrono::Local;
11
12use crate::log::{self, Level, Sink};
13
14const BAR: &str = "======================================================================";
15
16/// An open banner. Dropping it writes the finish frame, so an early `?`
17/// return still closes the run the way Python's `finally` does.
18pub struct Banner {
19    name: String,
20    start: Instant,
21}
22
23/// Open the frame. `name` is what Python shows (`os.path.basename(argv[0])`
24/// or an explicit script name); `argv_line` is `" ".join(sys.argv)`.
25pub fn begin(name: &str, argv_line: &str) -> Banner {
26    let now = Local::now();
27    let cwd = env::current_dir()
28        .map(|p| p.display().to_string())
29        .unwrap_or_default();
30    log::log(Level::Run, Sink::ConsoleOnly, "");
31    log::log(Level::Run, Sink::ConsoleOnly, BAR);
32    log::log(
33        Level::Run,
34        Sink::ConsoleOnly,
35        &format!("  {name}  |  started {}", now.format("%Y-%m-%d %H:%M:%S")),
36    );
37    log::log(Level::Run, Sink::ConsoleOnly, BAR);
38    log::log(Level::Run, Sink::ConsoleOnly, "");
39    log::log(
40        Level::Run,
41        Sink::FileOnly,
42        &format!(
43            "BEGIN argv=\"{argv_line}\" pid={} ver={} cwd={cwd}",
44            process::id(),
45            env!("CARGO_PKG_VERSION")
46        ),
47    );
48    Banner {
49        name: name.to_string(),
50        start: Instant::now(),
51    }
52}
53
54impl Drop for Banner {
55    fn drop(&mut self) {
56        let end = Local::now();
57        let elapsed = self.start.elapsed().as_secs_f64();
58        log::log(
59            Level::Run,
60            Sink::FileOnly,
61            &format!("END elapsed={elapsed:.1}s"),
62        );
63        log::log(Level::Run, Sink::ConsoleOnly, "");
64        log::log(Level::Run, Sink::ConsoleOnly, BAR);
65        log::log(
66            Level::Run,
67            Sink::ConsoleOnly,
68            &format!(
69                "  {}  |  finished {}  ({elapsed:.1}s)",
70                self.name,
71                end.format("%Y-%m-%d %H:%M:%S")
72            ),
73        );
74        log::log(Level::Run, Sink::ConsoleOnly, BAR);
75        log::log(Level::Run, Sink::ConsoleOnly, "");
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn bar_is_seventy_equals() {
85        assert_eq!(BAR.len(), 70);
86        assert!(BAR.chars().all(|c| c == '='));
87    }
88}