Skip to content

Commit 0dced27

Browse files
authored
Use hierarchical manifest format (#90)
1 parent 3db1195 commit 0dced27

40 files changed

+1745
-1383
lines changed

Cargo.lock

Lines changed: 468 additions & 281 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,15 @@ serde_json = "1.0.127"
2929
serde_with = "3.11.0"
3030
serde_yaml = "0.9.34"
3131
snafu = "0.8.4"
32+
strum = { version = "0.27.2", features = ["derive"] }
33+
usized = "0.0.2"
3234
walkdir = "2.5.0"
3335

3436
[dev-dependencies]
3537
assert_cmd = { version = "2.0.16", features = ["color-auto"] }
3638
assert_fs = { version = "1.1.2", features = ["color-auto"] }
3739
predicates = "3.1.2"
40+
pretty_assertions = "1.4.1"
3841
regex = "1.10.6"
3942

4043
[lints.clippy]
@@ -51,6 +54,9 @@ too-many-lines = "allow"
5154
unused-async = "allow"
5255
wildcard-imports = "allow"
5356

57+
[lints.rust]
58+
mismatched-lifetime-syntaxes = "allow"
59+
5460
[profile.release]
5561
codegen-units = 1
5662
lto = true

DESIGN.md

Lines changed: 0 additions & 192 deletions
This file was deleted.

README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -175,17 +175,14 @@ drive prefix, such as `C:`.
175175
Paths may not contain the path components `.` or `..` and may not end with a
176176
slash.
177177

178-
Filepack has no way of tracking empty directories, the presence of which are an
179-
error when creating or verifying a manifest.
180-
181178
Manifests contain an object with one mandatory key, `files`.
182179

183180
### `files`
184181

185-
The value of the mandatory `files` key is an object mapping string paths to
186-
manifest entries. Manifest entries are objects with the key `hash`, whose value
187-
is a hex-encoded BLAKE3 hash of the file, and `size`, whose value is the length
188-
of the file in bytes.
182+
The value of the mandatory `files` key is an object mapping path components to
183+
directory entries. Directory entries may be subdirectories or files. Files are
184+
objects with keys `hash`, the hex-encoded BLAKE3 hash of the file, and `size`,
185+
the length of the file in bytes.
189186

190187
An example manifest for a directory containing the files `README.md` and
191188
`src/main.c`:
@@ -197,9 +194,11 @@ An example manifest for a directory containing the files `README.md` and
197194
"hash": "5a9a6d96244ec398545fc0c98c2cb7ed52511b025c19e9ad1e3c1ef4ac8575ad",
198195
"size": 1573
199196
},
200-
"src/main.c": {
201-
"hash": "38abf296dc2a90f66f7870fe0ce584af3859668cf5140c7557a76786189dcf0f",
202-
"size": 4491
197+
"src": {
198+
"main.c": {
199+
"hash": "38abf296dc2a90f66f7870fe0ce584af3859668cf5140c7557a76786189dcf0f",
200+
"size": 4491
201+
}
203202
}
204203
}
205204
}

bin/forbid

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
set -euxo pipefail
3+
set -euo pipefail
44

55
which rg
66

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
watch +args='ltest':
22
cargo watch --clear --exec '{{ args }}'
33

4-
clippy: (watch 'lclippy --all-targets -- --deny warnings')
4+
clippy: (watch 'lclippy --tests --all --all-targets -- --deny warnings')
55

66
ci: lint
77
cargo test --workspace

src/component.rs

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
use super::*;
2+
3+
#[derive(Clone, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
4+
#[serde(deny_unknown_fields, rename_all = "kebab-case", transparent)]
5+
pub(crate) struct Component(String);
6+
7+
impl Component {
8+
pub(crate) fn as_bytes(&self) -> &[u8] {
9+
self.0.as_bytes()
10+
}
11+
12+
pub(crate) fn as_str(&self) -> &str {
13+
&self.0
14+
}
15+
}
16+
17+
impl Borrow<str> for Component {
18+
fn borrow(&self) -> &str {
19+
self.0.as_str()
20+
}
21+
}
22+
23+
impl FromStr for Component {
24+
type Err = PathError;
25+
26+
fn from_str(s: &str) -> Result<Self, Self::Err> {
27+
for character in s.chars() {
28+
if SEPARATORS.contains(&character) {
29+
return Err(PathError::Separator { character });
30+
}
31+
}
32+
33+
if s == ".." || s == "." {
34+
return Err(PathError::Component {
35+
component: s.into(),
36+
});
37+
}
38+
39+
if s.is_empty() {
40+
return Err(PathError::ComponentEmpty);
41+
}
42+
43+
Ok(Self(s.into()))
44+
}
45+
}
46+
47+
#[cfg(test)]
48+
mod tests {
49+
use super::*;
50+
51+
#[test]
52+
fn parent() {
53+
assert_eq!(
54+
"..".parse::<Component>().unwrap_err(),
55+
PathError::Component {
56+
component: "..".into(),
57+
},
58+
);
59+
}
60+
61+
#[test]
62+
fn current() {
63+
assert_eq!(
64+
".".parse::<Component>().unwrap_err(),
65+
PathError::Component {
66+
component: ".".into(),
67+
},
68+
);
69+
}
70+
71+
#[test]
72+
fn empty() {
73+
assert_eq!(
74+
"".parse::<Component>().unwrap_err(),
75+
PathError::ComponentEmpty,
76+
);
77+
}
78+
79+
#[test]
80+
fn separator() {
81+
assert_eq!(
82+
"/".parse::<Component>().unwrap_err(),
83+
PathError::Separator { character: '/' },
84+
);
85+
86+
assert_eq!(
87+
"\\".parse::<Component>().unwrap_err(),
88+
PathError::Separator { character: '\\' },
89+
);
90+
}
91+
}

src/context.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
use super::*;
2+
3+
#[derive(Clone, Copy, IntoStaticStr)]
4+
#[strum(serialize_all = "kebab-case")]
5+
pub(crate) enum Context {
6+
Directory,
7+
Entry,
8+
File,
9+
}
10+
11+
impl Context {
12+
fn name(self) -> &'static str {
13+
self.into()
14+
}
15+
}
16+
17+
impl Display for Context {
18+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
19+
write!(f, "{}", self.name())
20+
}
21+
}

0 commit comments

Comments
 (0)