1use std::fmt;
2
3pub mod built_info {
5 include!(concat!(env!("OUT_DIR"), "/built.rs"));
6}
7
8pub struct VersionInfo {
9 pub version: &'static str,
10 pub commit_hash: Option<&'static str>,
11 pub commit_hash_short: Option<&'static str>,
12 pub git_version: Option<&'static str>,
13 pub is_dirty: bool,
14 pub target: &'static str,
15 pub profile: &'static str,
16 pub rustc_version: &'static str,
17}
18
19impl Default for VersionInfo {
20 fn default() -> Self {
21 Self::new()
22 }
23}
24
25impl VersionInfo {
26 pub fn new() -> Self {
27 Self {
28 version: built_info::PKG_VERSION,
29 commit_hash: built_info::GIT_COMMIT_HASH,
30 commit_hash_short: built_info::GIT_COMMIT_HASH_SHORT,
31 git_version: built_info::GIT_VERSION,
32 is_dirty: built_info::GIT_DIRTY.unwrap_or(false),
33 target: built_info::TARGET,
34 profile: built_info::PROFILE,
35 rustc_version: built_info::RUSTC_VERSION,
36 }
37 }
38
39 pub fn is_development_build(&self) -> bool {
40 self.is_dirty
45 || self.profile == "debug"
46 || self.git_version.is_some_and(|git_ver| {
47 git_ver.contains('-') && !git_ver.ends_with("-0-g")
49 })
50 }
51
52 pub fn short_version(&self) -> String {
53 if self.is_development_build() {
54 if let Some(git_version) = self.git_version {
55 if git_version.contains('-') {
57 let parts: Vec<&str> = git_version.split('-').collect();
58 if parts.len() >= 3 {
59 let commits_after = parts[parts.len() - 2];
60 let commit_hash = parts[parts.len() - 1]
61 .strip_prefix('g')
62 .unwrap_or(parts[parts.len() - 1]);
63 if self.is_dirty {
64 format!(
65 "v{}-dev+{}.{}.dirty",
66 self.version, commits_after, commit_hash
67 )
68 } else {
69 format!("v{}-dev+{}.{}", self.version, commits_after, commit_hash)
70 }
71 } else if self.is_dirty {
72 format!("v{}-dev+dirty", self.version)
73 } else {
74 format!("v{}-dev", self.version)
75 }
76 } else if let Some(hash) = self.commit_hash_short {
77 if self.is_dirty {
78 format!("v{}-dev+dirty.{}", self.version, hash)
79 } else {
80 format!("v{}-dev+{}", self.version, hash)
81 }
82 } else {
83 format!("v{}-dev", self.version)
84 }
85 } else if let Some(hash) = self.commit_hash_short {
86 if self.is_dirty {
87 format!("v{}-dev+dirty.{}", self.version, hash)
88 } else {
89 format!("v{}-dev+{}", self.version, hash)
90 }
91 } else {
92 format!("v{}-dev", self.version)
93 }
94 } else {
95 format!("v{}", self.version)
96 }
97 }
98}
99
100impl fmt::Display for VersionInfo {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 writeln!(f, "git-plumber\n\nVersion: {}", self.short_version())?;
103
104 if self.is_development_build() {
105 if let Some(git_version) = self.git_version {
106 writeln!(f, "Git version notation: {git_version}")?;
107 }
108 if let Some(commit_hash) = self.commit_hash {
109 writeln!(f, "Commit hash: {commit_hash}")?;
110 }
111 if let Some(commits_ahead) = self
112 .git_version
113 .and_then(|v| v.split('-').nth(1).and_then(|n| n.parse::<u32>().ok()))
114 {
115 writeln!(f, "Commits since last tag: {commits_ahead}")?;
116 }
117
118 if self.is_dirty {
119 writeln!(f, "Working directory: dirty")?;
120 }
121
122 writeln!(f, "Profile: {}", self.profile)?;
123 writeln!(f, "Target: {}", self.target)?;
124 writeln!(f, "Rust: {}", self.rustc_version)?;
125 }
126
127 Ok(())
128 }
129}
130
131pub fn get_version_info() -> VersionInfo {
132 VersionInfo::new()
133}