1
- use std:: { borrow:: Cow , io:: BufWriter , path:: PathBuf } ;
1
+ use std:: { borrow:: Cow , io:: BufWriter , path:: PathBuf , sync :: OnceLock } ;
2
2
3
- use anyhow:: { Context , Result } ;
3
+ use anyhow:: { Error , Result } ;
4
4
use md5:: { Digest , Md5 } ;
5
+ use regex:: Regex ;
5
6
use yazi_shared:: Xdg ;
6
7
8
+ static PACKAGE_RAW_URL_RE : OnceLock < Regex > = OnceLock :: new ( ) ;
9
+ static PACKAGE_SHORT_URL_RE : OnceLock < Regex > = OnceLock :: new ( ) ;
10
+
11
+ #[ inline]
12
+ fn package_raw_url_re ( ) -> & ' static Regex {
13
+ PACKAGE_RAW_URL_RE . get_or_init ( || {
14
+ Regex :: new ( r"^(?P<proto>[^:]+)://(?P<host>[^/]+)/(?P<url_path>[^:]+)(:(?P<child>.*))?$" )
15
+ . unwrap ( )
16
+ } )
17
+ }
18
+
19
+ #[ inline]
20
+ fn package_short_url_re ( ) -> & ' static Regex {
21
+ PACKAGE_SHORT_URL_RE . get_or_init ( || {
22
+ Regex :: new ( r"^((?P<host>[^/]+)/)?(?P<owner>[^/]+)/(?P<repo>[^:]+)(:(?P<child>.*))?$" ) . unwrap ( )
23
+ } )
24
+ }
25
+
26
+ #[ derive( Debug ) ]
7
27
pub ( crate ) struct Package {
28
+ pub ( crate ) proto : String ,
8
29
pub ( crate ) host : String ,
9
- pub ( crate ) owner : String ,
10
- pub ( crate ) repo_name : String ,
30
+ pub ( crate ) url_path : String ,
11
31
pub ( crate ) child : String ,
12
32
pub ( crate ) rev : String ,
13
33
pub ( super ) is_flavor : bool ,
14
34
}
15
35
16
36
impl Package {
17
37
pub ( super ) fn new ( url : & str , rev : Option < & str > ) -> Result < Self > {
18
- let mut parts = url. splitn ( 2 , ':' ) ;
38
+ let rev = rev. unwrap_or_default ( ) . to_owned ( ) ;
39
+ let is_flavor = false ;
40
+
41
+ if let Some ( raw_url_match) = package_raw_url_re ( ) . captures ( url) {
42
+ let proto = raw_url_match[ "proto" ] . to_owned ( ) ;
43
+ let host = raw_url_match[ "host" ] . to_owned ( ) ;
44
+ let mut url_path = raw_url_match[ "url_path" ] . to_owned ( ) ;
45
+ let child = if let Some ( child) = raw_url_match. name ( "child" ) {
46
+ format ! ( "{}.yazi" , child. as_str( ) )
47
+ } else {
48
+ url_path. push_str ( ".yazi" ) ;
49
+ String :: new ( )
50
+ } ;
51
+
52
+ Ok ( Self { proto, host, url_path, child, rev, is_flavor } )
53
+ } else if let Some ( short_url_match) = package_short_url_re ( ) . captures ( url) {
54
+ let proto = "https" . to_owned ( ) ;
55
+ let host =
56
+ short_url_match. name ( "host" ) . map ( |m| m. as_str ( ) ) . unwrap_or ( "github.com" ) . to_owned ( ) ;
57
+ let owner = & short_url_match[ "owner" ] ;
58
+ let repo = & short_url_match[ "repo" ] ;
59
+ let mut url_path = format ! ( "{owner}/{repo}" ) ;
60
+
61
+ let child = if let Some ( child) = short_url_match. name ( "child" ) {
62
+ format ! ( "{}.yazi" , child. as_str( ) )
63
+ } else {
64
+ url_path. push_str ( ".yazi" ) ;
65
+ String :: new ( )
66
+ } ;
19
67
20
- let mut repo_part = parts. next ( ) . unwrap_or_default ( ) . to_owned ( ) ;
21
- let child = if let Some ( s) = parts. next ( ) {
22
- format ! ( "{s}.yazi" )
68
+ Ok ( Self { proto, host, url_path, child, rev, is_flavor } )
23
69
} else {
24
- repo_part. push_str ( ".yazi" ) ;
25
- String :: new ( )
26
- } ;
27
-
28
- let mut repo = repo_part. rsplit ( '/' ) ;
29
- let repo_name = repo. next ( ) . context ( "failed to get repo name" ) ?. to_owned ( ) ;
30
- let owner = repo. next ( ) . context ( "failed to get repo owner" ) ?. to_owned ( ) ;
31
- let host = repo. next ( ) . unwrap_or ( "github.com" ) . to_owned ( ) ;
32
-
33
- Ok ( Self {
34
- repo_name,
35
- owner,
36
- host,
37
- child,
38
- rev : rev. unwrap_or_default ( ) . to_owned ( ) ,
39
- is_flavor : false ,
40
- } )
70
+ Err ( Error :: msg ( "invalid package url" ) )
71
+ }
41
72
}
42
73
43
74
#[ inline]
44
75
pub ( super ) fn use_ ( & self ) -> Cow < str > {
45
76
if self . child . is_empty ( ) {
46
- format ! ( "{}/ {}/{}" , self . host , self . owner , self . repo_name . trim_end_matches( ".yazi" ) ) . into ( )
77
+ format ! ( "{}:// {}/{}" , self . proto , self . host , self . url_path . trim_end_matches( ".yazi" ) ) . into ( )
47
78
} else {
48
79
format ! (
49
- "{}/{}/{}:{}" ,
80
+ "{}://{}/{}:{}" ,
81
+ self . proto,
50
82
self . host,
51
- self . owner,
52
- self . repo_name,
83
+ self . url_path,
53
84
self . child. trim_end_matches( ".yazi" )
54
85
)
55
86
. into ( )
@@ -58,7 +89,11 @@ impl Package {
58
89
59
90
#[ inline]
60
91
pub ( super ) fn name ( & self ) -> & str {
61
- if self . child . is_empty ( ) { self . repo_name . as_str ( ) } else { self . child . as_str ( ) }
92
+ if self . child . is_empty ( ) {
93
+ self . url_path . rsplit ( '/' ) . next ( ) . unwrap_or ( & self . url_path )
94
+ } else {
95
+ self . child . as_str ( )
96
+ }
62
97
}
63
98
64
99
#[ inline]
@@ -70,7 +105,7 @@ impl Package {
70
105
71
106
#[ inline]
72
107
pub ( super ) fn remote ( & self ) -> String {
73
- format ! ( "https ://{}/{}/{}.git " , self . host , self . owner , self . repo_name )
108
+ format ! ( "{} ://{}/{}" , self . proto , self . host , self . url_path )
74
109
}
75
110
76
111
pub ( super ) fn header ( & self , s : & str ) -> Result < ( ) > {
@@ -90,3 +125,116 @@ impl Package {
90
125
Ok ( ( ) )
91
126
}
92
127
}
128
+
129
+ #[ cfg( test) ]
130
+ mod tests {
131
+ use super :: * ;
132
+
133
+ #[ test]
134
+ fn two_component_short_url ( ) -> Result < ( ) > {
135
+ let url = "owner/repo" ;
136
+
137
+ let pkg = Package :: new ( url, None ) ?;
138
+
139
+ assert_eq ! ( pkg. proto, "https" ) ;
140
+ assert_eq ! ( pkg. host, "github.com" ) ;
141
+ assert_eq ! ( pkg. url_path, "owner/repo.yazi" ) ;
142
+ assert_eq ! ( pkg. child, "" ) ;
143
+
144
+ assert_eq ! ( pkg. remote( ) , "https://github.com/owner/repo.yazi" ) ;
145
+
146
+ Ok ( ( ) )
147
+ }
148
+
149
+ #[ test]
150
+ fn three_component_short_url ( ) -> Result < ( ) > {
151
+ let url = "codeberg.org/owner/repo" ;
152
+
153
+ let pkg = Package :: new ( url, None ) ?;
154
+
155
+ assert_eq ! ( pkg. proto, "https" ) ;
156
+ assert_eq ! ( pkg. host, "codeberg.org" ) ;
157
+ assert_eq ! ( pkg. url_path, "owner/repo.yazi" ) ;
158
+ assert_eq ! ( pkg. child, "" ) ;
159
+
160
+ assert_eq ! ( pkg. remote( ) , "https://codeberg.org/owner/repo.yazi" ) ;
161
+
162
+ Ok ( ( ) )
163
+ }
164
+
165
+ #[ test]
166
+ fn two_component_short_url_with_child_path ( ) -> Result < ( ) > {
167
+ let url = "owner/repo:my-plugin" ;
168
+
169
+ let pkg = Package :: new ( url, None ) ?;
170
+
171
+ assert_eq ! ( pkg. proto, "https" ) ;
172
+ assert_eq ! ( pkg. host, "github.com" ) ;
173
+ assert_eq ! ( pkg. url_path, "owner/repo" ) ;
174
+ assert_eq ! ( pkg. child, "my-plugin.yazi" ) ;
175
+
176
+ assert_eq ! ( pkg. remote( ) , "https://github.com/owner/repo" ) ;
177
+ assert_eq ! ( pkg. use_( ) , "https://github.com/owner/repo:my-plugin" ) ;
178
+
179
+ Ok ( ( ) )
180
+ }
181
+
182
+ #[ test]
183
+ fn raw_ssh_url ( ) -> Result < ( ) > {
184
+ let url = "ssh://git@my-host:6969/my-plugin" ;
185
+
186
+ let pkg = Package :: new ( url, None ) ?;
187
+
188
+ assert_eq ! ( pkg. proto, "ssh" ) ;
189
+ assert_eq ! ( pkg. host, "git@my-host:6969" ) ;
190
+ assert_eq ! ( pkg. url_path, "my-plugin.yazi" ) ;
191
+ assert_eq ! ( pkg. child, "" ) ;
192
+
193
+ assert_eq ! ( pkg. remote( ) , "ssh://git@my-host:6969/my-plugin.yazi" ) ;
194
+
195
+ Ok ( ( ) )
196
+ }
197
+
198
+ #[ test]
199
+ fn raw_ssh_url_with_child_path ( ) -> Result < ( ) > {
200
+ let url =
"ssh://[email protected] :2222/~/my-repo.git:my-plugin" ;
201
+
202
+ let pkg = Package :: new ( url, None ) ?;
203
+
204
+ assert_eq ! ( pkg. proto, "ssh" ) ;
205
+ assert_eq ! ( pkg
. host
, "[email protected] :2222" ) ;
206
+ assert_eq ! ( pkg. url_path, "~/my-repo.git" ) ;
207
+ assert_eq ! ( pkg. child, "my-plugin.yazi" ) ;
208
+
209
+ assert_eq ! ( pkg
. remote
( ) , "ssh://[email protected] :2222/~/my-repo.git" ) ;
210
+ assert_eq ! ( pkg
. use_
( ) , "ssh://[email protected] :2222/~/my-repo.git:my-plugin" ) ;
211
+
212
+ Ok ( ( ) )
213
+ }
214
+
215
+ #[ test]
216
+ fn raw_http_url_with_non_standard_path ( ) -> Result < ( ) > {
217
+ let url = "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin" ;
218
+
219
+ let pkg = Package :: new ( url, None ) ?;
220
+
221
+ assert_eq ! ( pkg. proto, "https" ) ;
222
+ assert_eq ! ( pkg. host, "example.com" ) ;
223
+ assert_eq ! ( pkg. url_path, "xxx/yyy/zzz/owner/repo" ) ;
224
+ assert_eq ! ( pkg. child, "my-plugin.yazi" ) ;
225
+
226
+ assert_eq ! ( pkg. remote( ) , "https://example.com/xxx/yyy/zzz/owner/repo" ) ;
227
+ assert_eq ! ( pkg. use_( ) , "https://example.com/xxx/yyy/zzz/owner/repo:my-plugin" ) ;
228
+
229
+ Ok ( ( ) )
230
+ }
231
+
232
+ #[ test]
233
+ fn invalid_url ( ) {
234
+ let url = "one-component-url???" ;
235
+
236
+ let pkg = Package :: new ( url, None ) ;
237
+
238
+ assert ! ( pkg. is_err( ) ) ;
239
+ }
240
+ }
0 commit comments