Skip to main content

tickwise_cli/
lib.rs

1//! Library side of the `tickwise` binary.
2//!
3//! The binary is a thin shell over this crate so integration tests can
4//! drive the commands directly.
5
6#![forbid(unsafe_code)]
7#![warn(missing_docs)]
8
9pub mod compare;
10pub mod diff;
11pub mod inspect;
12
13const USAGE: &str = "\
14Tickwise: record, replay, and diff deterministic simulations.
15
16Usage:
17  tickwise inspect <session.rec>    show metadata and statistics for a recording
18  tickwise compare <a.rec> <b.rec>  find the first divergent tick
19  tickwise diff <a.dump> <b.dump>   field-level structural diff of two state dumps
20
21Diff flags:
22  --strict             every bit-level float difference counts as exact
23  --epsilon-f32 <x>    sub-epsilon threshold for f32, default 1e-5
24  --epsilon-f64 <x>    sub-epsilon threshold for f64, default 1e-12
25  --all                show every difference instead of the first 100 per tick
26  --no-color           plain output, also honored via the NO_COLOR variable
27
28Exit codes for compare and diff: 0 identical, 1 differences found, 2 trouble.
29
30Options:
31  -h, --help       show this help
32  -V, --version    show the version
33";
34
35fn color_allowed() -> bool {
36    use std::io::IsTerminal;
37    std::env::var_os("NO_COLOR").is_none() && std::io::stdout().is_terminal()
38}
39
40/// Runs the CLI with the given arguments, excluding the program name,
41/// and returns the process exit code.
42pub fn run(args: &[String]) -> u8 {
43    let Some((command, rest)) = args.split_first() else {
44        eprint!("{USAGE}");
45        return 2;
46    };
47
48    match command.as_str() {
49        "inspect" => match rest {
50            [path] => match inspect::render(path) {
51                Ok(report) => {
52                    print!("{}", report.text);
53                    u8::from(report.corrupt)
54                }
55                Err(err) => {
56                    eprintln!("tickwise inspect: {err}");
57                    1
58                }
59            },
60            _ => {
61                eprintln!("usage: tickwise inspect <session.rec>");
62                2
63            }
64        },
65        "compare" => match rest {
66            [a, b] => match compare::render(a, b) {
67                Ok(output) => {
68                    print!("{}", output.text);
69                    u8::from(output.diverged)
70                }
71                Err(err) => {
72                    eprintln!("tickwise compare: {err}");
73                    2
74                }
75            },
76            _ => {
77                eprintln!("usage: tickwise compare <a.rec> <b.rec>");
78                2
79            }
80        },
81        "diff" => match diff::parse_args(rest) {
82            Ok((a, b, mut options)) => {
83                options.color = options.color && color_allowed();
84                match diff::render(&a, &b, &options) {
85                    Ok(output) => {
86                        print!("{}", output.text);
87                        u8::from(output.differs)
88                    }
89                    Err(err) => {
90                        eprintln!("tickwise diff: {err}");
91                        2
92                    }
93                }
94            }
95            Err(message) => {
96                eprintln!("{message}");
97                2
98            }
99        },
100        "help" | "-h" | "--help" => {
101            print!("{USAGE}");
102            0
103        }
104        "-V" | "--version" => {
105            println!("tickwise {}", env!("CARGO_PKG_VERSION"));
106            0
107        }
108        other => {
109            eprintln!("tickwise: unknown command {other}");
110            eprint!("{USAGE}");
111            2
112        }
113    }
114}