Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add hash output file option to command #21

Merged
merged 9 commits into from
May 30, 2024
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ Cargo.lock
**/*.rs.bk

# IDE
.idea
.idea

# Build Files for paq.gif
demo.cast
5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "paq"
version = "1.0.1"
version = "1.1.0"
authors = ["Gregory Langlais <general@gregorylanglais.com>"]
edition = "2021"
description = "Hash file or directory recursively."
Expand Down Expand Up @@ -30,6 +30,9 @@ blake3 = "1.3.1"
rayon = "1.5"
clap = { version = "4.3.0", features = ["cargo", "unstable-styles"] }

[dev-dependencies]
assert_cmd = "2.0.11"

[features]
default = ["test-cleanup"]
test-cleanup = []
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ cargo install paq
1. Find [Latest Release](https://github.com/gregl83/paq/releases) `.zip` archive for computer Operating System and Architecture.
2. Download and extract `.zip`.
3. Modify permissions of the extracted `paq` binary to allow execution.
4. Move `paq` to system path.
4. Move `paq` to a system path.

## Usage

Expand All @@ -64,6 +64,8 @@ Included in this repository is an [example directory](./example) containing some

Run `paq [src]` to hash source file or directory.

Output hash to `.paq` file as valid JSON.

For help, run `paq --help`.

#### Hash Example Directory
Expand Down Expand Up @@ -124,9 +126,9 @@ Additionally, files or directory contents starting with dot or full stop *can* o
## How it Works

1. Recursively get path(s) for a given source argument.
2. Hash each path and file content if path is for a file.
3. Sort the list of hashes to maintain consistent ordering.
4. Compute the final hash by hashing the sorted list of hashes.
2. Hash each path and file contents if path is to a file.
3. Sort the list of hashes for consistent ordering.
4. Compute the final hash by hashing the list of hashes.

## License

Expand Down
3 changes: 3 additions & 0 deletions demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env bash

docker run --rm -ti -v .:/opt/paq -e "TERM=xterm-256color" -w /opt/paq rust /bin/bash -c "cargo install --path=. && cargo install --locked --git https://github.com/k9withabone/autocast && cargo install --locked --git https://github.com/asciinema/agg && cargo install --locked --git https://github.com/asciinema/agg && autocast --overwrite demo.yaml demo.cast && agg demo.cast paq.gif"
32 changes: 32 additions & 0 deletions demo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
settings:
title: paq demo
width: 90
height: 24
prompt: "\e[92m\e[1mubuntu\e[0m:\e[94m\e[1m/home/demo\e[0m$ "
environment:
- name: TERM
value: "xterm-256color"

instructions:
- !Command
command: paq -h
- !Wait 5s
- !Command
command: paq ./example
- !Wait 2s
- !Command
command: cd ./example
- !Command
command: paq
- !Wait 2s
- !Command
command: cd ..
- !Command
command: paq --ignore-hidden ./example
- !Wait 2s
- !Command
command: paq --ignore-hidden -o ./example
- !Wait 1s
- !Command
command: cat example.paq; echo
- !Wait 3s
Binary file modified paq.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 45 additions & 6 deletions src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
//! cargo run -- -h
//! ```

use std::path::PathBuf;
use std::path::{PathBuf};
use std::fs::File;
use std::io::{Error, Write};
use clap::{
crate_name,
crate_description,
Expand All @@ -22,7 +24,9 @@ use paq::hash_source;

#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct PathBufferValueParser {}
pub struct PathBufferValueParser {
validate_exists: bool
}

impl TypedValueParser for PathBufferValueParser {
type Value = PathBuf;
Expand All @@ -34,7 +38,7 @@ impl TypedValueParser for PathBufferValueParser {
value: &std::ffi::OsStr,
) -> Result<Self::Value, clap::Error> {
let path = PathBuf::from(value);
if !path.exists() {
if self.validate_exists && !path.exists() {
let mut err = clap::Error::new(ErrorKind::InvalidValue).with_cmd(cmd);
err.insert(
ContextKind::InvalidArg,
Expand All @@ -56,28 +60,63 @@ impl TypedValueParser for PathBufferValueParser {
}
}

fn derive_output_filepath(source: &PathBuf) -> PathBuf {
let source_canonical = source.canonicalize().unwrap();
let source_filename = source_canonical.file_name().unwrap().to_str().unwrap();
let mut path_buffer = PathBuf::from(source_canonical.parent().unwrap());
path_buffer.push(format!("{}.paq", source_filename));
path_buffer
}

fn write_hashfile(filepath: &PathBuf, hash: &str) -> Result<(), Error> {
let mut file = File::create(filepath).unwrap();
file.write_all(format!("\"{}\"", hash).as_bytes())
}

fn main() {
let output_default = "<src>.paq";
let matches = Command::new(crate_name!())
.version(crate_version!())
.about(crate_description!())
.allow_external_subcommands(false)
.arg(
Arg::new("src")
.help("Source to hash (filesystem path)")
.value_parser(PathBufferValueParser{})
.value_parser(PathBufferValueParser{validate_exists: true})
.default_value(".")
.help("Source to hash (filesystem path)")
)
.arg(
Arg::new("ignore-hidden")
.short('i')
.long("ignore-hidden")
.help("Ignore files or directories starting with dot or full stop")
.action(ArgAction::SetTrue)
.help("Ignore files or directories starting with dot or full stop")
)
.arg(
Arg::new("filepath")
.short('o')
.long("out")
.value_parser(PathBufferValueParser{validate_exists: false})
.require_equals(true)
.num_args(0..=1)
.default_missing_value(output_default)
.help(format!("Output hash (filesystem path) [default: {}]", output_default))
)
.after_help("Fails if operating system denies read access to any source file.")
.get_matches();

let source = matches.get_one::<PathBuf>("src").unwrap();
let ignore_hidden = matches.get_flag("ignore-hidden");
let output: Option<&PathBuf> = matches.get_one::<PathBuf>("filepath");
let hash = hash_source(&source, ignore_hidden);

if let Some(filepath) = output {
let output_filepath = match filepath.to_str().unwrap() {
s if s == output_default => derive_output_filepath(source),
_ => filepath.to_path_buf()
};
write_hashfile(&output_filepath, hash.as_str()).unwrap();
}

println!("{}", hash);
}
Loading