diff --git a/.gitignore b/.gitignore index 879d784..9e537f8 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,7 @@ Cargo.lock **/*.rs.bk # IDE -.idea \ No newline at end of file +.idea + +# Build Files for paq.gif +demo.cast \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index d7c9d5e..11cbcf1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "paq" -version = "1.0.1" +version = "1.1.0" authors = ["Gregory Langlais "] edition = "2021" description = "Hash file or directory recursively." @@ -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 = [] \ No newline at end of file diff --git a/README.md b/README.md index bc9e8c1..3e89f1f 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 @@ -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 diff --git a/demo.sh b/demo.sh new file mode 100644 index 0000000..0b1512f --- /dev/null +++ b/demo.sh @@ -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" \ No newline at end of file diff --git a/demo.yaml b/demo.yaml new file mode 100644 index 0000000..f49fff4 --- /dev/null +++ b/demo.yaml @@ -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 diff --git a/paq.gif b/paq.gif index f8620fb..c315041 100644 Binary files a/paq.gif and b/paq.gif differ diff --git a/src/bin.rs b/src/bin.rs index e4be639..e528eed 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -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, @@ -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; @@ -34,7 +38,7 @@ impl TypedValueParser for PathBufferValueParser { value: &std::ffi::OsStr, ) -> Result { 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, @@ -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 = ".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::("src").unwrap(); let ignore_hidden = matches.get_flag("ignore-hidden"); + let output: Option<&PathBuf> = matches.get_one::("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); } \ No newline at end of file diff --git a/tests/functional.rs b/tests/functional.rs index 78fad0e..da67ee3 100644 --- a/tests/functional.rs +++ b/tests/functional.rs @@ -1,173 +1,285 @@ -use std::env; -use std::path::PathBuf; - mod utils; -use utils::TempDir; - -#[test] -fn it_hashes_single_file() { - let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; - - let file_name = "alpha"; - let file_contents = "alpha-body".as_bytes(); - let dir = TempDir::new("it_hashes_single_file").unwrap(); - dir.new_file(file_name, file_contents).unwrap(); - let source = dir.path().join(file_name); - - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_not_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_not_ignored[..], expectation); -} -#[test] -fn it_hashes_directory() { - let expectation = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; +mod lib { + use std::env; + use std::path::PathBuf; + use crate::utils::TempDir; - let dir = TempDir::new("it_hashes_directory").unwrap(); - let source = dir.path().canonicalize().unwrap(); + #[test] + fn it_hashes_single_file() { + let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_not_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_not_ignored[..], expectation); -} + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let dir = TempDir::new("it_hashes_single_file").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().join(file_name); -#[test] -fn it_hashes_directory_from_any_path() { - let expectation = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_not_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_not_ignored[..], expectation); + } - let dir = TempDir::new("it_hashes_directory_from_any_path").unwrap(); - let source = dir.path().canonicalize().unwrap(); - let original_path = env::current_dir().unwrap(); - let new_path = PathBuf::from("/"); + #[test] + fn it_hashes_directory() { + let expectation = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_ignored[..], expectation); + let dir = TempDir::new("it_hashes_directory").unwrap(); + let source = dir.path().canonicalize().unwrap(); - env::set_current_dir(new_path).unwrap(); + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_not_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_not_ignored[..], expectation); + } - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_ignored[..], expectation); + #[test] + fn it_hashes_directory_from_any_path() { + let expectation = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; - env::set_current_dir(original_path).unwrap(); -} + let dir = TempDir::new("it_hashes_directory_from_any_path").unwrap(); + let source = dir.path().canonicalize().unwrap(); + let original_path = env::current_dir().unwrap(); + let new_path = PathBuf::from("/"); -#[cfg(target_family = "unix")] -#[test] -fn it_hashes_directory_relative_symlink_without_following() { - let expectation = "5bb837eff87dee38d63c081bc30a8d0ce7cc871c8b32e38e3e40f9ccdef4db98"; - - let symlink_name = "symlink"; - let symlink_target = PathBuf::from("target"); - let dir = TempDir::new("it_hashes_directory_relative_symlink_without_following").unwrap(); - dir.new_symlink(symlink_name, symlink_target).unwrap(); - let source = dir.path().canonicalize().unwrap(); - - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_ignored[..], expectation); -} + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_ignored[..], expectation); -#[cfg(target_family = "unix")] -#[test] -fn it_hashes_directory_absolute_symlink_without_following() { - let expectation = "60fbb028703074fe8b17a20155696f6dffab6a3de071b716792350f946d917aa"; - - let symlink_name = "symlink"; - let symlink_target = PathBuf::from("/"); - let dir = TempDir::new("it_hashes_directory_absolute_symlink_without_following").unwrap(); - dir.new_symlink(symlink_name, symlink_target).unwrap(); - let source = dir.path().canonicalize().unwrap(); - - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_ignored[..], expectation); -} + env::set_current_dir(new_path).unwrap(); -#[test] -fn it_hashes_directory_with_file() { - let expectation = "7ed5febd35e277763cdfc3e4bee136acf38e48e9462972a732cc4d348a37d653"; + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_ignored[..], expectation); - let file_name = "alpha"; - let file_contents = "alpha-body".as_bytes(); - let dir = TempDir::new("it_hashes_directory_with_file").unwrap(); - dir.new_file(file_name, file_contents).unwrap(); - let source = dir.path().canonicalize().unwrap(); + env::set_current_dir(original_path).unwrap(); + } - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation); - let hash_not_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_not_ignored[..], expectation); -} + #[cfg(target_family = "unix")] + #[test] + fn it_hashes_directory_relative_symlink_without_following() { + let expectation = "5bb837eff87dee38d63c081bc30a8d0ce7cc871c8b32e38e3e40f9ccdef4db98"; -#[test] -fn it_hashes_directory_with_ignored_file() { - let expectation_not_ignored = "e383192a5ef45576817b4222e455e3d538ae3bab279a62c0a8b67279ad007072"; - let expectation_ignored = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; - - let file_name = ".ignored"; - let file_contents = ".ignored-body".as_bytes(); - let dir = TempDir::new("it_hashes_directory_with_ignored_file").unwrap(); - dir.new_file(file_name, file_contents).unwrap(); - let source = dir.path().canonicalize().unwrap(); - - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation_ignored); - let hash_not_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_not_ignored[..], expectation_not_ignored); -} + let symlink_name = "symlink"; + let symlink_target = PathBuf::from("target"); + let dir = TempDir::new("it_hashes_directory_relative_symlink_without_following").unwrap(); + dir.new_symlink(symlink_name, symlink_target).unwrap(); + let source = dir.path().canonicalize().unwrap(); -#[test] -fn it_hashes_directory_with_ignored_subdirectory() { - let expectation_not_ignored = "f38a56a87aca98131b2fa5914fd13bc11f5823602293e8d84b5c69000b33ebf2"; - let expectation_ignored = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_ignored[..], expectation); + } - let dir = TempDir::new("it_hashes_directory_with_ignored_subdirectory").unwrap(); - let source = dir.path().canonicalize().unwrap(); + #[cfg(target_family = "unix")] + #[test] + fn it_hashes_directory_absolute_symlink_without_following() { + let expectation = "60fbb028703074fe8b17a20155696f6dffab6a3de071b716792350f946d917aa"; - let subdir = TempDir::new("it_hashes_directory_with_ignored_subdirectory/.test").unwrap(); - let hash_ignored = paq::hash_source(&source, true); - assert_eq!(&hash_ignored[..], expectation_ignored); - let hash_not_ignored = paq::hash_source(&source, false); - assert_eq!(&hash_not_ignored[..], expectation_not_ignored); + let symlink_name = "symlink"; + let symlink_target = PathBuf::from("/"); + let dir = TempDir::new("it_hashes_directory_absolute_symlink_without_following").unwrap(); + dir.new_symlink(symlink_name, symlink_target).unwrap(); + let source = dir.path().canonicalize().unwrap(); - println!("prevent early subdir drop for: {}", subdir.path().as_os_str().to_str().unwrap()) -} + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_ignored[..], expectation); + } + + #[test] + fn it_hashes_directory_with_file() { + let expectation = "7ed5febd35e277763cdfc3e4bee136acf38e48e9462972a732cc4d348a37d653"; + + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let dir = TempDir::new("it_hashes_directory_with_file").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().canonicalize().unwrap(); -#[test] -fn it_hashes_directory_files_consistently() { - let expectation = "59a0db8e557830ccb77ac0e4556931925cdc592a1a8b83e1bdc3c8da406f4ef5"; - - let alpha_file_name = "alpha"; - let alpha_file_contents = "alpha-body".as_bytes(); - let bravo_file_name = "bravo"; - let bravo_file_contents = "bravo-body".as_bytes(); - let charlie_file_name = "charlie"; - let charlie_file_contents = "charlie-body".as_bytes(); - let one_file_name = "1"; - let one_file_contents = "1-body".as_bytes(); - let nine_file_name = "9"; - let nine_file_contents = "9-body".as_bytes(); - - let dir = TempDir::new("it_hashes_directory_files_consistently").unwrap(); - dir.new_file(alpha_file_name, alpha_file_contents).unwrap(); - dir.new_file(bravo_file_name, bravo_file_contents).unwrap(); - dir.new_file(charlie_file_name, charlie_file_contents).unwrap(); - dir.new_file(one_file_name, one_file_contents).unwrap(); - dir.new_file(nine_file_name, nine_file_contents).unwrap(); - let source = dir.path().canonicalize().unwrap(); - - for _ in 0..50 { let hash_ignored = paq::hash_source(&source, true); assert_eq!(&hash_ignored[..], expectation); let hash_not_ignored = paq::hash_source(&source, false); assert_eq!(&hash_not_ignored[..], expectation); } + + #[test] + fn it_hashes_directory_with_ignored_file() { + let expectation_not_ignored = "e383192a5ef45576817b4222e455e3d538ae3bab279a62c0a8b67279ad007072"; + let expectation_ignored = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; + + let file_name = ".ignored"; + let file_contents = ".ignored-body".as_bytes(); + let dir = TempDir::new("it_hashes_directory_with_ignored_file").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().canonicalize().unwrap(); + + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation_ignored); + let hash_not_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_not_ignored[..], expectation_not_ignored); + } + + #[test] + fn it_hashes_directory_with_ignored_subdirectory() { + let expectation_not_ignored = "f38a56a87aca98131b2fa5914fd13bc11f5823602293e8d84b5c69000b33ebf2"; + let expectation_ignored = "82878ed8a480ee41775636820e05a934ca5c747223ca64306658ee5982e6c227"; + + let dir = TempDir::new("it_hashes_directory_with_ignored_subdirectory").unwrap(); + let source = dir.path().canonicalize().unwrap(); + + let subdir = TempDir::new("it_hashes_directory_with_ignored_subdirectory/.test").unwrap(); + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation_ignored); + let hash_not_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_not_ignored[..], expectation_not_ignored); + + println!("prevent early subdir drop for: {}", subdir.path().as_os_str().to_str().unwrap()) + } + + #[test] + fn it_hashes_directory_files_consistently() { + let expectation = "59a0db8e557830ccb77ac0e4556931925cdc592a1a8b83e1bdc3c8da406f4ef5"; + + let alpha_file_name = "alpha"; + let alpha_file_contents = "alpha-body".as_bytes(); + let bravo_file_name = "bravo"; + let bravo_file_contents = "bravo-body".as_bytes(); + let charlie_file_name = "charlie"; + let charlie_file_contents = "charlie-body".as_bytes(); + let one_file_name = "1"; + let one_file_contents = "1-body".as_bytes(); + let nine_file_name = "9"; + let nine_file_contents = "9-body".as_bytes(); + + let dir = TempDir::new("it_hashes_directory_files_consistently").unwrap(); + dir.new_file(alpha_file_name, alpha_file_contents).unwrap(); + dir.new_file(bravo_file_name, bravo_file_contents).unwrap(); + dir.new_file(charlie_file_name, charlie_file_contents).unwrap(); + dir.new_file(one_file_name, one_file_contents).unwrap(); + dir.new_file(nine_file_name, nine_file_contents).unwrap(); + let source = dir.path().canonicalize().unwrap(); + + for _ in 0..50 { + let hash_ignored = paq::hash_source(&source, true); + assert_eq!(&hash_ignored[..], expectation); + let hash_not_ignored = paq::hash_source(&source, false); + assert_eq!(&hash_not_ignored[..], expectation); + } + } +} + +mod bin { + use std::path::PathBuf; + use assert_cmd::Command; + use crate::utils::TempDir; + + #[test] + fn it_outputs_file_hash_using_default_short_arg() { + let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; + + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let hash_file_name = "alpha.paq"; + let dir = TempDir::new("it_outputs_file_hash_using_default_short_arg").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().join(file_name); + + let mut cmd = Command::cargo_bin("paq").unwrap(); + let assert = cmd + .arg(format!("{}", source.as_os_str().to_str().unwrap())) + .arg("-o") + .assert(); + assert + .code(0) + .stdout(format!("{}\n", expectation)) + .success(); + + let file_hash = dir.read_file(hash_file_name).unwrap(); + assert_eq!(file_hash.as_slice(), format!("\"{}\"", expectation).as_bytes()); + } + + #[test] + fn it_outputs_file_hash_using_short_arg() { + let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; + + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let hash_file_name = "custom.paq"; + let dir = TempDir::new("it_outputs_file_hash_using_short_arg").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().join(file_name); + let mut output = PathBuf::from(&source).parent().unwrap().to_path_buf(); + output.push(hash_file_name); + + let mut cmd = Command::cargo_bin("paq").unwrap(); + let assert = cmd + .arg(format!("{}", source.as_os_str().to_str().unwrap())) + .arg(format!("-o={}", output.as_os_str().to_str().unwrap())) + .assert(); + assert + .code(0) + .stdout(format!("{}\n", expectation)) + .success(); + + let file_hash = dir.read_file(hash_file_name).unwrap(); + assert_eq!(file_hash.as_slice(), format!("\"{}\"", expectation).as_bytes()); + } + + #[test] + fn it_outputs_file_hash_using_default_long_arg() { + let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; + + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let hash_file_name = "alpha.paq"; + let dir = TempDir::new("it_outputs_file_hash_using_default_long_arg").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().join(file_name); + + let mut cmd = Command::cargo_bin("paq").unwrap(); + let assert = cmd + .arg(format!("{}", source.as_os_str().to_str().unwrap())) + .arg("--out") + .assert(); + assert + .code(0) + .stdout(format!("{}\n", expectation)) + .success(); + + let file_hash = dir.read_file(hash_file_name).unwrap(); + assert_eq!(file_hash.as_slice(), format!("\"{}\"", expectation).as_bytes()); + } + + #[test] + fn it_outputs_file_hash_using_long_arg() { + let expectation = "48ec422c86fd2aa1ac182f832c10cf6cb07e4b89d88b83a7794bd8773460072c"; + + let file_name = "alpha"; + let file_contents = "alpha-body".as_bytes(); + let hash_file_name = "custom.paq"; + let dir = TempDir::new("it_outputs_file_hash_using_long_arg").unwrap(); + dir.new_file(file_name, file_contents).unwrap(); + let source = dir.path().join(file_name); + let mut output = PathBuf::from(&source).parent().unwrap().to_path_buf(); + output.push(hash_file_name); + + let mut cmd = Command::cargo_bin("paq").unwrap(); + let assert = cmd + .arg(format!("{}", source.as_os_str().to_str().unwrap())) + .arg(format!("--out={}", output.as_os_str().to_str().unwrap())) + .assert(); + assert + .code(0) + .stdout(format!("{}\n", expectation)) + .success(); + + let file_hash = dir.read_file(hash_file_name).unwrap(); + assert_eq!(file_hash.as_slice(), format!("\"{}\"", expectation).as_bytes()); + } } \ No newline at end of file diff --git a/tests/utils.rs b/tests/utils.rs index 2fb1bb4..c6c7a5d 100644 --- a/tests/utils.rs +++ b/tests/utils.rs @@ -61,6 +61,12 @@ impl TempDir { Ok(fs::write(file_path.as_os_str(), data).expect("Unable to write file")) } + /// Read a file in temporary directory. + pub fn read_file(&self, name: &str) -> Result> { + let file_path = PathBuf::from(format!("{}/{}", self.path().display(), name)); + Ok(fs::read(file_path.as_os_str()).expect("Unable to read file")) + } + /// Create a new symlink in temporary directory to target. #[cfg(target_family = "unix")] pub fn new_symlink(&self, name: &str, target: PathBuf) -> Result<()> {