use regex::Regex;
use std::env;
use std::ffi::OsStr;
use std::fs::File;
use std::io::{ BufRead, BufReader };
use std::process::Command;
fn run_hg<S: AsRef<OsStr> + std::fmt::Debug> (args: &[S]) -> Option<String> {
let hg = &env::var ("HG").unwrap_or_else (|_| "hg".into());
let hg = which::which (hg).ok()?;
let hg = match hg.extension() {
Some (e) if e.to_ascii_lowercase() == "bat" => {
hg.with_extension ("")
},
_ => hg,
};
let mut cmd = (|| {
if hg.exists() {
let mut cmd = Command::new (
BufReader::new (File::open (&hg).ok()?)
.lines()
.next()?
.ok()?
.strip_prefix ("#!")?);
cmd.arg (&hg);
Some (cmd)
} else {
None
}
})().unwrap_or_else (|| {
Command::new (&hg)
});
cmd.env ("HGRCPATH", "")
.env ("LANG", "C")
.args (args);
cmd.output()
.inspect (
|o| if o.status.success() {
println!("{cmd:?} OK:\n{}",
String::from_utf8_lossy (&o.stdout));
} else {
println!("{cmd:?} ERROR:\n{}",
String::from_utf8_lossy (&o.stderr));
})
.inspect_err (|e| println!("{cmd:?} ERROR:\n{e:?}"))
.ok()
.and_then (|output| String::from_utf8 (output.stdout).ok())
}
fn run_git<S: AsRef<OsStr>> (args: &[S]) -> Option<String> {
Command::new ("git")
.env ("GIT_CONFIG_NOSYSTEM", "1")
.env ("XDG_CONFG_HOME", "")
.env ("HOME", "")
.env ("LANG", "C")
.args (args)
.output()
.ok()
.and_then (|output| String::from_utf8 (output.stdout).ok())
}
fn mkdate() -> String {
use time::{ OffsetDateTime, macros::format_description };
OffsetDateTime::now_utc()
.format (format_description!("[year repr:last_two][month][day]"))
.unwrap()
}
pub fn get_mercurial_version_tag() -> Option<String> {
let output = run_hg (&[ "id", "-i", "-t" ]);
let mut iter = output.iter().flat_map (|s| s.split_whitespace()).fuse();
let hash = match iter.next() {
Some (hash) => hash,
_ => { return None },
};
let clean = !hash.ends_with ("+");
if let Some (version)
= iter.find (|s| s.chars().next()
.map (|c| ('0' <= c) && (c <= '9'))
.unwrap_or (false))
{
Some (if clean { version.into() }
else { format!("{}+{}", version, mkdate()) })
} else {
let version = run_hg (
&[ "parents",
"--template",
r#"{latesttag("re:[0-9]+(:?\.[0-9]+)*")%'{tag}.dev{distance}'}+{node|short}"# ]);
if clean { version }
else { version.map (|s| format!("{}.{}", s, mkdate())) }
}
}
pub fn get_git_version_tag() -> Option<String> {
let output = run_git (&[ "describe", "--long", "--tags", "--dirty=+" ])?;
let output = output.trim();
let cap = Regex::new (r"^(.*)-([0-9]+)-g([0-9a-f]{7})(\+)?$")
.unwrap()
.captures (&output)?;
let tag = cap.get (1)?;
let dist = cap.get (2)?;
let hash = cap.get (3)?;
let clean = cap.get (4).is_none();
Some (
if dist.as_str() == "0" {
if clean { tag.as_str().into() }
else { format!("{}+{}", tag.as_str(), mkdate()) }
} else {
if clean {
format!("{}.dev{}+git{}",
tag.as_str(), dist.as_str(), hash.as_str())
} else {
format!("{}.dev{}+git{}.{}",
tag.as_str(), dist.as_str(), hash.as_str(), mkdate())
}
})
}
pub fn get_mercurial_archived_version_tag() -> Option<String> {
let version_re = Regex::new (r"[0-9]+(\.[0-9]+)*|null").unwrap();
let mut tag = None;
let mut latesttag = None;
let mut distance = None;
let mut node = None;
for l in File::open (".hg_archival.txt")
.iter()
.flat_map (|f| BufReader::new (f).lines())
.filter_map (|l| l.ok())
{
let mut split = l.splitn (2, ':');
match split.next() {
Some ("tag") => tag = tag
.or_else (
|| split
.next()
.map (str::trim)
.filter (|v| version_re.is_match (v))
.map (String::from)),
Some ("latesttag") => latesttag = latesttag
.or_else (
|| split
.next()
.map (str::trim)
.filter (|v| version_re.is_match (v))
.map (String::from)),
Some ("latesttagdistance") => distance = distance
.or_else (|| split.next().map (str::trim).map (String::from)),
Some ("node") => node = node
.or_else (|| split.next().map (str::trim).map (String::from)),
_ => {}
}
}
tag.map (|tag| format!("{}+archive.{}", tag, mkdate()))
.or_else (|| Some (
format!("{}.dev{}+archive.{:.12}.{}",
latesttag?, distance?, node?, mkdate())))
}
pub fn get_version() -> String {
get_mercurial_version_tag()
.or_else (get_git_version_tag)
.or_else (get_mercurial_archived_version_tag)
.unwrap_or_else (
|| format!("{}+cargo.{}",
env::var ("CARGO_PKG_VERSION").unwrap(),
mkdate())
.into())
}