6
6
7
7
//#![allow(unused_variables, dead_code)]
8
8
9
- use regex:: Regex ;
9
+ use regex:: { Captures , Regex } ;
10
10
use std:: error:: Error ;
11
+ use std:: str:: FromStr ;
11
12
13
+ #[ derive( Debug , Eq , PartialEq ) ]
12
14
pub struct GodotVersion {
13
15
/// the original string (trimmed, stripped of text around)
14
16
pub full_string : String ,
@@ -20,40 +22,64 @@ pub struct GodotVersion {
20
22
pub patch : u8 ,
21
23
22
24
/// alpha|beta|dev|stable
23
- pub stability : String ,
25
+ pub status : String ,
24
26
25
27
/// Git revision 'custom_build.{rev}' or '{official}.rev', if available
26
28
pub custom_rev : Option < String > ,
27
29
}
28
30
29
31
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
30
34
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)"# ,
35
+ r#"(?x) # ignore whitespace and allow line comments (starting with `#`)
36
+ ^
37
+ (?P<major>\d+)
38
+ \.(?P<minor>\d+)
39
+ # Patch version is omitted if it's zero.
40
+ (?:\.(?P<patch>\d+))?
41
+ # stable|dev|alpha|beta|rc12|... Can be set through an env var when the engine is built.
42
+ \.(?P<status>[^.]+)
43
+ # Capture both module config and build, could be multiple components:
44
+ # mono|official|custom_build|gentoo|arch_linux|...
45
+ # Notice +? for non-greedy match.
46
+ (\.[^.]+)+?
47
+ # Git commit SHA1, currently truncated to 9 chars, but accept the full thing
48
+ (?:\.(?P<custom_rev>[a-f0-9]{9,40}))?
49
+ $
50
+ "# ,
34
51
) ?;
35
52
36
53
let fail = || format ! ( "Version substring cannot be parsed: `{version_str}`" ) ;
37
54
let caps = regex. captures ( version_str) . ok_or_else ( fail) ?;
38
55
39
56
Ok ( GodotVersion {
40
57
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 ( ) ) ,
58
+ major : cap ( & caps, "major" ) ?. unwrap ( ) ,
59
+ minor : cap ( & caps, "minor" ) ?. unwrap ( ) ,
60
+ patch : cap ( & caps, "patch" ) ?. unwrap_or ( 0 ) ,
61
+ status : cap ( & caps, "status" ) ?. unwrap ( ) ,
62
+ custom_rev : cap ( & caps, "custom_rev" ) ?,
50
63
} )
51
64
}
52
65
66
+ /// Extracts and parses a named capture group from a regex match.
67
+ fn cap < T : FromStr > ( caps : & Captures , key : & str ) -> Result < Option < T > , Box < dyn Error > > {
68
+ caps. name ( key)
69
+ . map ( |m| m. as_str ( ) . parse ( ) )
70
+ . transpose ( )
71
+ . map_err ( |_| {
72
+ format ! (
73
+ "Version string cannot be parsed: `{}`" ,
74
+ caps. get( 0 ) . unwrap( ) . as_str( )
75
+ )
76
+ . into ( )
77
+ } )
78
+ }
79
+
53
80
// ----------------------------------------------------------------------------------------------------------------------------------------------
54
81
55
82
#[ test]
56
- #[ rustfmt:: skip]
57
83
fn test_godot_versions ( ) {
58
84
fn s ( s : & str ) -> Option < String > {
59
85
Some ( s. to_string ( ) )
@@ -64,35 +90,38 @@ fn test_godot_versions() {
64
90
( "3.0.1.stable.official" , 3 , 0 , 1 , "stable" , None ) ,
65
91
( "3.2.stable.official" , 3 , 2 , 0 , "stable" , None ) ,
66
92
( "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" ) ) ,
93
+ ( "3.4.stable.official.206ba70f4" , 3 , 4 , 0 , "stable" , s ( "206ba70f4" ) ) ,
94
+ ( "3.4.1.stable.official.aa1b95889" , 3 , 4 , 1 , "stable" , s ( "aa1b95889" ) ) ,
69
95
( "3.5.beta.custom_build.837f2c5f8" , 3 , 5 , 0 , "beta" , s ( "837f2c5f8" ) ) ,
70
- ( "4.0.beta8.gentoo.45cac42c0" , 4 , 0 , 0 , "beta " , s ( "45cac42c0" ) ) ,
96
+ ( "4.0.beta8.gentoo.45cac42c0" , 4 , 0 , 0 , "beta8 " , s ( "45cac42c0" ) ) ,
71
97
( "4.0.dev.custom_build.e7e9e663b" , 4 , 0 , 0 , "dev" , s ( "e7e9e663b" ) ) ,
72
98
( "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" ) ) ,
99
+ ( "4.0.beta8.mono.custom_build.b28ddd918" , 4 , 0 , 0 , "beta8" , s ( "b28ddd918" ) ) ,
100
+ ( "4.0.rc1.official.8843d9ad3" , 4 , 0 , 0 , "rc1" , s ( "8843d9ad3" ) ) ,
101
+ ( "4.0.stable.arch_linux" , 4 , 0 , 0 , "stable" , None ) ,
75
102
] ;
76
103
77
104
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)
105
+ "Godot Engine v4.0.stable.arch_linux - https://godotengine.org" , // Surrounding cruft
106
+ "3.stable.official.206ba70f4" , // No minor version
107
+ "4.0.stable" , // No build type
82
108
] ;
83
109
84
- for ( full, major, minor, patch, stability, custom_rev) in good_versions {
110
+ for ( full, major, minor, patch, status, custom_rev) in good_versions {
111
+ let expected = GodotVersion {
112
+ full_string : full. to_owned ( ) ,
113
+ major,
114
+ minor,
115
+ patch,
116
+ status : status. to_owned ( ) ,
117
+ custom_rev,
118
+ } ;
85
119
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) ;
120
+ assert_eq ! ( parsed, expected, "{}" , full) ;
92
121
}
93
122
94
123
for full in bad_versions {
95
124
let parsed = parse_godot_version ( full) ;
96
- assert ! ( parsed. is_err( ) ) ;
125
+ assert ! ( parsed. is_err( ) , "{}" , full ) ;
97
126
}
98
127
}
0 commit comments