Skip to content

Commit

Permalink
Merge pull request #21 from gregl83/feature/13/add-file-out-option
Browse files Browse the repository at this point in the history
add hash output file option to command
  • Loading branch information
gregl83 committed May 30, 2024
2 parents 43f2e9a + 9efff8c commit 367d133
Show file tree
Hide file tree
Showing 9 changed files with 358 additions and 158 deletions.
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

0 comments on commit 367d133

Please sign in to comment.