Skip to main content

concinnity_dev/command/
version.rs

1// `cn version`, and the line `cn --version` prints. Both render through
2// `version_line` so the subcommand and the flag can never drift apart.
3//
4// The commit and the date come from the build stamp the build script bakes in.
5
6use std::sync::OnceLock;
7
8/// The engine version this build was compiled from.
9///
10/// Every crate in the workspace shares it, so it names the toolchain as a
11/// whole rather than any one library.
12pub const VERSION: &str = env!("CARGO_PKG_VERSION");
13
14// The commit the source was built from, empty when the tree was not a
15// checkout, and the date: the commit's when there is one, the build's when
16// there is not.
17const COMMIT: &str = env!("CONCINNITY_COMMIT");
18const STAMP_DATE: &str = env!("CONCINNITY_STAMP_DATE");
19
20// The name the CLI is invoked under, and what clap prefixes its own
21// `--version` output with.
22const NAME: &str = "concinnity";
23
24/// Print the version.
25pub fn version() -> std::io::Result<()> {
26    println!("{}", version_line());
27    Ok(())
28}
29
30/// The version and its build stamp, as `--version` reports them after the
31/// command name: `0.19.0 (c980f4866 2026-06-30)`.
32///
33/// Borrowed for the process because that is what clap's version field takes.
34pub fn version_details() -> &'static str {
35    static DETAILS: OnceLock<String> = OnceLock::new();
36    DETAILS.get_or_init(|| details(VERSION, COMMIT, STAMP_DATE))
37}
38
39/// The full one-line version banner, command name included.
40pub fn version_line() -> String {
41    format!("{NAME} {}", version_details())
42}
43
44// A build off a checkout names its commit, so the date beside it is that
45// commit's. Otherwise the only date there is the day of the build, and it is
46// labelled rather than left to read as a commit date.
47fn details(version: &str, commit: &str, date: &str) -> String {
48    match (commit.is_empty(), date.is_empty()) {
49        (false, false) => format!("{version} ({commit} {date})"),
50        (false, true) => format!("{version} ({commit})"),
51        (true, false) => format!("{version} ({date})"),
52        (true, true) => version.to_string(),
53    }
54}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59
60    #[test]
61    fn a_commit_build_names_the_commit_and_its_date() {
62        assert_eq!(
63            details("0.19.0", "c980f4866", "2026-06-30"),
64            "0.19.0 (c980f4866 2026-06-30)"
65        );
66    }
67
68    #[test]
69    fn a_build_off_no_checkout_labels_the_date_as_the_build_date() {
70        assert_eq!(details("0.19.0", "", "2026-08-30"), "0.19.0 (2026-08-30)");
71    }
72
73    #[test]
74    fn a_stamp_missing_a_part_drops_only_that_part() {
75        assert_eq!(details("0.19.0", "c980f4866", ""), "0.19.0 (c980f4866)");
76        assert_eq!(details("0.19.0", "", ""), "0.19.0");
77    }
78
79    // clap renders `--version` as "{name} {details}", so the banner has to be
80    // that same pair; nothing else keeps the two spellings identical.
81    #[test]
82    fn the_line_is_the_name_then_the_details() {
83        assert_eq!(version_line(), format!("concinnity {}", version_details()));
84        assert!(version_details().starts_with(VERSION));
85    }
86
87    #[test]
88    fn the_version_is_a_dotted_release() {
89        let parts: Vec<&str> = VERSION.split('.').collect();
90        assert_eq!(parts.len(), 3, "{VERSION} is not major.minor.patch");
91        assert!(
92            parts[0].chars().all(|c| c.is_ascii_digit()),
93            "{VERSION} has a non-numeric major"
94        );
95    }
96
97    // The stamp is baked by the build script; an empty date means it stopped
98    // running, which would silently strip the build info from every release.
99    #[test]
100    fn the_build_stamp_carries_a_date() {
101        assert!(!STAMP_DATE.is_empty(), "the build script stamped no date");
102    }
103}