@@ -3,18 +3,30 @@ mod sources;
3
3
4
4
use crate :: dirs:: LOCAL_CRATES_DIR ;
5
5
use crate :: prelude:: * ;
6
+ use cargo_metadata:: PackageId ;
7
+ use percent_encoding:: { percent_decode_str, utf8_percent_encode, NON_ALPHANUMERIC } ;
6
8
use rustwide:: Crate as RustwideCrate ;
9
+ use std:: convert:: TryFrom ;
7
10
use std:: fmt;
11
+ use std:: path:: Path ;
8
12
use std:: str:: FromStr ;
9
13
10
14
pub ( crate ) use crate :: crates:: sources:: github:: GitHubRepo ;
11
15
pub ( crate ) use crate :: crates:: sources:: registry:: RegistryCrate ;
12
16
17
+ #[ derive( Debug , Eq , PartialEq , Ord , PartialOrd , Hash , Serialize , Deserialize , Clone ) ]
18
+ pub struct GitRepo {
19
+ pub url : String ,
20
+ pub sha : Option < String > ,
21
+ }
22
+
13
23
#[ derive( Debug , Eq , PartialEq , Ord , PartialOrd , Hash , Serialize , Deserialize , Clone ) ]
14
24
pub enum Crate {
15
25
Registry ( RegistryCrate ) ,
16
26
GitHub ( GitHubRepo ) ,
17
27
Local ( String ) ,
28
+ Path ( String ) ,
29
+ Git ( GitRepo ) ,
18
30
}
19
31
20
32
impl Crate {
@@ -29,6 +41,20 @@ impl Crate {
29
41
}
30
42
}
31
43
Crate :: Local ( ref name) => format ! ( "local/{}" , name) ,
44
+ Crate :: Path ( ref path) => {
45
+ format ! ( "path/{}" , utf8_percent_encode( & path, & NON_ALPHANUMERIC ) )
46
+ }
47
+ Crate :: Git ( ref repo) => {
48
+ if let Some ( ref sha) = repo. sha {
49
+ format ! (
50
+ "git/{}/{}" ,
51
+ utf8_percent_encode( & repo. url, & NON_ALPHANUMERIC ) ,
52
+ sha
53
+ )
54
+ } else {
55
+ format ! ( "git/{}" , utf8_percent_encode( & repo. url, & NON_ALPHANUMERIC ) , )
56
+ }
57
+ }
32
58
}
33
59
}
34
60
@@ -39,6 +65,60 @@ impl Crate {
39
65
RustwideCrate :: git ( & format ! ( "https://github.com/{}/{}" , repo. org, repo. name) )
40
66
}
41
67
Self :: Local ( name) => RustwideCrate :: local ( & LOCAL_CRATES_DIR . join ( name) ) ,
68
+ Self :: Path ( path) => RustwideCrate :: local ( Path :: new ( & path) ) ,
69
+ Self :: Git ( repo) => RustwideCrate :: git ( & repo. url ) ,
70
+ }
71
+ }
72
+ }
73
+
74
+ impl TryFrom < & ' _ PackageId > for Crate {
75
+ type Error = failure:: Error ;
76
+
77
+ fn try_from ( pkgid : & PackageId ) -> Fallible < Crate > {
78
+ let parts = & pkgid
79
+ . repr
80
+ . split_ascii_whitespace ( )
81
+ . flat_map ( |s| {
82
+ // remove ()
83
+ s. trim_matches ( |c : char | c. is_ascii_punctuation ( ) )
84
+ // split resource and protocol
85
+ . split ( '+' )
86
+ } )
87
+ . collect :: < Vec < _ > > ( ) ;
88
+
89
+ match parts[ ..] {
90
+ [ name, version, "registry" , _] => Ok ( Crate :: Registry ( RegistryCrate {
91
+ name : name. to_string ( ) ,
92
+ version : version. to_string ( ) ,
93
+ } ) ) ,
94
+ [ _, _, "path" , path] => Ok ( Crate :: Path ( path. to_string ( ) ) ) ,
95
+ [ _, _, "git" , repo] => {
96
+ if repo. starts_with ( "https://github.com" ) {
97
+ Ok ( Crate :: GitHub ( repo. replace ( "#" , "/" ) . parse ( ) ?) )
98
+ } else {
99
+ let mut parts = repo. split ( '#' ) . rev ( ) . collect :: < Vec < _ > > ( ) ;
100
+ let url = parts. pop ( ) ;
101
+ let sha = parts. pop ( ) ;
102
+
103
+ match ( url, sha) {
104
+ ( Some ( url) , None ) => Ok ( Crate :: Git ( GitRepo {
105
+ url : url. to_string ( ) ,
106
+ sha : None ,
107
+ } ) ) ,
108
+ ( Some ( url) , Some ( sha) ) => Ok ( Crate :: Git ( GitRepo {
109
+ // remove additional queries if the sha is present
110
+ // as the crate version is already uniquely determined
111
+ url : url. split ( '?' ) . next ( ) . unwrap ( ) . to_string ( ) ,
112
+ sha : Some ( sha. to_string ( ) ) ,
113
+ } ) ) ,
114
+ _ => bail ! ( "malformed git repo: {}" , repo) ,
115
+ }
116
+ }
117
+ }
118
+ _ => bail ! (
119
+ "malformed pkgid format: {}\n maybe the representation has changed?" ,
120
+ pkgid. repr
121
+ ) ,
42
122
}
43
123
}
44
124
}
@@ -50,8 +130,25 @@ impl fmt::Display for Crate {
50
130
"{}" ,
51
131
match * self {
52
132
Crate :: Registry ( ref krate) => format!( "{}-{}" , krate. name, krate. version) ,
53
- Crate :: GitHub ( ref repo) => repo. slug( ) ,
133
+ Crate :: GitHub ( ref repo) =>
134
+ if let Some ( ref sha) = repo. sha {
135
+ format!( "{}/{}/{}" , repo. org, repo. name, sha)
136
+ } else {
137
+ format!( "{}/{}" , repo. org, repo. name)
138
+ } ,
54
139
Crate :: Local ( ref name) => format!( "{} (local)" , name) ,
140
+ Crate :: Path ( ref path) =>
141
+ format!( "{}" , utf8_percent_encode( path, & NON_ALPHANUMERIC ) ) ,
142
+ Crate :: Git ( ref repo) =>
143
+ if let Some ( ref sha) = repo. sha {
144
+ format!(
145
+ "{}/{}" ,
146
+ utf8_percent_encode( & repo. url, & NON_ALPHANUMERIC ) ,
147
+ sha
148
+ )
149
+ } else {
150
+ utf8_percent_encode( & repo. url, & NON_ALPHANUMERIC ) . to_string( )
151
+ } ,
55
152
}
56
153
)
57
154
}
@@ -77,17 +174,94 @@ impl FromStr for Crate {
77
174
name : name. to_string ( ) ,
78
175
sha : None ,
79
176
} ) ) ,
177
+ [ "git" , repo, sha] => Ok ( Crate :: Git ( GitRepo {
178
+ url : percent_decode_str ( repo) . decode_utf8 ( ) ?. to_string ( ) ,
179
+ sha : Some ( sha. to_string ( ) ) ,
180
+ } ) ) ,
181
+ [ "git" , repo] => Ok ( Crate :: Git ( GitRepo {
182
+ url : percent_decode_str ( repo) . decode_utf8 ( ) ?. to_string ( ) ,
183
+ sha : None ,
184
+ } ) ) ,
80
185
[ "local" , name] => Ok ( Crate :: Local ( name. to_string ( ) ) ) ,
186
+ [ "path" , path] => Ok ( Crate :: Path (
187
+ percent_decode_str ( path) . decode_utf8 ( ) ?. to_string ( ) ,
188
+ ) ) ,
81
189
_ => bail ! ( "unexpected crate value" ) ,
82
190
}
83
191
}
84
192
}
85
193
86
194
#[ cfg( test) ]
87
195
mod tests {
88
- use super :: { Crate , GitHubRepo , RegistryCrate } ;
196
+ use super :: { Crate , GitHubRepo , GitRepo , RegistryCrate } ;
197
+ use cargo_metadata:: PackageId ;
198
+ use percent_encoding:: { utf8_percent_encode, NON_ALPHANUMERIC } ;
199
+ use std:: convert:: TryFrom ;
89
200
use std:: str:: FromStr ;
90
201
202
+ macro_rules! test_from_pkgid {
203
+ ( $( $str: expr => $rust: expr, ) * ) => {
204
+ $(
205
+ let pkgid = PackageId {
206
+ repr: $str. to_string( ) ,
207
+ } ;
208
+
209
+ assert_eq!( Crate :: try_from( & pkgid) . unwrap( ) , $rust) ;
210
+ ) *
211
+ } ;
212
+ }
213
+
214
+ #[ test]
215
+ fn test_parse_from_pkgid ( ) {
216
+ test_from_pkgid ! {
217
+ "dummy 0.1.0 (path+file:///opt/rustwide/workdir)" => Crate :: Path ( "file:///opt/rustwide/workdir" . to_string( ) ) ,
218
+ "dummy 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" => Crate :: Registry ( RegistryCrate {
219
+ name: "dummy" . to_string( ) ,
220
+ version: "0.1.0" . to_string( )
221
+ } ) ,
222
+ "dummy 0.1.0 (git+https://github.com/dummy_org/dummy#9823f01cf4948a41279f6a3febcf793130cab4f6)" => Crate :: GitHub ( GitHubRepo {
223
+ org: "dummy_org" . to_string( ) ,
224
+ name: "dummy" . to_string( ) ,
225
+ sha: Some ( "9823f01cf4948a41279f6a3febcf793130cab4f6" . to_string( ) )
226
+ } ) ,
227
+ "dummy 0.1.0 (git+https://github.com/dummy_org/dummy?rev=dummyrev#9823f01cf4948a41279f6a3febcf793130cab4f6)" => Crate :: GitHub ( GitHubRepo {
228
+ org: "dummy_org" . to_string( ) ,
229
+ name: "dummy" . to_string( ) ,
230
+ sha: Some ( "9823f01cf4948a41279f6a3febcf793130cab4f6" . to_string( ) )
231
+ } ) ,
232
+ "dummy 0.1.0 (git+https://github.com/dummy_org/dummy)" => Crate :: GitHub ( GitHubRepo {
233
+ org: "dummy_org" . to_string( ) ,
234
+ name: "dummy" . to_string( ) ,
235
+ sha: None
236
+ } ) ,
237
+ "dummy 0.1.0 (git+https://gitlab.com/dummy_org/dummy#9823f01cf4948a41279f6a3febcf793130cab4f6)" => Crate :: Git ( GitRepo {
238
+ url: "https://gitlab.com/dummy_org/dummy"
239
+ . to_string( ) ,
240
+ sha: Some ( "9823f01cf4948a41279f6a3febcf793130cab4f6" . to_string( ) )
241
+ } ) ,
242
+ "dummy 0.1.0 (git+https://gitlab.com/dummy_org/dummy?branch=dummybranch#9823f01cf4948a41279f6a3febcf793130cab4f6)" => Crate :: Git ( GitRepo {
243
+ url: "https://gitlab.com/dummy_org/dummy"
244
+ . to_string( ) ,
245
+ sha: Some ( "9823f01cf4948a41279f6a3febcf793130cab4f6" . to_string( ) )
246
+ } ) ,
247
+ "dummy 0.1.0 (git+https://gitlab.com/dummy_org/dummy)" => Crate :: Git ( GitRepo {
248
+ url: "https://gitlab.com/dummy_org/dummy"
249
+ . to_string( ) ,
250
+ sha: None
251
+ } ) ,
252
+ "dummy 0.1.0 (git+https://gitlab.com/dummy_org/dummy?branch=dummybranch)" => Crate :: Git ( GitRepo {
253
+ url: "https://gitlab.com/dummy_org/dummy?branch=dummybranch"
254
+ . to_string( ) ,
255
+ sha: None
256
+ } ) ,
257
+ }
258
+
259
+ assert ! ( Crate :: try_from( & PackageId {
260
+ repr: "invalid" . to_string( )
261
+ } )
262
+ . is_err( ) ) ;
263
+ }
264
+
91
265
#[ test]
92
266
fn test_parse ( ) {
93
267
macro_rules! test_from_str {
@@ -107,8 +281,13 @@ mod tests {
107
281
108
282
test_from_str ! {
109
283
"local/build-fail" => Crate :: Local ( "build-fail" . to_string( ) ) ,
284
+ "path/pathtofile" => Crate :: Path ( "pathtofile" . to_string( ) ) ,
285
+ & format!( "path/{}" , utf8_percent_encode( "path/with:stange?characters" , & NON_ALPHANUMERIC ) ) => Crate :: Path ( "path/with:stange?characters" . to_string( ) ) ,
110
286
"gh/org/user" => Crate :: GitHub ( GitHubRepo { org: "org" . to_string( ) , name: "user" . to_string( ) , sha: None } ) ,
111
287
"gh/org/user/sha" => Crate :: GitHub ( GitHubRepo { org: "org" . to_string( ) , name: "user" . to_string( ) , sha: Some ( "sha" . to_string( ) ) } ) ,
288
+ "git/url" => Crate :: Git ( GitRepo { url: "url" . to_string( ) , sha: None } ) ,
289
+ & format!( "git/{}" , utf8_percent_encode( "url/with:stange?characters" , & NON_ALPHANUMERIC ) ) => Crate :: Git ( GitRepo { url: "url/with:stange?characters" . to_string( ) , sha: None } ) ,
290
+ "git/url/sha" => Crate :: Git ( GitRepo { url: "url" . to_string( ) , sha: Some ( "sha" . to_string( ) ) } ) ,
112
291
"reg/name/version" => Crate :: Registry ( RegistryCrate { name: "name" . to_string( ) , version: "version" . to_string( ) } ) ,
113
292
}
114
293
}
0 commit comments