use std::{ collections::HashSet, ffi::{OsStr, OsString}, io::{Read, Write}, os::unix::ffi::OsStrExt, }; const NUL: &[u8] = b"\0"; pub fn append_paths( input: R, mut output: W, new_paths: &[OsString], ) -> std::io::Result<()> { let last_byte_in_input = copy_while_tracking_last_byte(input, &mut output)?; // Add NUL if there is input and it does not end in NUL. if matches!(last_byte_in_input, Some(byte) if byte != 0) { output.write_all(NUL)?; } for path in new_paths { output.write_all(path.as_bytes())?; output.write_all(NUL)?; } Ok(()) } fn copy_while_tracking_last_byte( mut input: R, mut output: W, ) -> std::io::Result> { let mut buf = [0; 8192]; let mut last = None; loop { let read_bytes = input.read(&mut buf)?; if read_bytes == 0 { break; } last = Some(buf[read_bytes - 1]); output.write_all(&buf[..read_bytes])?; } Ok(last) } pub fn prepend_paths( mut input: R, mut output: W, new_paths: &[OsString], ) -> std::io::Result<()> { for path in new_paths { output.write_all(path.as_bytes())?; output.write_all(NUL)?; } std::io::copy(&mut input, &mut output)?; Ok(()) } pub fn remove_paths( mut input: R, mut output: W, unwanted_paths: &[OsString], ) -> std::io::Result<()> { let mut buf = Vec::new(); input.read_to_end(&mut buf)?; let unwanted_paths = HashSet::<&OsString>::from_iter(unwanted_paths); for segment in buf.split(|b| *b == 0) { if segment.is_empty() { continue; } let os_string_segment = OsStr::from_bytes(segment).to_os_string(); if !unwanted_paths.contains(&os_string_segment) { output.write_all(segment)?; output.write_all(NUL)?; } } Ok(()) } #[cfg(test)] mod tests { use std::ffi::OsString; use super::{append_paths, prepend_paths, remove_paths}; #[test] fn append_paths_works_with_empty_stdin_and_args() { let input = b""; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b""); } #[test] fn append_paths_works_with_empty_args() { let input = b"README.md\0"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b"README.md\0"); } #[test] fn append_paths_works_with_empty_stdin() { let input = b""; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("README.md")]).unwrap(); assert_eq!(output, b"README.md\0"); } #[test] fn append_paths_works_with_unique_stdin_and_args() { let input = b"a.rs\0b.rs\0"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("c.rs")]).unwrap(); assert_eq!(output, b"a.rs\0b.rs\0c.rs\0"); } #[test] fn append_paths_keeps_duplicate_entries() { let input = b"a.rs\0b.rs\0"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("a.rs")]).unwrap(); assert_eq!(output, b"a.rs\0b.rs\0a.rs\0"); } #[test] fn append_paths_inserts_separator_when_stdin_is_not_null_terminated() { let input = b"a.rs"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("b.rs")]).unwrap(); assert_eq!(output, b"a.rs\0b.rs\0"); } #[test] fn append_paths_does_not_insert_extra_separator_when_stdin_is_null_terminated() { let input = b"a.rs\0"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("b.rs")]).unwrap(); assert_eq!(output, b"a.rs\0b.rs\0"); } #[test] fn append_paths_handles_partially_terminated_input_sequence() { let input = b"a.rs\0b.rs"; let mut output = Vec::new(); append_paths(&input[..], &mut output, &[OsString::from("c.rs")]).unwrap(); assert_eq!(output, b"a.rs\0b.rs\0c.rs\0"); } #[test] fn prepend_paths_works_with_empty_stdin_and_args() { let input = b""; let mut output = Vec::new(); prepend_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b""); } #[test] fn prepend_paths_works_with_empty_args() { let input = b"README.md\0"; let mut output = Vec::new(); prepend_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b"README.md\0"); } #[test] fn prepend_paths_works_with_empty_stdin() { let input = b""; let mut output = Vec::new(); prepend_paths(&input[..], &mut output, &[OsString::from("README.md")]).unwrap(); assert_eq!(output, b"README.md\0"); } #[test] fn prepend_paths_works_with_unique_stdin_and_args() { let input = b"a.rs\0b.rs\0"; let mut output = Vec::new(); prepend_paths(&input[..], &mut output, &[OsString::from("c.rs")]).unwrap(); assert_eq!(output, b"c.rs\0a.rs\0b.rs\0"); } #[test] fn prepend_paths_keeps_duplicate_entries() { let input = b"a.rs\0b.rs\0"; let mut output = Vec::new(); prepend_paths(&input[..], &mut output, &[OsString::from("a.rs")]).unwrap(); assert_eq!(output, b"a.rs\0a.rs\0b.rs\0"); } #[test] fn remove_paths_works_with_empty_stdin_and_args() { let input = b""; let mut output = Vec::new(); remove_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b""); } #[test] fn remove_paths_works_with_empty_args() { let input = b"README.md\0"; let mut output = Vec::new(); remove_paths(&input[..], &mut output, &[]).unwrap(); assert_eq!(output, b"README.md\0"); } #[test] fn remove_paths_works_with_empty_stdin() { let input = b""; let mut output = Vec::new(); remove_paths(&input[..], &mut output, &[OsString::from("README.md")]).unwrap(); assert_eq!(output, b""); } #[test] fn remove_paths_filters_matching_entries() { let input = b"a.rs\0b.rs\0c.rs\0"; let mut output = Vec::new(); remove_paths(&input[..], &mut output, &[OsString::from("b.rs")]).unwrap(); assert_eq!(output, b"a.rs\0c.rs\0"); } #[test] fn remove_paths_removes_all_matching_entries() { let input = b"a.rs\0b.rs\0b.rs\0c.rs\0"; let mut output = Vec::new(); remove_paths(&input[..], &mut output, &[OsString::from("b.rs")]).unwrap(); assert_eq!(output, b"a.rs\0c.rs\0"); } }