Skip to content

Commit 13f952d

Browse files
committed
wip
1 parent 39e4ed0 commit 13f952d

File tree

3 files changed

+111
-70
lines changed

3 files changed

+111
-70
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
[package]
22
name = "pnp"
3-
version = "0.2.0"
3+
version = "0.3.0"
44
edition = "2021"
55
license = "BSD-2-Clause"
66
description = "Resolution primitives for Yarn PnP"
77
homepage = "https://yarnpkg.com"
88
repository = "https://github.com/yarnpkg/berry/"
99

10-
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
11-
1210
[dependencies]
1311
clean-path = "0.2.1"
1412
fancy-regex = "0.11.0"

src/fs.rs

Lines changed: 109 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use lazy_static::lazy_static;
22
use lru::LruCache;
33
use regex::bytes::Regex;
44
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};
66
use zip::{ZipArchive, result::ZipError};
77

88
use crate::util;
@@ -12,8 +12,8 @@ use crate::util;
1212
#[derive(Deserialize)]
1313
#[derive(PartialEq)]
1414
#[serde(untagged)]
15-
pub enum PnpPath {
16-
Zip(PathBuf, Option<String>),
15+
pub enum VPath {
16+
Zip(PathBuf, String),
1717
Native(PathBuf),
1818
}
1919

@@ -28,19 +28,35 @@ pub enum Error {
2828
#[error("Decompression error")]
2929
DecompressionError,
3030

31+
#[error(transparent)]
32+
Utf8Error(#[from] Utf8Error),
33+
3134
#[error(transparent)]
3235
IOError(#[from] std::io::Error),
3336

3437
#[error(transparent)]
3538
ZipError(#[from] ZipError),
3639
}
3740

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> {
3954
let file = fs::File::open(p)?;
4055
let reader = BufReader::new(file);
4156

4257
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"))?;
4460

4561
Ok(zip)
4662
}
@@ -88,10 +104,10 @@ impl Zip {
88104
self.files.contains_key(p)
89105
}
90106

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> {
92108
let i = self.files.get(p)
93-
.ok_or(Error::EntryNotFound)?;
94-
109+
.ok_or(std::io::Error::from(std::io::ErrorKind::NotFound))?;
110+
95111
let mut entry = self.archive.by_index_raw(*i)?;
96112
let mut data = Vec::new();
97113

@@ -100,7 +116,7 @@ impl Zip {
100116
match entry.compression() {
101117
zip::CompressionMethod::DEFLATE => {
102118
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"))?;
104120

105121
Ok(decompressed_data)
106122
}
@@ -110,56 +126,84 @@ impl Zip {
110126
}
111127

112128
_ => {
113-
Err(Error::UnsupportedCompression)
129+
Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "Unsupported compression algorithm"))
114130
}
115131
}
116132
}
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+
}
117139
}
118140

119141
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>;
121151
}
122152

123153
pub struct LruZipCache {
124-
lru: LruCache<PathBuf, Result<Zip, Error>>,
154+
lru: LruCache<PathBuf, Zip>,
125155
}
126156

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()),
136167
}
137168
}
138169
}
139170

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+
}
145188

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+
}
149192

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+
}
153196

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+
}
157200

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+
}
161205

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> {
163207
lazy_static! {
164208
// $0: full path
165209
// $1: virtual folder
@@ -171,24 +215,24 @@ pub fn path_to_pnp(p: &Path) -> Result<PnpPath, Box<dyn std::error::Error>> {
171215
static ref ZIP_RE: Regex = Regex::new("\\.zip").unwrap();
172216
}
173217

174-
let p_str = p.as_os_str()
218+
let mut p_str = p.as_os_str()
175219
.to_string_lossy()
176220
.to_string();
177221

178-
let mut p_bytes = util::normalize_path(p_str)
222+
let mut p_bytes = util::normalize_path(p_str.clone())
179223
.as_bytes().to_vec();
180224

181225
if let Some(m) = VIRTUAL_RE.captures(&p_bytes) {
182226
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())?) {
184228
let bytes = [
185229
&target.as_bytes(),
186230
&b"../".repeat(depth_n)[0..],
187231
&subpath.as_bytes(),
188232
].concat();
189233

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();
192236
}
193237
}
194238
}
@@ -203,7 +247,7 @@ pub fn path_to_pnp(p: &Path) -> Result<PnpPath, Box<dyn std::error::Error>> {
203247
}
204248

205249
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()))
207251
}
208252

209253
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>> {
214258
}
215259

216260
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)))
218262
} 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])?);
220264

221265
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..])?)
223267
} else {
224-
None
268+
return Ok(VPath::Native(zip_path))
225269
};
226270

227-
Ok(PnpPath::Zip(zip_path, sub_path))
271+
Ok(VPath::Zip(zip_path, sub_path))
228272
}
229273
} else {
230-
Ok(PnpPath::Native(p.to_owned()))
274+
Ok(VPath::Native(PathBuf::from(p_str)))
231275
}
232276
}
233277

@@ -269,42 +313,41 @@ mod tests {
269313
let mut zip = open_zip(&PathBuf::from("@babel-plugin-syntax-dynamic-import-npm-7.8.3-fb9ff5634a-8.zip"))
270314
.unwrap();
271315

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")
276317
.unwrap();
277318

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");
279320
}
280321

281322
#[test]
282323
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#"[
284325
[".zip", null],
285326
["foo", null],
286-
["foo.zip", ["foo.zip", null]],
327+
["foo.zip", "foo.zip"],
287328
["foo.zip/bar", ["foo.zip", "bar"]],
288329
["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"]],
293336
["./a/b/c/.zip", null],
294337
["./a/b/c/foo.zipp", null],
295338
["./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"],
297340
["./a/b/c/foo.zip-bar.zip/bar/baz/qux.zip", ["a/b/c/foo.zip-bar.zip", "bar/baz/qux.zip"]],
298341
["./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"]]
299342
]"#).expect("Assertion failed: Expected the expectations to be loaded");
300343

301344
for (input, expected) in tests.iter() {
302-
let expectation: PnpPath = match expected {
345+
let expectation: VPath = match expected {
303346
Some(p) => p.clone(),
304-
None => PnpPath::Native(input.clone()),
347+
None => VPath::Native(input.clone()),
305348
};
306349

307-
match path_to_pnp(input) {
350+
match vpath(input) {
308351
Ok(res) => {
309352
assert_eq!(res, expectation, "input='{:?}'", input);
310353
}

0 commit comments

Comments
 (0)