Skip to content

Commit f05915f

Browse files
bors[bot]ttencate
andauthored
Merge #168
168: Allow version regex to match 4.0.stable.arch_linux r=Bromeon a=ttencate Also leverage named capture groups and verbose regex mode for slightly more readability. Co-authored-by: Thomas ten Cate <[email protected]>
2 parents b0762e9 + 725c280 commit f05915f

File tree

1 file changed

+72
-31
lines changed

1 file changed

+72
-31
lines changed

godot-codegen/src/godot_version.rs

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
//#![allow(unused_variables, dead_code)]
88

9-
use regex::Regex;
9+
use regex::{Captures, Regex};
1010
use std::error::Error;
11+
use std::str::FromStr;
1112

13+
#[derive(Debug, Eq, PartialEq)]
1214
pub struct GodotVersion {
1315
/// the original string (trimmed, stripped of text around)
1416
pub full_string: String,
@@ -20,36 +22,64 @@ pub struct GodotVersion {
2022
pub patch: u8,
2123

2224
/// alpha|beta|dev|stable
23-
pub stability: String,
25+
pub status: String,
2426

2527
/// Git revision 'custom_build.{rev}' or '{official}.rev', if available
2628
pub custom_rev: Option<String>,
2729
}
2830

2931
pub fn parse_godot_version(version_str: &str) -> Result<GodotVersion, Box<dyn Error>> {
32+
// Format of the string emitted by `godot --version`:
33+
// https://github.com/godot-rust/gdext/issues/118#issuecomment-1465748123
34+
// We assume that it's on a line of its own, but it may be surrounded by other lines.
3035
let regex = Regex::new(
31-
// major minor [patch] official|custom_build|gentoo
32-
// v v v v
33-
r#"(\d+)\.(\d+)(?:\.(\d+))?\.(alpha|beta|dev|rc|stable)[0-9]*\.(?:mono\.)?(?:[a-z_]+\.([a-f0-9]+)|official)"#,
36+
r#"(?xm)
37+
# x: ignore whitespace and allow line comments (starting with `#`)
38+
# m: multi-line mode, ^ and $ match start and end of line
39+
^
40+
(?P<major>\d+)
41+
\.(?P<minor>\d+)
42+
# Patch version is omitted if it's zero.
43+
(?:\.(?P<patch>\d+))?
44+
# stable|dev|alpha|beta|rc12|... Can be set through an env var when the engine is built.
45+
\.(?P<status>[^.]+)
46+
# Capture both module config and build, could be multiple components:
47+
# mono|official|custom_build|gentoo|arch_linux|...
48+
# Notice +? for non-greedy match.
49+
(\.[^.]+)+?
50+
# Git commit SHA1, currently truncated to 9 chars, but accept the full thing
51+
(?:\.(?P<custom_rev>[a-f0-9]{9,40}))?
52+
$
53+
"#,
3454
)?;
3555

3656
let fail = || format!("Version substring cannot be parsed: `{version_str}`");
3757
let caps = regex.captures(version_str).ok_or_else(fail)?;
3858

3959
Ok(GodotVersion {
4060
full_string: caps.get(0).unwrap().as_str().to_string(),
41-
major: caps.get(1).ok_or_else(fail)?.as_str().parse::<u8>()?,
42-
minor: caps.get(2).ok_or_else(fail)?.as_str().parse::<u8>()?,
43-
patch: caps
44-
.get(3)
45-
.map(|m| m.as_str().parse::<u8>())
46-
.transpose()?
47-
.unwrap_or(0),
48-
stability: caps.get(4).ok_or_else(fail)?.as_str().to_string(),
49-
custom_rev: caps.get(5).map(|m| m.as_str().to_string()),
61+
major: cap(&caps, "major")?.unwrap(),
62+
minor: cap(&caps, "minor")?.unwrap(),
63+
patch: cap(&caps, "patch")?.unwrap_or(0),
64+
status: cap(&caps, "status")?.unwrap(),
65+
custom_rev: cap(&caps, "custom_rev")?,
5066
})
5167
}
5268

69+
/// Extracts and parses a named capture group from a regex match.
70+
fn cap<T: FromStr>(caps: &Captures, key: &str) -> Result<Option<T>, Box<dyn Error>> {
71+
caps.name(key)
72+
.map(|m| m.as_str().parse())
73+
.transpose()
74+
.map_err(|_| {
75+
format!(
76+
"Version string cannot be parsed: `{}`",
77+
caps.get(0).unwrap().as_str()
78+
)
79+
.into()
80+
})
81+
}
82+
5383
// ----------------------------------------------------------------------------------------------------------------------------------------------
5484

5585
#[test]
@@ -64,35 +94,46 @@ fn test_godot_versions() {
6494
("3.0.1.stable.official", 3, 0, 1, "stable", None),
6595
("3.2.stable.official", 3, 2, 0, "stable", None),
6696
("3.37.stable.official", 3, 37, 0, "stable", None),
67-
("3.4.stable.official.206ba70f4", 3, 4, 0, "stable", s("206ba70f4")),
68-
("3.4.1.stable.official.aa1b95889", 3, 4, 1, "stable", s("aa1b95889")),
97+
("3.4.stable.official.206ba70f4", 3, 4, 0, "stable", s("206ba70f4")),
98+
("3.4.1.stable.official.aa1b95889", 3, 4, 1, "stable", s("aa1b95889")),
6999
("3.5.beta.custom_build.837f2c5f8", 3, 5, 0, "beta", s("837f2c5f8")),
70-
("4.0.beta8.gentoo.45cac42c0", 4, 0, 0, "beta", s("45cac42c0")),
100+
("4.0.beta8.gentoo.45cac42c0", 4, 0, 0, "beta8", s("45cac42c0")),
71101
("4.0.dev.custom_build.e7e9e663b", 4, 0, 0, "dev", s("e7e9e663b")),
72102
("4.0.alpha.custom_build.faddbcfc0", 4, 0, 0, "alpha", s("faddbcfc0")),
73-
("4.0.beta8.mono.custom_build.b28ddd918", 4, 0, 0, "beta", s("b28ddd918")),
74-
("4.0.rc1.official.8843d9ad3", 4, 0, 0, "rc", s("8843d9ad3")),
103+
("4.0.beta8.mono.custom_build.b28ddd918", 4, 0, 0, "beta8", s("b28ddd918")),
104+
("4.0.rc1.official.8843d9ad3", 4, 0, 0, "rc1", s("8843d9ad3")),
105+
("4.0.stable.arch_linux", 4, 0, 0, "stable", None),
106+
// Output from 4.0.stable on MacOS in debug mode:
107+
// https://github.com/godotengine/godot/issues/74906
108+
("arguments
109+
0: /Users/runner/work/_temp/godot_bin/godot.macos.editor.dev.x86_64
110+
1: --version
111+
Current path: /Users/runner/work/gdext/gdext/godot-core
112+
4.1.dev.custom_build.79454bfd3", 4, 1, 0, "dev", s("79454bfd3")),
75113
];
76114

77115
let bad_versions = [
78-
"4.0.unstable.custom_build.e7e9e663b", // 'unstable'
79-
"4.0.3.custom_build.e7e9e663b", // no stability
80-
"3.stable.official.206ba70f4", // no minor
81-
"4.0.alpha.custom_build", // no rev after 'custom_build' (this is allowed for 'official' however)
116+
"Godot Engine v4.0.stable.arch_linux - https://godotengine.org", // Surrounding cruft
117+
"3.stable.official.206ba70f4", // No minor version
118+
"4.0.stable", // No build type
82119
];
83120

84-
for (full, major, minor, patch, stability, custom_rev) in good_versions {
121+
for (full, major, minor, patch, status, custom_rev) in good_versions {
122+
let expected = GodotVersion {
123+
// Version line is last in every test at the moment.
124+
full_string: full.lines().last().unwrap().to_owned(),
125+
major,
126+
minor,
127+
patch,
128+
status: status.to_owned(),
129+
custom_rev,
130+
};
85131
let parsed: GodotVersion = parse_godot_version(full).unwrap();
86-
assert_eq!(parsed.major, major);
87-
assert_eq!(parsed.minor, minor);
88-
assert_eq!(parsed.patch, patch);
89-
assert_eq!(parsed.stability, stability);
90-
assert_eq!(parsed.custom_rev, custom_rev);
91-
assert_eq!(parsed.full_string, full);
132+
assert_eq!(parsed, expected, "{}", full);
92133
}
93134

94135
for full in bad_versions {
95136
let parsed = parse_godot_version(full);
96-
assert!(parsed.is_err());
137+
assert!(parsed.is_err(), "{}", full);
97138
}
98139
}

0 commit comments

Comments
 (0)