diff --git a/Cargo.lock b/Cargo.lock index 523c23c..fd23fd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -380,6 +380,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "heck" version = "0.4.1" @@ -704,6 +710,7 @@ dependencies = [ "base64", "clap", "dir-diff", + "glob", "hex", "indicatif", "indoc", diff --git a/repak_cli/Cargo.toml b/repak_cli/Cargo.toml index 2bf287b..60dbb78 100644 --- a/repak_cli/Cargo.toml +++ b/repak_cli/Cargo.toml @@ -34,6 +34,7 @@ rayon = "1.10.0" sha2 = "0.10.8" strum = { workspace = true } itertools = "0.13.0" +glob = "0.3.1" [dev-dependencies] assert_cmd = "2.0.14" diff --git a/repak_cli/src/main.rs b/repak_cli/src/main.rs index 8fadcea..1c13b51 100644 --- a/repak_cli/src/main.rs +++ b/repak_cli/src/main.rs @@ -67,7 +67,7 @@ struct ActionUnpack { /// Files or directories to include. Can be specified multiple times. If not specified, everything is extracted. #[arg(action = clap::ArgAction::Append, short, long)] - include: Vec, + include: Vec, } #[derive(Parser, Debug)] @@ -339,12 +339,6 @@ fn unpack(aes_key: Option, action: ActionUnpack) -> Result<(), repa let mount_point = PathBuf::from(pak.mount_point()); let prefix = Path::new(&action.strip_prefix); - let includes = action - .include - .iter() - .map(|i| prefix.join(Path::new(i))) - .collect::>(); - struct UnpackEntry { entry_path: String, out_path: PathBuf, @@ -356,8 +350,28 @@ fn unpack(aes_key: Option, action: ActionUnpack) -> Result<(), repa .into_iter() .map(|entry_path| { let full_path = mount_point.join(&entry_path); - if !includes.is_empty() && !includes.iter().any(|i| full_path.starts_with(i)) { - return Ok(None); + if !action.include.is_empty() { + if let Ok(stripped) = full_path.strip_prefix(prefix) { + let options = glob::MatchOptions { + case_sensitive: true, + require_literal_separator: true, + require_literal_leading_dot: false, + }; + if !action.include.iter().any(|i| { + // check full file path + i.matches_path_with(stripped, options) + // check ancestor directories + || stripped.ancestors().skip(1).any(|a| { + i.matches_path_with(a, options) + // hack to check ancestor directories with trailing slash + || i.matches_path_with(&a.join(""), options) + }) + }) { + return Ok(None); + } + } else { + return Ok(None); + } } let out_path = output .join(full_path.strip_prefix(prefix).map_err(|_| { diff --git a/repak_cli/tests/cli.rs b/repak_cli/tests/cli.rs index 5026bc7..3003f1a 100644 --- a/repak_cli/tests/cli.rs +++ b/repak_cli/tests/cli.rs @@ -148,6 +148,26 @@ fn test_cli_unpack() { // TODO test unpacking to non-empty directory } +#[test] +fn test_cli_unpack_include() { + let dir = tempfile::tempdir().unwrap(); + + let assert = Command::cargo_bin("repak") + .unwrap() + .arg("unpack") + .arg(PAK) + .arg("-s") + .arg("../mount") + .arg("-i") + .arg("point/**/*.txt") + .arg("-o") + .arg(dir.path()) + .assert(); + assert.success().stdout(formatdoc! {r#" + Unpacked 2 files to {} from ../repak/tests/packs/pack_v11.pak + "#, &dir.path().to_string_lossy()}); +} + #[test] fn test_cli_hashlist() { let assert = Command::cargo_bin("repak")