@@ -2,7 +2,7 @@ use lazy_static::lazy_static;
2
2
use lru:: LruCache ;
3
3
use regex:: bytes:: Regex ;
4
4
use serde:: Deserialize ;
5
- use std:: { path:: { Path , PathBuf } , fs, io:: { BufReader , Read } , collections:: { HashSet , HashMap } } ;
5
+ use std:: { path:: { Path , PathBuf } , fs, io:: { BufReader , Read } , collections:: { HashSet , HashMap } , str :: Utf8Error , num :: NonZeroUsize } ;
6
6
use zip:: { ZipArchive , result:: ZipError } ;
7
7
8
8
use crate :: util;
@@ -12,8 +12,8 @@ use crate::util;
12
12
#[ derive( Deserialize ) ]
13
13
#[ derive( PartialEq ) ]
14
14
#[ serde( untagged) ]
15
- pub enum PnpPath {
16
- Zip ( PathBuf , Option < String > ) ,
15
+ pub enum VPath {
16
+ Zip ( PathBuf , String ) ,
17
17
Native ( PathBuf ) ,
18
18
}
19
19
@@ -28,19 +28,35 @@ pub enum Error {
28
28
#[ error( "Decompression error" ) ]
29
29
DecompressionError ,
30
30
31
+ #[ error( transparent) ]
32
+ Utf8Error ( #[ from] Utf8Error ) ,
33
+
31
34
#[ error( transparent) ]
32
35
IOError ( #[ from] std:: io:: Error ) ,
33
36
34
37
#[ error( transparent) ]
35
38
ZipError ( #[ from] ZipError ) ,
36
39
}
37
40
38
- pub fn open_zip ( p : & Path ) -> Result < Zip , Error > {
41
+ fn make_io_utf8_error ( ) -> std:: io:: Error {
42
+ std:: io:: Error :: new (
43
+ std:: io:: ErrorKind :: InvalidData ,
44
+ "File did not contain valid UTF-8"
45
+ )
46
+ }
47
+
48
+ fn io_bytes_to_str ( vec : & [ u8 ] ) -> Result < & str , std:: io:: Error > {
49
+ std:: str:: from_utf8 ( & vec)
50
+ . map_err ( |_| make_io_utf8_error ( ) )
51
+ }
52
+
53
+ pub fn open_zip ( p : & Path ) -> Result < Zip , std:: io:: Error > {
39
54
let file = fs:: File :: open ( p) ?;
40
55
let reader = BufReader :: new ( file) ;
41
56
42
57
let archive = ZipArchive :: new ( reader) ?;
43
- let zip = Zip :: new ( archive) ?;
58
+ let zip = Zip :: new ( archive)
59
+ . map_err ( |_| std:: io:: Error :: new ( std:: io:: ErrorKind :: Other , "Failed to read the zip file" ) ) ?;
44
60
45
61
Ok ( zip)
46
62
}
@@ -88,10 +104,10 @@ impl Zip {
88
104
self . files . contains_key ( p)
89
105
}
90
106
91
- pub fn read_file ( & mut self , p : & str ) -> Result < Vec < u8 > , Error > {
107
+ pub fn read ( & mut self , p : & str ) -> Result < Vec < u8 > , std :: io :: Error > {
92
108
let i = self . files . get ( p)
93
- . ok_or ( Error :: EntryNotFound ) ?;
94
-
109
+ . ok_or ( std :: io :: Error :: from ( std :: io :: ErrorKind :: NotFound ) ) ?;
110
+
95
111
let mut entry = self . archive . by_index_raw ( * i) ?;
96
112
let mut data = Vec :: new ( ) ;
97
113
@@ -100,7 +116,7 @@ impl Zip {
100
116
match entry. compression ( ) {
101
117
zip:: CompressionMethod :: DEFLATE => {
102
118
let decompressed_data = miniz_oxide:: inflate:: decompress_to_vec ( & data)
103
- . map_err ( |_e| Error :: DecompressionError ) ?;
119
+ . map_err ( |_| std :: io :: Error :: new ( std :: io :: ErrorKind :: Other , "Error during decompression" ) ) ?;
104
120
105
121
Ok ( decompressed_data)
106
122
}
@@ -110,56 +126,84 @@ impl Zip {
110
126
}
111
127
112
128
_ => {
113
- Err ( Error :: UnsupportedCompression )
129
+ Err ( std :: io :: Error :: new ( std :: io :: ErrorKind :: InvalidData , "Unsupported compression algorithm" ) )
114
130
}
115
131
}
116
132
}
133
+
134
+ pub fn read_to_string ( & mut self , p : & str ) -> Result < String , std:: io:: Error > {
135
+ let data = self . read ( p) ?;
136
+
137
+ Ok ( io_bytes_to_str ( data. as_slice ( ) ) ?. to_string ( ) )
138
+ }
117
139
}
118
140
119
141
pub trait ZipCache {
120
- fn act < T , F : FnOnce ( & Zip ) -> T > ( & mut self , p : & Path , cb : F ) -> Result < T , & Error > ;
142
+ fn act < T , F : FnOnce ( & mut Zip ) -> T > ( & mut self , p : & Path , cb : F ) -> Result < T , std:: io:: Error > ;
143
+
144
+ fn canonicalize ( & mut self , zip_path : & Path , sub : & str ) -> Result < PathBuf , std:: io:: Error > ;
145
+
146
+ fn is_dir ( & mut self , zip_path : & Path , sub : & str ) -> bool ;
147
+ fn is_file ( & mut self , zip_path : & Path , sub : & str ) -> bool ;
148
+
149
+ fn read ( & mut self , zip_path : & Path , sub : & str ) -> Result < Vec < u8 > , std:: io:: Error > ;
150
+ fn read_to_string ( & mut self , zip_path : & Path , sub : & str ) -> Result < String , std:: io:: Error > ;
121
151
}
122
152
123
153
pub struct LruZipCache {
124
- lru : LruCache < PathBuf , Result < Zip , Error > > ,
154
+ lru : LruCache < PathBuf , Zip > ,
125
155
}
126
156
127
- impl ZipCache for LruZipCache {
128
- fn act < T , F : FnOnce ( & Zip ) -> T > ( & mut self , p : & Path , cb : F ) -> Result < T , & Error > {
129
- let res = self . lru . get_or_insert ( p. to_owned ( ) , || {
130
- open_zip ( p)
131
- } ) ;
132
-
133
- match res {
134
- Ok ( zip) => Ok ( cb ( zip) ) ,
135
- Err ( err) => Err ( & err) ,
157
+ impl Default for LruZipCache {
158
+ fn default ( ) -> LruZipCache {
159
+ LruZipCache :: new ( 50 )
160
+ }
161
+ }
162
+
163
+ impl LruZipCache {
164
+ pub fn new ( n : usize ) -> LruZipCache {
165
+ LruZipCache {
166
+ lru : LruCache :: new ( NonZeroUsize :: new ( n) . unwrap ( ) ) ,
136
167
}
137
168
}
138
169
}
139
170
140
- /*
141
- static resolveVirtual(p: PortablePath): PortablePath {
142
- const match = p.match(VIRTUAL_REGEXP);
143
- if (!match || (!match[3] && match[5]))
144
- return p;
171
+ impl ZipCache for LruZipCache {
172
+ fn act < T , F : FnOnce ( & mut Zip ) -> T > ( & mut self , p : & Path , cb : F ) -> Result < T , std:: io:: Error > {
173
+ if let Some ( zip) = self . lru . get_mut ( p) {
174
+ return Ok ( cb ( zip) ) ;
175
+ }
176
+
177
+ let zip = open_zip ( p) ?;
178
+ self . lru . put ( p. to_owned ( ) , zip) ;
179
+
180
+ Ok ( cb ( self . lru . get_mut ( p) . unwrap ( ) ) )
181
+ }
182
+
183
+ fn canonicalize ( & mut self , zip_path : & Path , sub : & str ) -> Result < PathBuf , std:: io:: Error > {
184
+ let res = std:: fs:: canonicalize ( zip_path) ?;
185
+
186
+ Ok ( res. join ( sub) )
187
+ }
145
188
146
- const target = ppath.dirname(match[1] as PortablePath);
147
- if (!match[3] || !match[4] )
148
- return target;
189
+ fn is_dir ( & mut self , zip_path : & Path , p : & str ) -> bool {
190
+ self . act ( zip_path , |zip| zip . is_dir ( p ) ) . unwrap_or ( false )
191
+ }
149
192
150
- const isnum = NUMBER_REGEXP.test(match[4]);
151
- if (!isnum )
152
- return p;
193
+ fn is_file ( & mut self , zip_path : & Path , p : & str ) -> bool {
194
+ self . act ( zip_path , |zip| zip . is_file ( p ) ) . unwrap_or ( false )
195
+ }
153
196
154
- const depth = Number(match[4]);
155
- const backstep = `../`.repeat(depth) as PortablePath;
156
- const subpath = (match[5] || `.`) as PortablePath;
197
+ fn read ( & mut self , zip_path : & Path , p : & str ) -> Result < Vec < u8 > , std :: io :: Error > {
198
+ self . act ( zip_path , |zip| zip . read ( p ) ) ?
199
+ }
157
200
158
- return VirtualFS.resolveVirtual(ppath.join(target, backstep, subpath));
159
- }
160
- */
201
+ fn read_to_string ( & mut self , zip_path : & Path , p : & str ) -> Result < String , std:: io:: Error > {
202
+ self . act ( zip_path, |zip| zip. read_to_string ( p) ) ?
203
+ }
204
+ }
161
205
162
- pub fn path_to_pnp ( p : & Path ) -> Result < PnpPath , Box < dyn std:: error :: Error > > {
206
+ pub fn vpath ( p : & Path ) -> Result < VPath , std:: io :: Error > {
163
207
lazy_static ! {
164
208
// $0: full path
165
209
// $1: virtual folder
@@ -171,24 +215,24 @@ pub fn path_to_pnp(p: &Path) -> Result<PnpPath, Box<dyn std::error::Error>> {
171
215
static ref ZIP_RE : Regex = Regex :: new( "\\ .zip" ) . unwrap( ) ;
172
216
}
173
217
174
- let p_str = p. as_os_str ( )
218
+ let mut p_str = p. as_os_str ( )
175
219
. to_string_lossy ( )
176
220
. to_string ( ) ;
177
221
178
- let mut p_bytes = util:: normalize_path ( p_str)
222
+ let mut p_bytes = util:: normalize_path ( p_str. clone ( ) )
179
223
. as_bytes ( ) . to_vec ( ) ;
180
224
181
225
if let Some ( m) = VIRTUAL_RE . captures ( & p_bytes) {
182
226
if let ( Some ( target) , Some ( depth) , Some ( subpath) ) = ( m. get ( 1 ) , m. get ( 4 ) , m. get ( 5 ) ) {
183
- if let Ok ( depth_n) = str:: parse ( & std :: str :: from_utf8 ( depth. as_bytes ( ) ) ?) {
227
+ if let Ok ( depth_n) = str:: parse ( io_bytes_to_str ( & depth. as_bytes ( ) ) ?) {
184
228
let bytes = [
185
229
& target. as_bytes ( ) ,
186
230
& b"../" . repeat ( depth_n) [ 0 ..] ,
187
231
& subpath. as_bytes ( ) ,
188
232
] . concat ( ) ;
189
233
190
- p_bytes = util:: normalize_path ( std :: str :: from_utf8 ( & bytes) ?)
191
- . as_bytes ( ) . to_vec ( ) ;
234
+ p_str = util:: normalize_path ( io_bytes_to_str ( & bytes) ?) ;
235
+ p_bytes = p_str . as_bytes ( ) . to_vec ( ) ;
192
236
}
193
237
}
194
238
}
@@ -203,7 +247,7 @@ pub fn path_to_pnp(p: &Path) -> Result<PnpPath, Box<dyn std::error::Error>> {
203
247
}
204
248
205
249
if idx == 0 || p_bytes. get ( idx - 1 ) == Some ( & b'/' ) {
206
- return Ok ( PnpPath :: Native ( p. to_owned ( ) ) )
250
+ return Ok ( VPath :: Native ( p. to_owned ( ) ) )
207
251
}
208
252
209
253
if let Some ( next_m) = ZIP_RE . find_at ( & p_bytes, next_char_idx) {
@@ -214,20 +258,20 @@ pub fn path_to_pnp(p: &Path) -> Result<PnpPath, Box<dyn std::error::Error>> {
214
258
}
215
259
216
260
if p_bytes. len ( ) > next_char_idx && p_bytes. get ( next_char_idx) != Some ( & b'/' ) {
217
- Ok ( PnpPath :: Native ( p . to_owned ( ) ) )
261
+ Ok ( VPath :: Native ( PathBuf :: from ( p_str ) ) )
218
262
} else {
219
- let zip_path = PathBuf :: from ( std :: str :: from_utf8 ( & p_bytes[ 0 ..next_char_idx] ) ?) ;
263
+ let zip_path = PathBuf :: from ( io_bytes_to_str ( & p_bytes[ 0 ..next_char_idx] ) ?) ;
220
264
221
265
let sub_path = if next_char_idx + 1 < p_bytes. len ( ) {
222
- Some ( util:: normalize_path ( std :: str :: from_utf8 ( & p_bytes[ next_char_idx + 1 ..] ) ?) )
266
+ util:: normalize_path ( io_bytes_to_str ( & p_bytes[ next_char_idx + 1 ..] ) ?)
223
267
} else {
224
- None
268
+ return Ok ( VPath :: Native ( zip_path ) )
225
269
} ;
226
270
227
- Ok ( PnpPath :: Zip ( zip_path, sub_path) )
271
+ Ok ( VPath :: Zip ( zip_path, sub_path) )
228
272
}
229
273
} else {
230
- Ok ( PnpPath :: Native ( p . to_owned ( ) ) )
274
+ Ok ( VPath :: Native ( PathBuf :: from ( p_str ) ) )
231
275
}
232
276
}
233
277
@@ -269,42 +313,41 @@ mod tests {
269
313
let mut zip = open_zip ( & PathBuf :: from ( "@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip" ) )
270
314
. unwrap ( ) ;
271
315
272
- let res = zip. read_file ( "node_modules/@babel/plugin-syntax-dynamic-import/package.json" )
273
- . unwrap ( ) ;
274
-
275
- let res_str = std:: str:: from_utf8 ( & res)
316
+ let res = zip. read_to_string ( "node_modules/@babel/plugin-syntax-dynamic-import/package.json" )
276
317
. unwrap ( ) ;
277
318
278
- assert_eq ! ( res_str , "{\n \" name\" : \" @babel/plugin-syntax-dynamic-import\" ,\n \" version\" : \" 7.8.3\" ,\n \" description\" : \" Allow parsing of import()\" ,\n \" repository\" : \" https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import\" ,\n \" license\" : \" MIT\" ,\n \" publishConfig\" : {\n \" access\" : \" public\" \n },\n \" main\" : \" lib/index.js\" ,\n \" keywords\" : [\n \" babel-plugin\" \n ],\n \" dependencies\" : {\n \" @babel/helper-plugin-utils\" : \" ^7.8.0\" \n },\n \" peerDependencies\" : {\n \" @babel/core\" : \" ^7.0.0-0\" \n },\n \" devDependencies\" : {\n \" @babel/core\" : \" ^7.8.0\" \n }\n }\n " ) ;
319
+ assert_eq ! ( res , "{\n \" name\" : \" @babel/plugin-syntax-dynamic-import\" ,\n \" version\" : \" 7.8.3\" ,\n \" description\" : \" Allow parsing of import()\" ,\n \" repository\" : \" https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-dynamic-import\" ,\n \" license\" : \" MIT\" ,\n \" publishConfig\" : {\n \" access\" : \" public\" \n },\n \" main\" : \" lib/index.js\" ,\n \" keywords\" : [\n \" babel-plugin\" \n ],\n \" dependencies\" : {\n \" @babel/helper-plugin-utils\" : \" ^7.8.0\" \n },\n \" peerDependencies\" : {\n \" @babel/core\" : \" ^7.0.0-0\" \n },\n \" devDependencies\" : {\n \" @babel/core\" : \" ^7.8.0\" \n }\n }\n " ) ;
279
320
}
280
321
281
322
#[ test]
282
323
fn test_path_to_pnp ( ) {
283
- let tests: Vec < ( PathBuf , Option < PnpPath > ) > = serde_json:: from_str ( r#"[
324
+ let tests: Vec < ( PathBuf , Option < VPath > ) > = serde_json:: from_str ( r#"[
284
325
[".zip", null],
285
326
["foo", null],
286
- ["foo.zip", [ "foo.zip", null] ],
327
+ ["foo.zip", "foo.zip"],
287
328
["foo.zip/bar", ["foo.zip", "bar"]],
288
329
["foo.zip/bar/baz", ["foo.zip", "bar/baz"]],
289
- ["/a/b/c/foo.zip", ["/a/b/c/foo.zip", null]],
290
- ["./a/b/c/foo.zip", ["a/b/c/foo.zip", null]],
291
- ["./a/b/__virtual__/abcdef/0/c/foo.zip", ["a/b/c/foo.zip", null]],
292
- ["./a/b/__virtual__/abcdef/1/c/foo.zip", ["a/c/foo.zip", null]],
330
+ ["/a/b/c/foo.zip", "/a/b/c/foo.zip"],
331
+ ["./a/b/c/foo.zip", "a/b/c/foo.zip"],
332
+ ["./a/b/__virtual__/abcdef/0/c/d", "a/b/c/d"],
333
+ ["./a/b/__virtual__/abcdef/1/c/d", "a/c/d"],
334
+ ["./a/b/__virtual__/abcdef/0/c/foo.zip/bar", ["a/b/c/foo.zip", "bar"]],
335
+ ["./a/b/__virtual__/abcdef/1/c/foo.zip/bar", ["a/c/foo.zip", "bar"]],
293
336
["./a/b/c/.zip", null],
294
337
["./a/b/c/foo.zipp", null],
295
338
["./a/b/c/foo.zip/bar/baz/qux.zip", ["a/b/c/foo.zip", "bar/baz/qux.zip"]],
296
- ["./a/b/c/foo.zip-bar.zip", [ "a/b/c/foo.zip-bar.zip", null] ],
339
+ ["./a/b/c/foo.zip-bar.zip", "a/b/c/foo.zip-bar.zip"],
297
340
["./a/b/c/foo.zip-bar.zip/bar/baz/qux.zip", ["a/b/c/foo.zip-bar.zip", "bar/baz/qux.zip"]],
298
341
["./a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip/d", ["a/b/c/foo.zip-bar/foo.zip-bar/foo.zip-bar.zip", "d"]]
299
342
]"# ) . expect ( "Assertion failed: Expected the expectations to be loaded" ) ;
300
343
301
344
for ( input, expected) in tests. iter ( ) {
302
- let expectation: PnpPath = match expected {
345
+ let expectation: VPath = match expected {
303
346
Some ( p) => p. clone ( ) ,
304
- None => PnpPath :: Native ( input. clone ( ) ) ,
347
+ None => VPath :: Native ( input. clone ( ) ) ,
305
348
} ;
306
349
307
- match path_to_pnp ( input) {
350
+ match vpath ( input) {
308
351
Ok ( res) => {
309
352
assert_eq ! ( res, expectation, "input='{:?}'" , input) ;
310
353
}
0 commit comments