Merge pull request #812 from yyogo/master

Append trailing path separators to directories
This commit is contained in:
David Peter 2022-05-15 16:53:20 +02:00 committed by GitHub
commit de27835264
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 282 additions and 197 deletions

View File

@ -14,6 +14,7 @@
## Changes ## Changes
- Changed `-u` flag to be equivalent to `-HI`. Multiple `-u` flags still allowed but do nothing, see #840 (@jacksontheel) - Changed `-u` flag to be equivalent to `-HI`. Multiple `-u` flags still allowed but do nothing, see #840 (@jacksontheel)
- Directories are now printed with an additional path separator at the end: `foo/bar/`, see #436 and #812 (@yyogo)
## Other ## Other

View File

@ -111,6 +111,9 @@ pub struct Config {
/// The separator used to print file paths. /// The separator used to print file paths.
pub path_separator: Option<String>, pub path_separator: Option<String>,
/// The actual separator, either the system default separator or `path_separator`
pub actual_path_separator: String,
/// The maximum number of search results /// The maximum number of search results
pub max_results: Option<usize>, pub max_results: Option<usize>,

92
src/dir_entry.rs Normal file
View File

@ -0,0 +1,92 @@
use std::{
fs::{FileType, Metadata},
path::{Path, PathBuf},
};
use once_cell::unsync::OnceCell;
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,
}
impl DirEntry {
#[inline]
pub fn normal(e: ignore::DirEntry) -> Self {
Self {
inner: DirEntryInner::Normal(e),
metadata: OnceCell::new(),
}
}
pub fn broken_symlink(path: PathBuf) -> Self {
Self {
inner: DirEntryInner::BrokenSymlink(path),
metadata: OnceCell::new(),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
DirEntryInner::Normal(e) => e.path(),
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn into_path(self) -> PathBuf {
match self.inner {
DirEntryInner::Normal(e) => e.into_path(),
DirEntryInner::BrokenSymlink(p) => p,
}
}
pub fn file_type(&self) -> Option<FileType> {
match &self.inner {
DirEntryInner::Normal(e) => e.file_type(),
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
}
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata
.get_or_init(|| match &self.inner {
DirEntryInner::Normal(e) => e.metadata().ok(),
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
})
.as_ref()
}
pub fn depth(&self) -> Option<usize> {
match &self.inner {
DirEntryInner::Normal(e) => Some(e.depth()),
DirEntryInner::BrokenSymlink(_) => None,
}
}
}
impl PartialEq for DirEntry {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.path() == other.path()
}
}
impl Eq for DirEntry {}
impl PartialOrd for DirEntry {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.path().partial_cmp(other.path())
}
}
impl Ord for DirEntry {
#[inline]
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.path().cmp(other.path())
}
}

View File

@ -1,7 +1,7 @@
use std::path::PathBuf;
use std::sync::mpsc::Receiver; use std::sync::mpsc::Receiver;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use crate::dir_entry::DirEntry;
use crate::error::print_error; use crate::error::print_error;
use crate::exit_codes::{merge_exitcodes, ExitCode}; use crate::exit_codes::{merge_exitcodes, ExitCode};
use crate::walk::WorkerResult; use crate::walk::WorkerResult;
@ -25,8 +25,8 @@ pub fn job(
// Obtain the next result from the receiver, else if the channel // Obtain the next result from the receiver, else if the channel
// has closed, exit from the loop // has closed, exit from the loop
let value: PathBuf = match lock.recv() { let dir_entry: DirEntry = match lock.recv() {
Ok(WorkerResult::Entry(path)) => path, Ok(WorkerResult::Entry(dir_entry)) => dir_entry,
Ok(WorkerResult::Error(err)) => { Ok(WorkerResult::Error(err)) => {
if show_filesystem_errors { if show_filesystem_errors {
print_error(err.to_string()); print_error(err.to_string());
@ -39,7 +39,7 @@ pub fn job(
// Drop the lock so that other threads can read from the receiver. // Drop the lock so that other threads can read from the receiver.
drop(lock); drop(lock);
// Generate a command, execute it and store its exit code. // Generate a command, execute it and store its exit code.
results.push(cmd.execute(&value, Arc::clone(&out_perm), buffer_output)) results.push(cmd.execute(dir_entry.path(), Arc::clone(&out_perm), buffer_output))
} }
// Returns error in case of any error. // Returns error in case of any error.
merge_exitcodes(results) merge_exitcodes(results)
@ -51,8 +51,10 @@ pub fn batch(
show_filesystem_errors: bool, show_filesystem_errors: bool,
limit: usize, limit: usize,
) -> ExitCode { ) -> ExitCode {
let paths = rx.iter().filter_map(|value| match value { let paths = rx
WorkerResult::Entry(path) => Some(path), .into_iter()
.filter_map(|worker_result| match worker_result {
WorkerResult::Entry(dir_entry) => Some(dir_entry.into_path()),
WorkerResult::Error(err) => { WorkerResult::Error(err) => {
if show_filesystem_errors { if show_filesystem_errors {
print_error(err.to_string()); print_error(err.to_string());

View File

@ -9,7 +9,7 @@ use std::path::{Path, PathBuf};
use normpath::PathExt; use normpath::PathExt;
use crate::walk; use crate::dir_entry;
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> { pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
if path.is_absolute() { if path.is_absolute() {
@ -51,7 +51,7 @@ pub fn is_executable(_: &fs::Metadata) -> bool {
false false
} }
pub fn is_empty(entry: &walk::DirEntry) -> bool { pub fn is_empty(entry: &dir_entry::DirEntry) -> bool {
if let Some(file_type) = entry.file_type() { if let Some(file_type) = entry.file_type() {
if file_type.is_dir() { if file_type.is_dir() {
if let Ok(mut entries) = fs::read_dir(entry.path()) { if let Ok(mut entries) = fs::read_dir(entry.path()) {

View File

@ -1,5 +1,5 @@
use crate::dir_entry;
use crate::filesystem; use crate::filesystem;
use crate::walk;
/// Whether or not to show /// Whether or not to show
#[derive(Default)] #[derive(Default)]
@ -14,7 +14,7 @@ pub struct FileTypes {
} }
impl FileTypes { impl FileTypes {
pub fn should_ignore(&self, entry: &walk::DirEntry) -> bool { pub fn should_ignore(&self, entry: &dir_entry::DirEntry) -> bool {
if let Some(ref entry_type) = entry.file_type() { if let Some(ref entry_type) = entry.file_type() {
(!self.files && entry_type.is_file()) (!self.files && entry_type.is_file())
|| (!self.directories && entry_type.is_dir()) || (!self.directories && entry_type.is_dir())

View File

@ -1,5 +1,6 @@
mod app; mod app;
mod config; mod config;
mod dir_entry;
mod error; mod error;
mod exec; mod exec;
mod exit_codes; mod exit_codes;
@ -221,6 +222,9 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
let path_separator = matches let path_separator = matches
.value_of("path-separator") .value_of("path-separator")
.map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned())); .map_or_else(filesystem::default_path_separator, |s| Some(s.to_owned()));
let actual_path_separator = path_separator
.clone()
.unwrap_or_else(|| std::path::MAIN_SEPARATOR.to_string());
check_path_separator_length(path_separator.as_deref())?; check_path_separator_length(path_separator.as_deref())?;
let size_limits = extract_size_limits(&matches)?; let size_limits = extract_size_limits(&matches)?;
@ -367,6 +371,7 @@ fn construct_config(matches: clap::ArgMatches, pattern_regex: &str) -> Result<Co
owner_constraint, owner_constraint,
show_filesystem_errors: matches.is_present("show-errors"), show_filesystem_errors: matches.is_present("show-errors"),
path_separator, path_separator,
actual_path_separator,
max_results: matches max_results: matches
.value_of("max-results") .value_of("max-results")
.map(|n| n.parse::<usize>()) .map(|n| n.parse::<usize>())

View File

@ -5,6 +5,7 @@ use std::path::Path;
use lscolors::{Indicator, LsColors, Style}; use lscolors::{Indicator, LsColors, Style};
use crate::config::Config; use crate::config::Config;
use crate::dir_entry::DirEntry;
use crate::error::print_error; use crate::error::print_error;
use crate::exit_codes::ExitCode; use crate::exit_codes::ExitCode;
use crate::filesystem::strip_current_dir; use crate::filesystem::strip_current_dir;
@ -13,18 +14,21 @@ fn replace_path_separator(path: &str, new_path_separator: &str) -> String {
path.replace(std::path::MAIN_SEPARATOR, new_path_separator) path.replace(std::path::MAIN_SEPARATOR, new_path_separator)
} }
// TODO: this function is performance critical and can probably be optimized fn stripped_path<'a>(entry: &'a DirEntry, config: &Config) -> &'a Path {
pub fn print_entry<W: Write>(stdout: &mut W, entry: &Path, config: &Config) { let path = entry.path();
let path = if config.strip_cwd_prefix { if config.strip_cwd_prefix {
strip_current_dir(entry) strip_current_dir(path)
} else { } else {
entry path
}; }
}
// TODO: this function is performance critical and can probably be optimized
pub fn print_entry<W: Write>(stdout: &mut W, entry: &DirEntry, config: &Config) {
let r = if let Some(ref ls_colors) = config.ls_colors { let r = if let Some(ref ls_colors) = config.ls_colors {
print_entry_colorized(stdout, path, config, ls_colors) print_entry_colorized(stdout, entry, config, ls_colors)
} else { } else {
print_entry_uncolorized(stdout, path, config) print_entry_uncolorized(stdout, entry, config)
}; };
if let Err(e) = r { if let Err(e) = r {
@ -38,15 +42,39 @@ pub fn print_entry<W: Write>(stdout: &mut W, entry: &Path, config: &Config) {
} }
} }
// Display a trailing slash if the path is a directory and the config option is enabled.
// If the path_separator option is set, display that instead.
// The trailing slash will not be colored.
#[inline]
fn print_trailing_slash<W: Write>(
stdout: &mut W,
entry: &DirEntry,
config: &Config,
style: Option<&Style>,
) -> io::Result<()> {
if entry.file_type().map_or(false, |ft| ft.is_dir()) {
write!(
stdout,
"{}",
style
.map(Style::to_ansi_term_style)
.unwrap_or_default()
.paint(&config.actual_path_separator)
)?;
}
Ok(())
}
// TODO: this function is performance critical and can probably be optimized // TODO: this function is performance critical and can probably be optimized
fn print_entry_colorized<W: Write>( fn print_entry_colorized<W: Write>(
stdout: &mut W, stdout: &mut W,
path: &Path, entry: &DirEntry,
config: &Config, config: &Config,
ls_colors: &LsColors, ls_colors: &LsColors,
) -> io::Result<()> { ) -> io::Result<()> {
// Split the path between the parent and the last component // Split the path between the parent and the last component
let mut offset = 0; let mut offset = 0;
let path = stripped_path(entry, config);
let path_str = path.to_string_lossy(); let path_str = path.to_string_lossy();
if let Some(parent) = path.parent() { if let Some(parent) = path.parent() {
@ -74,11 +102,18 @@ fn print_entry_colorized<W: Write>(
} }
let style = ls_colors let style = ls_colors
.style_for_path(path) .style_for_path_with_metadata(path, entry.metadata())
.map(Style::to_ansi_term_style) .map(Style::to_ansi_term_style)
.unwrap_or_default(); .unwrap_or_default();
write!(stdout, "{}", style.paint(&path_str[offset..]))?; write!(stdout, "{}", style.paint(&path_str[offset..]))?;
print_trailing_slash(
stdout,
entry,
config,
ls_colors.style_for_indicator(Indicator::Directory),
)?;
if config.null_separator { if config.null_separator {
write!(stdout, "\0")?; write!(stdout, "\0")?;
} else { } else {
@ -91,42 +126,46 @@ fn print_entry_colorized<W: Write>(
// TODO: this function is performance critical and can probably be optimized // TODO: this function is performance critical and can probably be optimized
fn print_entry_uncolorized_base<W: Write>( fn print_entry_uncolorized_base<W: Write>(
stdout: &mut W, stdout: &mut W,
path: &Path, entry: &DirEntry,
config: &Config, config: &Config,
) -> io::Result<()> { ) -> io::Result<()> {
let separator = if config.null_separator { "\0" } else { "\n" }; let separator = if config.null_separator { "\0" } else { "\n" };
let path = stripped_path(entry, config);
let mut path_string = path.to_string_lossy(); let mut path_string = path.to_string_lossy();
if let Some(ref separator) = config.path_separator { if let Some(ref separator) = config.path_separator {
*path_string.to_mut() = replace_path_separator(&path_string, separator); *path_string.to_mut() = replace_path_separator(&path_string, separator);
} }
write!(stdout, "{}{}", path_string, separator) write!(stdout, "{}", path_string)?;
print_trailing_slash(stdout, entry, config, None)?;
write!(stdout, "{}", separator)
} }
#[cfg(not(unix))] #[cfg(not(unix))]
fn print_entry_uncolorized<W: Write>( fn print_entry_uncolorized<W: Write>(
stdout: &mut W, stdout: &mut W,
path: &Path, entry: &DirEntry,
config: &Config, config: &Config,
) -> io::Result<()> { ) -> io::Result<()> {
print_entry_uncolorized_base(stdout, path, config) print_entry_uncolorized_base(stdout, entry, config)
} }
#[cfg(unix)] #[cfg(unix)]
fn print_entry_uncolorized<W: Write>( fn print_entry_uncolorized<W: Write>(
stdout: &mut W, stdout: &mut W,
path: &Path, entry: &DirEntry,
config: &Config, config: &Config,
) -> io::Result<()> { ) -> io::Result<()> {
use std::os::unix::ffi::OsStrExt; use std::os::unix::ffi::OsStrExt;
if config.interactive_terminal || config.path_separator.is_some() { if config.interactive_terminal || config.path_separator.is_some() {
// Fall back to the base implementation // Fall back to the base implementation
print_entry_uncolorized_base(stdout, path, config) print_entry_uncolorized_base(stdout, entry, config)
} else { } else {
// Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes // Print path as raw bytes, allowing invalid UTF-8 filenames to be passed to other processes
let separator = if config.null_separator { b"\0" } else { b"\n" }; let separator = if config.null_separator { b"\0" } else { b"\n" };
stdout.write_all(path.as_os_str().as_bytes())?; stdout.write_all(stripped_path(entry, config).as_os_str().as_bytes())?;
print_trailing_slash(stdout, entry, config, None)?;
stdout.write_all(separator) stdout.write_all(separator)
} }
} }

View File

@ -1,8 +1,7 @@
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fs::{FileType, Metadata};
use std::io; use std::io;
use std::mem; use std::mem;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -13,10 +12,10 @@ use std::{borrow::Cow, io::Write};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use ignore::overrides::OverrideBuilder; use ignore::overrides::OverrideBuilder;
use ignore::{self, WalkBuilder}; use ignore::{self, WalkBuilder};
use once_cell::unsync::OnceCell;
use regex::bytes::Regex; use regex::bytes::Regex;
use crate::config::Config; use crate::config::Config;
use crate::dir_entry::DirEntry;
use crate::error::print_error; use crate::error::print_error;
use crate::exec; use crate::exec;
use crate::exit_codes::{merge_exitcodes, ExitCode}; use crate::exit_codes::{merge_exitcodes, ExitCode};
@ -36,7 +35,7 @@ enum ReceiverMode {
/// The Worker threads can result in a valid entry having PathBuf or an error. /// The Worker threads can result in a valid entry having PathBuf or an error.
pub enum WorkerResult { pub enum WorkerResult {
Entry(PathBuf), Entry(DirEntry),
Error(ignore::Error), Error(ignore::Error),
} }
@ -181,7 +180,7 @@ struct ReceiverBuffer<W> {
/// The deadline to switch to streaming mode. /// The deadline to switch to streaming mode.
deadline: Instant, deadline: Instant,
/// The buffer of quickly received paths. /// The buffer of quickly received paths.
buffer: Vec<PathBuf>, buffer: Vec<DirEntry>,
/// Result count. /// Result count.
num_results: usize, num_results: usize,
} }
@ -242,20 +241,20 @@ impl<W: Write> ReceiverBuffer<W> {
/// Wait for a result or state change. /// Wait for a result or state change.
fn poll(&mut self) -> Result<(), ExitCode> { fn poll(&mut self) -> Result<(), ExitCode> {
match self.recv() { match self.recv() {
Ok(WorkerResult::Entry(path)) => { Ok(WorkerResult::Entry(dir_entry)) => {
if self.config.quiet { if self.config.quiet {
return Err(ExitCode::HasResults(true)); return Err(ExitCode::HasResults(true));
} }
match self.mode { match self.mode {
ReceiverMode::Buffering => { ReceiverMode::Buffering => {
self.buffer.push(path); self.buffer.push(dir_entry);
if self.buffer.len() > MAX_BUFFER_LENGTH { if self.buffer.len() > MAX_BUFFER_LENGTH {
self.stream()?; self.stream()?;
} }
} }
ReceiverMode::Streaming => { ReceiverMode::Streaming => {
self.print(&path)?; self.print(&dir_entry)?;
self.flush()?; self.flush()?;
} }
} }
@ -284,8 +283,8 @@ impl<W: Write> ReceiverBuffer<W> {
} }
/// Output a path. /// Output a path.
fn print(&mut self, path: &Path) -> Result<(), ExitCode> { fn print(&mut self, entry: &DirEntry) -> Result<(), ExitCode> {
output::print_entry(&mut self.stdout, path, &self.config); output::print_entry(&mut self.stdout, entry, &self.config);
if self.interrupt_flag.load(Ordering::Relaxed) { if self.interrupt_flag.load(Ordering::Relaxed) {
// Ignore any errors on flush, because we're about to exit anyway // Ignore any errors on flush, because we're about to exit anyway
@ -396,62 +395,6 @@ fn spawn_receiver(
}) })
} }
enum DirEntryInner {
Normal(ignore::DirEntry),
BrokenSymlink(PathBuf),
}
pub struct DirEntry {
inner: DirEntryInner,
metadata: OnceCell<Option<Metadata>>,
}
impl DirEntry {
fn normal(e: ignore::DirEntry) -> Self {
Self {
inner: DirEntryInner::Normal(e),
metadata: OnceCell::new(),
}
}
fn broken_symlink(path: PathBuf) -> Self {
Self {
inner: DirEntryInner::BrokenSymlink(path),
metadata: OnceCell::new(),
}
}
pub fn path(&self) -> &Path {
match &self.inner {
DirEntryInner::Normal(e) => e.path(),
DirEntryInner::BrokenSymlink(pathbuf) => pathbuf.as_path(),
}
}
pub fn file_type(&self) -> Option<FileType> {
match &self.inner {
DirEntryInner::Normal(e) => e.file_type(),
DirEntryInner::BrokenSymlink(_) => self.metadata().map(|m| m.file_type()),
}
}
pub fn metadata(&self) -> Option<&Metadata> {
self.metadata
.get_or_init(|| match &self.inner {
DirEntryInner::Normal(e) => e.metadata().ok(),
DirEntryInner::BrokenSymlink(path) => path.symlink_metadata().ok(),
})
.as_ref()
}
pub fn depth(&self) -> Option<usize> {
match &self.inner {
DirEntryInner::Normal(e) => Some(e.depth()),
DirEntryInner::BrokenSymlink(_) => None,
}
}
}
fn spawn_senders( fn spawn_senders(
config: &Arc<Config>, config: &Arc<Config>,
quit_flag: &Arc<AtomicBool>, quit_flag: &Arc<AtomicBool>,
@ -602,7 +545,7 @@ fn spawn_senders(
} }
} }
let send_result = tx_thread.send(WorkerResult::Entry(entry_path.to_owned())); let send_result = tx_thread.send(WorkerResult::Entry(entry));
if send_result.is_err() { if send_result.is_err() {
return ignore::WalkState::Quit; return ignore::WalkState::Quit;

View File

@ -72,7 +72,7 @@ fn test_simple() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -82,14 +82,14 @@ fn test_empty_pattern() {
let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES); let te = TestEnv::new(DEFAULT_DIRS, DEFAULT_FILES);
let expected = "./a.foo let expected = "./a.foo
./e1 e2 ./e1 e2
./one ./one/
./one/b.foo ./one/b.foo
./one/two ./one/two/
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three ./one/two/three/
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo ./one/two/three/directory_foo/
./symlink"; ./symlink";
te.assert_output(&["--regex"], expected); te.assert_output(&["--regex"], expected);
@ -171,24 +171,24 @@ fn test_explicit_root_path() {
one/two/c.foo one/two/c.foo
one/two/C.Foo2 one/two/C.Foo2
one/two/three/d.foo one/two/three/d.foo
one/two/three/directory_foo", one/two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
&["foo", "one/two/three"], &["foo", "one/two/three"],
"one/two/three/d.foo "one/two/three/d.foo
one/two/three/directory_foo", one/two/three/directory_foo/",
); );
te.assert_output_subdirectory( te.assert_output_subdirectory(
"one/two", "one/two/",
&["foo", "../../"], &["foo", "../../"],
"../../a.foo "../../a.foo
../../one/b.foo ../../one/b.foo
../../one/two/c.foo ../../one/two/c.foo
../../one/two/C.Foo2 ../../one/two/C.Foo2
../../one/two/three/d.foo ../../one/two/three/d.foo
../../one/two/three/directory_foo", ../../one/two/three/directory_foo/",
); );
te.assert_output_subdirectory( te.assert_output_subdirectory(
@ -196,9 +196,9 @@ fn test_explicit_root_path() {
&["", ".."], &["", ".."],
"../c.foo "../c.foo
../C.Foo2 ../C.Foo2
../three ../three/
../three/d.foo ../three/d.foo
../three/directory_foo", ../three/directory_foo/",
); );
} }
@ -387,7 +387,7 @@ fn test_full_path() {
&format!("^{prefix}.*three.*foo$", prefix = prefix), &format!("^{prefix}.*three.*foo$", prefix = prefix),
], ],
"./one/two/three/d.foo "./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -404,7 +404,7 @@ fn test_hidden() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -444,7 +444,7 @@ fn test_no_ignore() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
@ -457,7 +457,7 @@ fn test_no_ignore() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -610,7 +610,7 @@ fn test_no_ignore_vcs() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -664,7 +664,7 @@ fn test_no_ignore_aliases() {
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -763,7 +763,7 @@ fn test_print0() {
./one/two/C.Foo2NULL ./one/two/C.Foo2NULL
./one/two/c.fooNULL ./one/two/c.fooNULL
./one/two/three/d.fooNULL ./one/two/three/d.fooNULL
./one/two/three/directory_fooNULL", ./one/two/three/directory_foo/NULL",
); );
} }
@ -776,12 +776,12 @@ fn test_max_depth() {
&["--max-depth", "3"], &["--max-depth", "3"],
"./a.foo "./a.foo
./e1 e2 ./e1 e2
./one ./one/
./one/b.foo ./one/b.foo
./one/two ./one/two/
./one/two/c.foo ./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three ./one/two/three/
./symlink", ./symlink",
); );
@ -789,9 +789,9 @@ fn test_max_depth() {
&["--max-depth", "2"], &["--max-depth", "2"],
"./a.foo "./a.foo
./e1 e2 ./e1 e2
./one ./one/
./one/b.foo ./one/b.foo
./one/two ./one/two/
./symlink", ./symlink",
); );
@ -799,7 +799,7 @@ fn test_max_depth() {
&["--max-depth", "1"], &["--max-depth", "1"],
"./a.foo "./a.foo
./e1 e2 ./e1 e2
./one ./one/
./symlink", ./symlink",
); );
} }
@ -813,15 +813,15 @@ fn test_min_depth() {
&["--min-depth", "3"], &["--min-depth", "3"],
"./one/two/c.foo "./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three ./one/two/three/
./one/two/three/d.foo ./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
&["--min-depth", "4"], &["--min-depth", "4"],
"./one/two/three/d.foo "./one/two/three/d.foo
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -834,7 +834,7 @@ fn test_exact_depth() {
&["--exact-depth", "3"], &["--exact-depth", "3"],
"./one/two/c.foo "./one/two/c.foo
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three", ./one/two/three/",
); );
} }
@ -854,19 +854,19 @@ fn test_prune() {
te.assert_output( te.assert_output(
&["foo"], &["foo"],
"./foo "./foo/
./foo/foo.file ./foo/foo.file
./foo/bar/foo.file ./foo/bar/foo.file
./bar/foo.file ./bar/foo.file
./bar/foo ./bar/foo/
./bar/foo/foo.file ./bar/foo/foo.file
./baz/foo.file", ./baz/foo.file",
); );
te.assert_output( te.assert_output(
&["--prune", "foo"], &["--prune", "foo"],
"./foo "./foo/
./bar/foo ./bar/foo/
./bar/foo.file ./bar/foo.file
./baz/foo.file", ./baz/foo.file",
); );
@ -882,14 +882,14 @@ fn test_absolute_path() {
&format!( &format!(
"{abs_path}/a.foo "{abs_path}/a.foo
{abs_path}/e1 e2 {abs_path}/e1 e2
{abs_path}/one {abs_path}/one/
{abs_path}/one/b.foo {abs_path}/one/b.foo
{abs_path}/one/two {abs_path}/one/two/
{abs_path}/one/two/c.foo {abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2 {abs_path}/one/two/C.Foo2
{abs_path}/one/two/three {abs_path}/one/two/three/
{abs_path}/one/two/three/d.foo {abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo {abs_path}/one/two/three/directory_foo/
{abs_path}/symlink", {abs_path}/symlink",
abs_path = &abs_path abs_path = &abs_path
), ),
@ -903,7 +903,7 @@ fn test_absolute_path() {
{abs_path}/one/two/c.foo {abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2 {abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo {abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo", {abs_path}/one/two/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -922,7 +922,7 @@ fn test_implicit_absolute_path() {
{abs_path}/one/two/c.foo {abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2 {abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo {abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo", {abs_path}/one/two/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -942,7 +942,7 @@ fn test_normalized_absolute_path() {
{abs_path}/one/two/c.foo {abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2 {abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo {abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo", {abs_path}/one/two/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -967,18 +967,18 @@ fn test_type() {
te.assert_output( te.assert_output(
&["--type", "d"], &["--type", "d"],
"./one "./one/
./one/two ./one/two/
./one/two/three ./one/two/three/
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
&["--type", "d", "--type", "l"], &["--type", "d", "--type", "l"],
"./one "./one/
./one/two ./one/two/
./one/two/three ./one/two/three/
./one/two/three/directory_foo ./one/two/three/directory_foo/
./symlink", ./symlink",
); );
@ -1005,10 +1005,10 @@ fn test_type_executable() {
te.assert_output( te.assert_output(
&["--type", "executable", "--type", "directory"], &["--type", "executable", "--type", "directory"],
"./executable-file.sh "./executable-file.sh
./one ./one/
./one/two ./one/two/
./one/two/three ./one/two/three/
./one/two/three/directory_foo", ./one/two/three/directory_foo/",
); );
} }
@ -1025,18 +1025,18 @@ fn test_type_empty() {
te.assert_output( te.assert_output(
&["--type", "empty"], &["--type", "empty"],
"./0_bytes.foo "./0_bytes.foo
./dir_empty", ./dir_empty/",
); );
te.assert_output( te.assert_output(
&["--type", "empty", "--type", "file", "--type", "directory"], &["--type", "empty", "--type", "file", "--type", "directory"],
"./0_bytes.foo "./0_bytes.foo
./dir_empty", ./dir_empty/",
); );
te.assert_output(&["--type", "empty", "--type", "file"], "./0_bytes.foo"); te.assert_output(&["--type", "empty", "--type", "file"], "./0_bytes.foo");
te.assert_output(&["--type", "empty", "--type", "directory"], "./dir_empty"); te.assert_output(&["--type", "empty", "--type", "directory"], "./dir_empty/");
} }
/// File extension (--extension) /// File extension (--extension)
@ -1107,12 +1107,12 @@ fn test_no_extension() {
te.assert_output( te.assert_output(
&["^[^.]+$"], &["^[^.]+$"],
"./aa "./aa
./one ./one/
./one/bb ./one/bb
./one/two ./one/two/
./one/two/three ./one/two/three/
./one/two/three/d ./one/two/three/d
./one/two/three/directory_foo ./one/two/three/directory_foo/
./symlink", ./symlink",
); );
@ -1152,14 +1152,14 @@ fn test_symlink_as_root() {
"{dir}/a.foo "{dir}/a.foo
{dir}/broken_symlink {dir}/broken_symlink
{dir}/e1 e2 {dir}/e1 e2
{dir}/one {dir}/one/
{dir}/one/b.foo {dir}/one/b.foo
{dir}/one/two {dir}/one/two/
{dir}/one/two/c.foo {dir}/one/two/c.foo
{dir}/one/two/C.Foo2 {dir}/one/two/C.Foo2
{dir}/one/two/three {dir}/one/two/three/
{dir}/one/two/three/d.foo {dir}/one/two/three/d.foo
{dir}/one/two/three/directory_foo {dir}/one/two/three/directory_foo/
{dir}/symlink", {dir}/symlink",
dir = &parent_parent dir = &parent_parent
), ),
@ -1178,9 +1178,9 @@ fn test_symlink_and_absolute_path() {
&format!( &format!(
"{abs_path}/{expected_path}/c.foo "{abs_path}/{expected_path}/c.foo
{abs_path}/{expected_path}/C.Foo2 {abs_path}/{expected_path}/C.Foo2
{abs_path}/{expected_path}/three {abs_path}/{expected_path}/three/
{abs_path}/{expected_path}/three/d.foo {abs_path}/{expected_path}/three/d.foo
{abs_path}/{expected_path}/three/directory_foo", {abs_path}/{expected_path}/three/directory_foo/",
abs_path = &abs_path, abs_path = &abs_path,
expected_path = expected_path expected_path = expected_path
), ),
@ -1196,9 +1196,9 @@ fn test_symlink_as_absolute_root() {
&format!( &format!(
"{abs_path}/symlink/c.foo "{abs_path}/symlink/c.foo
{abs_path}/symlink/C.Foo2 {abs_path}/symlink/C.Foo2
{abs_path}/symlink/three {abs_path}/symlink/three/
{abs_path}/symlink/three/d.foo {abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo", {abs_path}/symlink/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -1220,9 +1220,9 @@ fn test_symlink_and_full_path() {
&format!("^{prefix}.*three", prefix = prefix), &format!("^{prefix}.*three", prefix = prefix),
], ],
&format!( &format!(
"{abs_path}/{expected_path}/three "{abs_path}/{expected_path}/three/
{abs_path}/{expected_path}/three/d.foo {abs_path}/{expected_path}/three/d.foo
{abs_path}/{expected_path}/three/directory_foo", {abs_path}/{expected_path}/three/directory_foo/",
abs_path = &abs_path, abs_path = &abs_path,
expected_path = expected_path expected_path = expected_path
), ),
@ -1241,9 +1241,9 @@ fn test_symlink_and_full_path_abs_path() {
&format!("{abs_path}/symlink", abs_path = abs_path), &format!("{abs_path}/symlink", abs_path = abs_path),
], ],
&format!( &format!(
"{abs_path}/symlink/three "{abs_path}/symlink/three/
{abs_path}/symlink/three/d.foo {abs_path}/symlink/three/d.foo
{abs_path}/symlink/three/directory_foo", {abs_path}/symlink/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -1255,32 +1255,32 @@ fn test_excludes() {
te.assert_output( te.assert_output(
&["--exclude", "*.foo"], &["--exclude", "*.foo"],
"./one "./one/
./one/two ./one/two/
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three ./one/two/three/
./one/two/three/directory_foo ./one/two/three/directory_foo/
./e1 e2 ./e1 e2
./symlink", ./symlink",
); );
te.assert_output( te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2"], &["--exclude", "*.foo", "--exclude", "*.Foo2"],
"./one "./one/
./one/two ./one/two/
./one/two/three ./one/two/three/
./one/two/three/directory_foo ./one/two/three/directory_foo/
./e1 e2 ./e1 e2
./symlink", ./symlink",
); );
te.assert_output( te.assert_output(
&["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"], &["--exclude", "*.foo", "--exclude", "*.Foo2", "foo"],
"./one/two/three/directory_foo", "./one/two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
&["--exclude", "one/two", "foo"], &["--exclude", "one/two/", "foo"],
"./a.foo "./a.foo
./one/b.foo", ./one/b.foo",
); );
@ -1289,11 +1289,11 @@ fn test_excludes() {
&["--exclude", "one/**/*.foo"], &["--exclude", "one/**/*.foo"],
"./a.foo "./a.foo
./e1 e2 ./e1 e2
./one ./one/
./one/two ./one/two/
./one/two/C.Foo2 ./one/two/C.Foo2
./one/two/three ./one/two/three/
./one/two/three/directory_foo ./one/two/three/directory_foo/
./symlink", ./symlink",
); );
} }
@ -1866,7 +1866,7 @@ fn test_custom_path_separator() {
one=two=c.foo one=two=c.foo
one=two=C.Foo2 one=two=C.Foo2
one=two=three=d.foo one=two=three=d.foo
one=two=three=directory_foo", one=two=three=directory_foo=",
); );
} }
@ -1877,20 +1877,20 @@ fn test_base_directory() {
te.assert_output( te.assert_output(
&["--base-directory", "one"], &["--base-directory", "one"],
"./b.foo "./b.foo
./two ./two/
./two/c.foo ./two/c.foo
./two/C.Foo2 ./two/C.Foo2
./two/three ./two/three/
./two/three/d.foo ./two/three/d.foo
./two/three/directory_foo", ./two/three/directory_foo/",
); );
te.assert_output( te.assert_output(
&["--base-directory", "one/two", "foo"], &["--base-directory", "one/two/", "foo"],
"./c.foo "./c.foo
./C.Foo2 ./C.Foo2
./three/d.foo ./three/d.foo
./three/directory_foo", ./three/directory_foo/",
); );
// Explicit root path // Explicit root path
@ -1899,12 +1899,12 @@ fn test_base_directory() {
"two/c.foo "two/c.foo
two/C.Foo2 two/C.Foo2
two/three/d.foo two/three/d.foo
two/three/directory_foo", two/three/directory_foo/",
); );
// Ignore base directory when absolute path is used // Ignore base directory when absolute path is used
let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES); let (te, abs_path) = get_test_env_with_abs_path(DEFAULT_DIRS, DEFAULT_FILES);
let abs_base_dir = &format!("{abs_path}/one/two", abs_path = &abs_path); let abs_base_dir = &format!("{abs_path}/one/two/", abs_path = &abs_path);
te.assert_output( te.assert_output(
&["--base-directory", abs_base_dir, "foo", &abs_path], &["--base-directory", abs_base_dir, "foo", &abs_path],
&format!( &format!(
@ -1913,7 +1913,7 @@ fn test_base_directory() {
{abs_path}/one/two/c.foo {abs_path}/one/two/c.foo
{abs_path}/one/two/C.Foo2 {abs_path}/one/two/C.Foo2
{abs_path}/one/two/three/d.foo {abs_path}/one/two/three/d.foo
{abs_path}/one/two/three/directory_foo", {abs_path}/one/two/three/directory_foo/",
abs_path = &abs_path abs_path = &abs_path
), ),
); );
@ -2069,14 +2069,14 @@ fn test_strip_cwd_prefix() {
&["--strip-cwd-prefix", "."], &["--strip-cwd-prefix", "."],
"a.foo "a.foo
e1 e2 e1 e2
one one/
one/b.foo one/b.foo
one/two one/two/
one/two/c.foo one/two/c.foo
one/two/C.Foo2 one/two/C.Foo2
one/two/three one/two/three/
one/two/three/d.foo one/two/three/d.foo
one/two/three/directory_foo one/two/three/directory_foo/
symlink", symlink",
); );
} }