@@ -4,19 +4,24 @@ use anyhow::Result;
4
4
use itertools:: Itertools ;
5
5
use lazy_regex:: { regex, Lazy } ;
6
6
use log:: debug;
7
+ use platforms:: { Platform , OS } ;
7
8
use regex:: Regex ;
8
- use std:: { ffi:: OsStr , path:: Path } ;
9
+ use std:: {
10
+ ffi:: OsStr ,
11
+ path:: { Path , PathBuf } ,
12
+ } ;
9
13
use strum:: { EnumIter , IntoEnumIterator } ;
10
14
use thiserror:: Error ;
11
15
12
16
#[ derive( Debug , Error ) ]
13
17
pub ( crate ) enum ExtensionError {
14
- #[ error( "{path: } has unknown extension {ext:}" ) ]
15
- UnknownExtension { path : String , ext : String } ,
18
+ #[ error( "{} has unknown extension {ext:}" , path . display ( ) ) ]
19
+ UnknownExtension { path : PathBuf , ext : String } ,
16
20
}
17
21
18
22
#[ derive( Debug , EnumIter , PartialEq , Eq ) ]
19
23
pub ( crate ) enum Extension {
24
+ AppImage ,
20
25
Bz ,
21
26
Bz2 ,
22
27
Exe ,
@@ -37,6 +42,7 @@ pub(crate) enum Extension {
37
42
impl Extension {
38
43
pub ( crate ) fn extension ( & self ) -> & ' static str {
39
44
match self {
45
+ Extension :: AppImage => ".AppImage" ,
40
46
Extension :: Bz => ".bz" ,
41
47
Extension :: Bz2 => ".bz2" ,
42
48
Extension :: Exe => ".exe" ,
@@ -55,9 +61,14 @@ impl Extension {
55
61
}
56
62
}
57
63
64
+ pub ( crate ) fn extension_without_dot ( & self ) -> & str {
65
+ self . extension ( ) . strip_prefix ( '.' ) . unwrap ( )
66
+ }
67
+
58
68
pub ( crate ) fn is_archive ( & self ) -> bool {
59
69
match self {
60
- Extension :: Bz
70
+ Extension :: AppImage
71
+ | Extension :: Bz
61
72
| Extension :: Bz2
62
73
| Extension :: Exe
63
74
| Extension :: Gz
@@ -75,41 +86,71 @@ impl Extension {
75
86
}
76
87
}
77
88
78
- pub ( crate ) fn from_path < S : AsRef < str > > ( path : S ) -> Result < Option < Extension > > {
79
- let path = path. as_ref ( ) ;
80
- let Some ( ext_str) = Path :: new ( path) . extension ( ) else {
89
+ pub ( crate ) fn should_preserve_extension_on_install ( & self ) -> bool {
90
+ match self {
91
+ Extension :: AppImage | Extension :: Exe | Extension :: Pyz => true ,
92
+ Extension :: Xz
93
+ | Extension :: Gz
94
+ | Extension :: Bz
95
+ | Extension :: Bz2
96
+ | Extension :: Tar
97
+ | Extension :: TarBz
98
+ | Extension :: TarBz2
99
+ | Extension :: TarGz
100
+ | Extension :: TarXz
101
+ | Extension :: Tbz
102
+ | Extension :: Tgz
103
+ | Extension :: Txz
104
+ | Extension :: Zip => false ,
105
+ }
106
+ }
107
+
108
+ pub ( crate ) fn matches_platform ( & self , platform : & Platform ) -> bool {
109
+ match self {
110
+ Extension :: AppImage => platform. target_os == OS :: Linux ,
111
+ Extension :: Exe => platform. target_os == OS :: Windows ,
112
+ _ => true ,
113
+ }
114
+ }
115
+
116
+ pub ( crate ) fn from_path ( path : & Path ) -> Result < Option < Extension > > {
117
+ let Some ( ext_str_from_path) = path. extension ( ) else {
81
118
return Ok ( None ) ;
82
119
} ;
120
+ let path_str = path. to_string_lossy ( ) ;
83
121
84
122
// We need to try the longest extensions first so that ".tar.gz" matches before ".gz" and so
85
123
// on for other compression formats.
86
124
if let Some ( ext) = Extension :: iter ( )
87
125
. sorted_by ( |a, b| Ord :: cmp ( & a. extension ( ) . len ( ) , & b. extension ( ) . len ( ) ) )
88
126
. rev ( )
89
- . find ( |e| path. ends_with ( e. extension ( ) ) )
127
+ // This is intentionally using a string comparison instead of looking at
128
+ // path.extension(). That's because the `.extension()` method returns `"bz"` for paths
129
+ // like "foo.tar.bz", instead of "tar.bz".
130
+ . find ( |e| path_str. ends_with ( e. extension ( ) ) )
90
131
{
91
132
return Ok ( Some ( ext) ) ;
92
133
}
93
134
94
- if extension_is_part_of_version ( path, ext_str ) {
95
- debug ! ( "the extension {ext_str :?} is part of the version, ignoring" ) ;
135
+ if extension_is_part_of_version ( path, ext_str_from_path ) {
136
+ debug ! ( "the extension {ext_str_from_path :?} is part of the version, ignoring" ) ;
96
137
return Ok ( None ) ;
97
138
}
98
139
99
- if extension_is_platform ( ext_str ) {
100
- debug ! ( "the extension {ext_str :?} is a platform name, ignoring" ) ;
140
+ if extension_is_platform ( ext_str_from_path ) {
141
+ debug ! ( "the extension {ext_str_from_path :?} is a platform name, ignoring" ) ;
101
142
return Ok ( None ) ;
102
143
}
103
144
104
145
Err ( ExtensionError :: UnknownExtension {
105
- path : path. to_string ( ) ,
106
- ext : ext_str . to_string_lossy ( ) . to_string ( ) ,
146
+ path : path. to_path_buf ( ) ,
147
+ ext : ext_str_from_path . to_string_lossy ( ) . to_string ( ) ,
107
148
}
108
149
. into ( ) )
109
150
}
110
151
}
111
152
112
- fn extension_is_part_of_version ( path : & str , ext_str : & OsStr ) -> bool {
153
+ fn extension_is_part_of_version ( path : & Path , ext_str : & OsStr ) -> bool {
113
154
let ext_str = ext_str. to_string_lossy ( ) . to_string ( ) ;
114
155
115
156
let version_number_ext_re = regex ! ( r"^[0-9]+" ) ;
@@ -119,7 +160,9 @@ fn extension_is_part_of_version(path: &str, ext_str: &OsStr) -> bool {
119
160
120
161
// This matches something like "foo_3.2.1_linux_amd64" and captures "1_linux_amd64".
121
162
let version_number_re = regex ! ( r"[0-9]+\.([0-9]+[^.]*)$" ) ;
122
- let Some ( caps) = version_number_re. captures ( path) else {
163
+ let Some ( caps) = version_number_re. captures ( path. to_str ( ) . expect (
164
+ "this path came from a UTF-8 string originally so it should always convert back to one" ,
165
+ ) ) else {
123
166
return false ;
124
167
} ;
125
168
let Some ( dot_num) = caps. get ( 1 ) else {
@@ -149,7 +192,9 @@ fn extension_is_platform(ext_str: &OsStr) -> bool {
149
192
mod test {
150
193
use super :: * ;
151
194
use test_case:: test_case;
195
+ use test_log:: test;
152
196
197
+ #[ test_case( "foo.AppImage" , Ok ( Some ( Extension :: AppImage ) ) ) ]
153
198
#[ test_case( "foo.bz" , Ok ( Some ( Extension :: Bz ) ) ) ]
154
199
#[ test_case( "foo.bz2" , Ok ( Some ( Extension :: Bz2 ) ) ) ]
155
200
#[ test_case( "foo.exe" , Ok ( Some ( Extension :: Exe ) ) ) ]
@@ -166,11 +211,11 @@ mod test {
166
211
#[ test_case( "foo_3.9.1.linux.amd64" , Ok ( None ) ) ]
167
212
#[ test_case( "i386-linux-ghcup-0.1.30.0" , Ok ( None ) ) ]
168
213
#[ test_case( "i386-linux-ghcup-0.1.30.0-linux_amd64" , Ok ( None ) ) ]
169
- #[ test_case( "foo.bar" , Err ( ExtensionError :: UnknownExtension { path: "foo.bar" . to_string ( ) , ext: "bar" . to_string( ) } . into( ) ) ) ]
214
+ #[ test_case( "foo.bar" , Err ( ExtensionError :: UnknownExtension { path: PathBuf :: from ( "foo.bar" ) , ext: "bar" . to_string( ) } . into( ) ) ) ]
170
215
fn from_path ( path : & str , expect : Result < Option < Extension > > ) {
171
216
crate :: test_case:: init_logging ( ) ;
172
217
173
- let ext = Extension :: from_path ( path) ;
218
+ let ext = Extension :: from_path ( Path :: new ( path) ) ;
174
219
if expect. is_ok ( ) {
175
220
assert ! ( ext. is_ok( ) ) ;
176
221
assert_eq ! ( ext. unwrap( ) , expect. unwrap( ) ) ;
@@ -181,4 +226,37 @@ mod test {
181
226
) ;
182
227
}
183
228
}
229
+
230
+ #[ test]
231
+ fn matches_platform ( ) -> Result < ( ) > {
232
+ let freebsd = Platform :: find ( "x86_64-unknown-freebsd" ) . unwrap ( ) . clone ( ) ;
233
+ let linux = Platform :: find ( "x86_64-unknown-linux-gnu" ) . unwrap ( ) . clone ( ) ;
234
+ let macos = Platform :: find ( "aarch64-apple-darwin" ) . unwrap ( ) . clone ( ) ;
235
+ let windows = Platform :: find ( "x86_64-pc-windows-msvc" ) . unwrap ( ) . clone ( ) ;
236
+
237
+ let ext = Extension :: from_path ( Path :: new ( "foo.exe" ) ) ?. unwrap ( ) ;
238
+ assert ! (
239
+ ext. matches_platform( & windows) ,
240
+ "foo.exe is valid on {windows}"
241
+ ) ;
242
+ for p in [ & freebsd, & linux, & macos] {
243
+ assert ! ( !ext. matches_platform( p) , "foo.exe is not valid on {p}" ) ;
244
+ }
245
+
246
+ let ext = Extension :: from_path ( Path :: new ( "foo.AppImage" ) ) ?. unwrap ( ) ;
247
+ assert ! (
248
+ ext. matches_platform( & linux) ,
249
+ "foo.exe is valid on {windows}"
250
+ ) ;
251
+ for p in [ & freebsd, & macos, & windows] {
252
+ assert ! ( !ext. matches_platform( p) , "foo.AppImage is not valid on {p}" ) ;
253
+ }
254
+
255
+ let ext = Extension :: from_path ( Path :: new ( "foo.tar.gz" ) ) ?. unwrap ( ) ;
256
+ for p in [ & freebsd, & linux, & macos, & windows] {
257
+ assert ! ( ext. matches_platform( p) , "foo.tar.gz is valid on {p}" ) ;
258
+ }
259
+
260
+ Ok ( ( ) )
261
+ }
184
262
}
0 commit comments