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,36 +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
34
+ // We assume that it's on a line of its own, but it may be surrounded by other lines.
30
35
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
+ "# ,
34
54
) ?;
35
55
36
56
let fail = || format ! ( "Version substring cannot be parsed: `{version_str}`" ) ;
37
57
let caps = regex. captures ( version_str) . ok_or_else ( fail) ?;
38
58
39
59
Ok ( GodotVersion {
40
60
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" ) ?,
50
66
} )
51
67
}
52
68
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
+
53
83
// ----------------------------------------------------------------------------------------------------------------------------------------------
54
84
55
85
#[ test]
@@ -64,35 +94,46 @@ fn test_godot_versions() {
64
94
( "3.0.1.stable.official" , 3 , 0 , 1 , "stable" , None ) ,
65
95
( "3.2.stable.official" , 3 , 2 , 0 , "stable" , None ) ,
66
96
( "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" ) ) ,
69
99
( "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" ) ) ,
71
101
( "4.0.dev.custom_build.e7e9e663b" , 4 , 0 , 0 , "dev" , s ( "e7e9e663b" ) ) ,
72
102
( "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" ) ) ,
75
113
] ;
76
114
77
115
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
82
119
] ;
83
120
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
+ } ;
85
131
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) ;
92
133
}
93
134
94
135
for full in bad_versions {
95
136
let parsed = parse_godot_version ( full) ;
96
- assert ! ( parsed. is_err( ) ) ;
137
+ assert ! ( parsed. is_err( ) , "{}" , full ) ;
97
138
}
98
139
}
0 commit comments