1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
use std::fmt::Display;
use std::str::FromStr;
use std::sync::LazyLock;
use regex::Regex;
/// The Mullvad VPN app product version
pub const VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/product-version.txt"));
#[derive(Debug, Clone, PartialEq)]
pub struct Version {
pub year: String,
pub incremental: String,
pub beta: Option<String>,
}
impl Version {
pub fn parse(version: &str) -> Version {
Version::from_str(version).unwrap()
}
}
impl Display for Version {
/// Format Version as a string: year.incremental{-beta}
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let Version {
year,
incremental,
beta,
} = &self;
match beta {
Some(beta) => write!(f, "{year}.{incremental}-{beta}"),
None => write!(f, "{year}.{incremental}"),
}
}
}
impl FromStr for Version {
type Err = String;
fn from_str(version: &str) -> Result<Self, Self::Err> {
const VERSION_REGEX: &str =
r"^20([0-9]{2})\.([1-9][0-9]?)(-beta([1-9][0-9]?))?(-dev-[0-9a-f]+)?$";
static RE: LazyLock<Regex> = LazyLock::new(|| Regex::new(VERSION_REGEX).unwrap());
let captures = RE
.captures(version)
.ok_or_else(|| format!("Version does not match expected format: {version}"))?;
let year = captures.get(1).expect("Missing year").as_str().to_owned();
let incremental = captures
.get(2)
.ok_or("Missing incremental")?
.as_str()
.to_owned();
let beta = captures.get(4).map(|m| m.as_str().to_owned());
Ok(Version {
year,
incremental,
beta,
})
}
}
|