Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ tempfile = "3.24.0"
[lints.clippy]
all = { level = "deny", priority = -1 }
arbitrary-source-item-ordering = "deny"
enum-glob-use = "allow"
float-cmp = "allow"
large_enum_variant = "allow"
missing-errors-doc = "allow"
Expand Down
73 changes: 40 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ hash function.
A manifest named `filepack.json` containing the hashes of files in a directory
can be created with:

```sh
```shell
filepack create path/to/directory
```

Which will write the manifest to `path/to/directory/filepack.json`.

Files can later be verified with:

```sh
```shell
filepack verify path/to/directory
```

Expand Down Expand Up @@ -115,42 +115,33 @@ for information about a particular subcommand.

Create a manifest.

Optional path portability lints can be enabled with:
Recommended lints can be enabled with:

```sh
filepack create --deny all
```

Metadata can optionally be included in the manifest with:

```sh
filepack create --metadata <PATH>
```shell
filepack create --deny distribution
```

Where `<PATH>` is a [YAML](https://en.wikipedia.org/wiki/YAML) document
containing metadata with the same schema as that of the manifest.

### `filepack verify`

Verify the contents of a directory against a manifest.

To verify the contents of `DIR` against `DIR/filepack.json`:

```sh
```shell
filepack verify DIR
```

If the current directory contains `filepack.json`, `DIR` can be omitted:

```sh
```shell
filepack verify
```

`filepack verify` takes an optional `--print` flag, which prints the manifest
to standard output if verification succeeds. This can be used in a pipeline to
ensure that you the manifest has been verified before proceeding:

```sh
```shell
filepack verify --print | jq
```

Expand Down Expand Up @@ -234,33 +225,49 @@ The signature is a 128 character hexidecimal string and is elided for brevity.
Metadata
--------

`filepack create` can optionally write metadata describing the contents of the
package to a file named `metadata.json`, containing a JSON object with the
Filepack packages may optionally contain metadata describing the contents of
the packages in a file named `metadata.yaml` containing a YAML object with the
following keys:

- `title`: A string containing the package's human-readable title.

An example `metadata.json`:
Metadata follows a fixed schema and is not user-extensible. Future version of
`filepack` may define new metadata fields, causing verification errors if those
fields are present and invalid according to the new schema.

```js
{
"title": "Tobin's Spirit Guide"
}
`filepack create` loads `metadata.yaml` if present and checks for validity and
unknown fields.

`filepack verify` also loads `metadata.yaml` if present and checks for
validity. Unknown fields, however, are not an error, so that future versions of
`filepack` may define new metadata fields in a backwards-compatible fashion.

An example `metadata.yaml`:

```yaml
title: Tobin's Spirit Guide
```

Lints
-----

`filepack create` supports optional lints that can be enabled with:
`filepack create` supports optional lints that can be enabled by group:

```
filepack create --deny all
```shell
filepack create --deny distribution
```

These lints cover issues such as non-portable paths which are illegal on
Windows file systems, paths which would conflict on case-insensitive file
The `distribution` lint group checks for issues which can cause problems if the
package is indended for distribution, such as non-portable paths that are
illegal on Windows, paths which would conflict on case-insensitive file
systems, and inclusion of junk files such as `.DS_Store`.

Lint group names and the lints they cover can be printed with:

```shell
filepack lints
```

Keys and Signatures
-------------------

Expand All @@ -273,7 +280,7 @@ and the creation and verification of

Keypairs are generated with:

```
```shell
filepack keygen
```

Expand All @@ -293,15 +300,15 @@ location is platform-dependent:

Generated public keys can be printed with:

```
```shell
filepack key
```

### Signing

Signatures are created with:

```
```shell
filepack sign
```

Expand All @@ -314,7 +321,7 @@ fingerprint hash, recursively calculated from the contents of the manifest.
Signatures embedded in a manifest are verified whenever a manifest is verified.
The presence of a signature by a particular public key can be asserted with:

```
```sh
filepack verify --key PUBLIC_KEY
```

Expand Down
2 changes: 1 addition & 1 deletion bin/package
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ cp -r \
dist

echo "Creating filepack manifest..."
cargo run create --deny all dist
cargo run create --deny distribution dist

echo "Creating release archive..."
cd dist
Expand Down
20 changes: 20 additions & 0 deletions src/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,23 @@ impl Arguments {
self.subcommand.run(self.options)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn commands_in_readme_are_valid() {
let readme = filesystem::read_to_string("README.md").unwrap();

let re = Regex::new(r"(?s)```shell(.*?)```").unwrap();

for capture in re.captures_iter(&readme) {
let command = capture[1].split('|').next().unwrap();
assert!(
Arguments::try_parse_from(command.split_whitespace()).is_ok(),
"bad filepack command in readme: {command}",
);
}
}
}
8 changes: 4 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ use {
key_name::KeyName,
key_type::KeyType,
keychain::Keychain,
lint::Lint,
lint_error::{Lint, LintError},
lint_group::LintGroup,
message::Message,
metadata::Metadata,
Expand Down Expand Up @@ -77,7 +77,7 @@ use {
sync::LazyLock,
time::{SystemTime, SystemTimeError, UNIX_EPOCH},
},
strum::{EnumIter, EnumString, IntoStaticStr},
strum::{EnumDiscriminants, EnumIter, EnumString, IntoEnumIterator, IntoStaticStr},
usized::IntoU64,
walkdir::WalkDir,
};
Expand All @@ -89,7 +89,7 @@ pub use self::{
};

#[cfg(test)]
use {std::collections::HashSet, strum::IntoEnumIterator};
use {std::collections::HashSet, strum::IntoDiscriminant};

#[cfg(test)]
fn tempdir() -> tempfile::TempDir {
Expand Down Expand Up @@ -121,7 +121,7 @@ mod key_identifier;
mod key_name;
mod key_type;
mod keychain;
mod lint;
mod lint_error;
mod lint_group;
mod manifest;
mod message;
Expand Down
24 changes: 22 additions & 2 deletions src/lint.rs → src/lint_error.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
use super::*;

#[derive(Debug, PartialEq, Snafu)]
pub(crate) enum Lint {
#[derive(Debug, EnumDiscriminants, PartialEq, Snafu)]
#[strum_discriminants(
name(Lint),
derive(EnumIter, IntoStaticStr, Ord, PartialOrd, Serialize),
serde(rename_all = "kebab-case"),
strum(serialize_all = "kebab-case")
)]
pub(crate) enum LintError {
#[snafu(display("paths would conflict on case-insensitive filesystem"))]
CaseConflict,
#[snafu(display("many filesystems do not allow filenames longer than 255 bytes"))]
FilenameLength,
#[snafu(display("possible junk file"))]
Expand All @@ -17,3 +25,15 @@ pub(crate) enum Lint {
#[snafu(display("Windows does not allow filenames that end with a space"))]
WindowsTrailingSpace,
}

impl Lint {
fn name(self) -> &'static str {
self.into()
}
}

impl Display for Lint {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.name())
}
}
45 changes: 43 additions & 2 deletions src/lint_group.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,47 @@
use super::*;

#[derive(Clone, ValueEnum)]
#[derive(Clone, Copy, EnumIter, Eq, Ord, PartialEq, PartialOrd, Serialize, ValueEnum)]
#[serde(rename_all = "kebab-case")]
pub(crate) enum LintGroup {
All,
Compatibility,
Distribution,
Junk,
}

impl LintGroup {
pub(crate) fn lints(self) -> BTreeSet<Lint> {
use Lint::*;

match self {
Self::Compatibility => [
CaseConflict,
FilenameLength,
WindowsLeadingSpace,
WindowsReservedCharacter,
WindowsReservedFilename,
WindowsTrailingPeriod,
WindowsTrailingSpace,
]
.into(),
Self::Distribution => &Self::Junk.lints() | &Self::Compatibility.lints(),
Self::Junk => [Junk].into(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn all_lints_are_in_at_least_one_group() {
let mut lints = BTreeSet::new();
for group in LintGroup::iter() {
lints.append(&mut group.lints());
}

for lint in Lint::iter() {
assert!(lints.contains(&lint), "lint {lint} not in group");
}
}
}
11 changes: 11 additions & 0 deletions src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ mod tests {
Metadata::deserialize(Metadata::FILENAME.as_ref(), UNKNOWN_FIELD).unwrap();
}

#[test]
fn metadata_in_readme_is_valid() {
let readme = filesystem::read_to_string("README.md").unwrap();

let re = Regex::new(r"(?s)```yaml(.*?)```").unwrap();

for capture in re.captures_iter(&readme) {
Metadata::deserialize_strict("README.md".as_ref(), &capture[1]).unwrap();
}
}

#[test]
fn strict_deserialize_rejects_unknown_fields() {
assert_eq!(
Expand Down
Loading