diff options
| -rw-r--r-- | src/bin/r2md-remove.rs | 6 | ||||
| -rw-r--r-- | src/path_list_editor.rs | 67 |
2 files changed, 42 insertions, 31 deletions
diff --git a/src/bin/r2md-remove.rs b/src/bin/r2md-remove.rs index 0835668..07c9124 100644 --- a/src/bin/r2md-remove.rs +++ b/src/bin/r2md-remove.rs @@ -1,7 +1,7 @@ use std::{io, path::PathBuf}; use clap::Parser; -use repo2markdown::{normalizer::Normalizer, path_list_editor::remove_paths}; +use repo2markdown::path_list_editor::remove_paths; /// Remove paths from a null-separated file list from stdin. #[derive(Debug, Parser)] @@ -18,13 +18,11 @@ struct Cli { fn main() -> Result<(), Box<dyn std::error::Error>> { let cli = Cli::parse(); - let normalizer = Normalizer::new(&cli.origin, &cli.origin)?; - remove_paths( io::stdin().lock(), io::stdout().lock(), &cli.paths, - &normalizer, + &cli.origin, ) } diff --git a/src/path_list_editor.rs b/src/path_list_editor.rs index bdd6982..1ae4816 100644 --- a/src/path_list_editor.rs +++ b/src/path_list_editor.rs @@ -5,14 +5,14 @@ //! `r2md-prepend`, and `r2md-remove`. //! //! Paths that are kept from stdin are written back using their original byte representation. -//! Removal compares paths after normalization using the supplied [`Normalizer`]. +//! Removal compares paths after normalization relative to a supplied origin base. use std::{ collections::HashSet, ffi::OsString, io::{Read, Write}, os::unix::ffi::OsStrExt, - path::PathBuf, + path::{Path, PathBuf}, }; use crate::{normalizer::Normalizer, util::path_list::paths_from_null_separated_bytes}; @@ -95,28 +95,29 @@ pub fn prepend_paths<R: Read, W: Write>( /// Removes paths from a null-separated path list. /// /// The `input` stream is interpreted as a NUL-separated sequence of path entries. Each entry is -/// normalized with `normalizer` and compared against the normalized form of each path in +/// normalized relative to `origin_base` and compared against the normalized form of each path in /// `unwanted_paths`. /// /// Entries whose normalized path matches one of the normalized `unwanted_paths` are skipped. /// Entries that are kept are written to `output` using their original byte form, followed by a NUL /// byte. -/// -/// Matching is normalization-aware, so paths such as `src/../a.rs`, `./a.rs`, and an absolute path -/// to `a.rs` may match depending on the supplied [`Normalizer`]. This function does not deduplicate -/// kept entries. pub fn remove_paths<R: Read, W: Write>( mut input: R, mut output: W, unwanted_paths: &[PathBuf], - normalizer: &Normalizer, + origin_base: &Path, ) -> Result<(), Box<dyn std::error::Error>> { - let mut input_buf = Vec::new(); - input.read_to_end(&mut input_buf)?; + // The root is arbitrary here because root_relative is not user-facing. + // It only needs to be consistent across both sides of the comparison. + let normalizer = Normalizer::new(Path::new("."), origin_base)?; + // Normalize unwanted paths once and place them in a HashSet for efficient lookup. let unwanted_paths = unwanted_paths .iter() .map(|path| normalizer.normalize(path)) .collect::<Result<HashSet<_>, _>>()?; + + let mut input_buf = Vec::new(); + input.read_to_end(&mut input_buf)?; for path in paths_from_null_separated_bytes(&input_buf) { let normalized_path = normalizer.normalize(path)?; if !unwanted_paths.contains(&normalized_path) { @@ -131,18 +132,12 @@ pub fn remove_paths<R: Read, W: Write>( mod tests { use std::{ ffi::OsString, + os::unix::ffi::OsStrExt, path::{Path, PathBuf}, }; - use crate::normalizer::Normalizer; - use super::{append_paths, prepend_paths, remove_paths}; - fn get_normalizer() -> Normalizer { - let default_path = Path::new("."); - Normalizer::new(default_path, default_path).unwrap() - } - #[test] fn append_paths_works_with_empty_stdin_and_args() { let input = b""; @@ -278,7 +273,7 @@ mod tests { let input = b""; let mut output = Vec::new(); - remove_paths(&input[..], &mut output, &[], &get_normalizer()).unwrap(); + remove_paths(&input[..], &mut output, &[], Path::new(".")).unwrap(); assert_eq!(output, b""); } @@ -288,7 +283,7 @@ mod tests { let input = b"README.md\0"; let mut output = Vec::new(); - remove_paths(&input[..], &mut output, &[], &get_normalizer()).unwrap(); + remove_paths(&input[..], &mut output, &[], Path::new(".")).unwrap(); assert_eq!(output, b"README.md\0"); } @@ -302,7 +297,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("README.md")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -318,7 +313,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("b.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -334,7 +329,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("b.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -350,7 +345,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("src/main.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -366,7 +361,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("a.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -382,7 +377,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("src/../a.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -398,7 +393,7 @@ mod tests { &input[..], &mut output, &[PathBuf::from("./a.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); @@ -414,10 +409,28 @@ mod tests { &input[..], &mut output, &[PathBuf::from("other.rs")], - &get_normalizer(), + Path::new("."), ) .unwrap(); assert_eq!(output, b"src/./main.rs\0src/lib.rs\0"); } + + #[test] + fn remove_paths_matches_relative_unwanted_path_against_absolute_input_using_origin_base() { + let temp_dir = tempfile::tempdir().unwrap(); + let origin = temp_dir.path(); + let absolute_path = origin.join("a.rs"); + + let mut input = Vec::new(); + input.extend(absolute_path.as_os_str().as_bytes()); + input.push(0); + input.extend(b"b.rs\0"); + + let mut output = Vec::new(); + + remove_paths(&input[..], &mut output, &[PathBuf::from("a.rs")], origin).unwrap(); + + assert_eq!(output, b"b.rs\0"); + } } |
