diff --git a/src/exec/job.rs b/src/exec/job.rs index bd87230..0740c13 100644 --- a/src/exec/job.rs +++ b/src/exec/job.rs @@ -9,7 +9,6 @@ use super::TokenizedCommand; /// be executed, and this process will continue until the receiver's sender has closed. pub fn job( rx: Arc>>, - base: Arc>, cmd: Arc, out_perm: Arc>, ) { @@ -23,12 +22,7 @@ pub fn job( // Obtain the next path from the receiver, else if the channel // has closed, exit from the loop let value: PathBuf = match lock.recv() { - Ok(value) => { - match *base { - Some(ref base) => base.join(&value), - None => value, - } - } + Ok(value) => value, Err(_) => break, }; diff --git a/src/exec/mod.rs b/src/exec/mod.rs index bec9712..cb86fbe 100644 --- a/src/exec/mod.rs +++ b/src/exec/mod.rs @@ -109,6 +109,8 @@ impl TokenizedCommand { input: &Path, out_perm: Arc>, ) -> CommandTicket<'a> { + let input = input.strip_prefix(".").unwrap_or(input); + for token in &self.tokens { match *token { Token::Basename => *command += basename(&input.to_string_lossy()), diff --git a/src/fshelper/mod.rs b/src/fshelper/mod.rs index 3e30c83..34a9df1 100644 --- a/src/fshelper/mod.rs +++ b/src/fshelper/mod.rs @@ -1,50 +1,18 @@ +use std::env::current_dir; use std::path::{Path, PathBuf}; use std::io; -/// Get a relative path with respect to a certain base path. -/// See: https://stackoverflow.com/a/39343127/704831 -pub fn path_relative_from(path: &Path, base: &Path) -> Option { - use std::path::Component; - - if path.is_absolute() != base.is_absolute() { - if path.is_absolute() { - Some(PathBuf::from(path)) - } else { - Some(PathBuf::from(base.join(path))) - } +pub fn path_absolute_form(path: &Path) -> io::Result { + if path.is_absolute() { + Ok(path.to_path_buf()) } else { - let mut ita = path.components(); - let mut itb = base.components(); - let mut comps: Vec = vec![]; - loop { - match (ita.next(), itb.next()) { - (None, None) => break, - (Some(a), None) => { - comps.push(a); - comps.extend(ita.by_ref()); - break; - } - (None, _) => comps.push(Component::ParentDir), - (Some(a), Some(b)) if comps.is_empty() && a == b => (), - (Some(a), Some(b)) if b == Component::CurDir => comps.push(a), - (Some(_), Some(b)) if b == Component::ParentDir => return None, - (Some(a), Some(_)) => { - comps.push(Component::ParentDir); - for _ in itb { - comps.push(Component::ParentDir); - } - comps.push(a); - comps.extend(ita.by_ref()); - break; - } - } - } - Some(comps.iter().map(|c| c.as_os_str()).collect()) + let path = path.strip_prefix(".").unwrap_or(path); + current_dir().map(|path_buf| path_buf.join(path)) } } pub fn absolute_path(path: &Path) -> io::Result { - let path_buf = path.canonicalize()?; + let path_buf = path_absolute_form(path)?; #[cfg(windows)] let path_buf = Path::new(path_buf.as_path().to_string_lossy().trim_left_matches( @@ -53,3 +21,13 @@ pub fn absolute_path(path: &Path) -> io::Result { Ok(path_buf) } + +// Path::is_dir() is not guarandteed to be intuitively correct for "." and ".." +// See: https://github.com/rust-lang/rust/issues/45302 +pub fn is_dir(path: &Path) -> bool { + if path.file_name().is_some() { + path.is_dir() + } else { + path.is_dir() && path.canonicalize().is_ok() + } +} diff --git a/src/internal.rs b/src/internal.rs index 3c73915..6dfaa1a 100644 --- a/src/internal.rs +++ b/src/internal.rs @@ -7,13 +7,6 @@ use lscolors::LsColors; use walk::FileType; use regex_syntax::{Expr, ExprBuilder}; -/// Root directory -#[cfg(unix)] -pub static ROOT_DIR: &'static str = "/"; - -#[cfg(windows)] -pub static ROOT_DIR: &'static str = ""; - /// Defines how to display search result paths. #[derive(PartialEq)] pub enum PathDisplay { diff --git a/src/main.rs b/src/main.rs index 25e48a5..8b3b78c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,7 @@ use atty::Stream; use regex::RegexBuilder; use exec::TokenizedCommand; -use internal::{error, pattern_has_uppercase_char, FdOptions, PathDisplay, ROOT_DIR}; +use internal::{error, pattern_has_uppercase_char, FdOptions, PathDisplay}; use lscolors::LsColors; use walk::FileType; @@ -42,8 +42,7 @@ fn main() { // Get the current working directory let current_dir = Path::new("."); - // .is_dir() is not guarandteed to be intuitively correct for "." and ".." - if let Err(_) = current_dir.canonicalize() { + if !fshelper::is_dir(¤t_dir) { error("Error: could not get current directory."); } @@ -52,7 +51,7 @@ fn main() { Some(path) => PathBuf::from(path), None => current_dir.to_path_buf(), }; - if let Err(_) = root_dir_buf.canonicalize() { + if !fshelper::is_dir(&root_dir_buf) { error(&format!( "Error: '{}' is not a directory.", root_dir_buf.to_string_lossy() @@ -132,21 +131,11 @@ fn main() { command, }; - // If base_dir is ROOT_DIR, then root_dir must be absolute. - // Otherwise root_dir/entry cannot be turned into an existing relative path from base_dir. - // - // We utilize ROOT_DIR to avoid resolving the components of root_dir. - let base_dir_buf = match config.path_display { - PathDisplay::Relative => current_dir.to_path_buf(), - PathDisplay::Absolute => PathBuf::from(ROOT_DIR), - }; - let base_dir = base_dir_buf.as_path(); - match RegexBuilder::new(pattern) .case_insensitive(!config.case_sensitive) .dot_matches_new_line(true) .build() { - Ok(re) => walk::scan(root_dir, Arc::new(re), base_dir, Arc::new(config)), + Ok(re) => walk::scan(root_dir, Arc::new(re), Arc::new(config)), Err(err) => error(err.description()), } } diff --git a/src/output.rs b/src/output.rs index aa246ea..6fa59f4 100644 --- a/src/output.rs +++ b/src/output.rs @@ -1,4 +1,4 @@ -use internal::{FdOptions, PathDisplay}; +use internal::FdOptions; use lscolors::LsColors; use std::{fs, process}; @@ -10,23 +10,13 @@ use std::os::unix::fs::PermissionsExt; use ansi_term; -pub fn print_entry(base: &Path, entry: &PathBuf, config: &FdOptions) { - let path_full = if !entry.as_os_str().is_empty() { - base.join(entry) - } else { - base.to_path_buf() - }; - - let path_to_print = if config.path_display == PathDisplay::Absolute { - &path_full - } else { - entry - }; +pub fn print_entry(entry: &PathBuf, config: &FdOptions) { + let path = entry.strip_prefix(".").unwrap_or(entry); let r = if let Some(ref ls_colors) = config.ls_colors { - print_entry_colorized(base, path_to_print, config, ls_colors) + print_entry_colorized(path, config, ls_colors) } else { - print_entry_uncolorized(path_to_print, config) + print_entry_uncolorized(path, config) }; if r.is_err() { @@ -35,12 +25,7 @@ pub fn print_entry(base: &Path, entry: &PathBuf, config: &FdOptions) { } } -fn print_entry_colorized( - base: &Path, - path: &Path, - config: &FdOptions, - ls_colors: &LsColors, -) -> io::Result<()> { +fn print_entry_colorized(path: &Path, config: &FdOptions, ls_colors: &LsColors) -> io::Result<()> { let default_style = ansi_term::Style::default(); let stdout = io::stdout(); @@ -50,7 +35,7 @@ fn print_entry_colorized( let mut separator = String::new(); // Full path to the current component. - let mut component_path = base.to_path_buf(); + let mut component_path = PathBuf::new(); // Traverse the path and colorize each component for component in path.components() { diff --git a/src/walk.rs b/src/walk.rs index 117b0cf..f633117 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -1,6 +1,6 @@ use exec::{self, TokenizedCommand}; use fshelper; -use internal::{error, FdOptions, PathDisplay}; +use internal::{error, FdOptions}; use output; use std::path::Path; @@ -36,7 +36,7 @@ pub enum FileType { /// If the `--exec` argument was supplied, this will create a thread pool for executing /// jobs in parallel from a given command line and the discovered paths. Otherwise, each /// path will simply be written to standard output. -pub fn scan(root: &Path, pattern: Arc, base: &Path, config: Arc) { +pub fn scan(root: &Path, pattern: Arc, config: Arc) { let (tx, rx) = channel(); let threads = config.threads; @@ -54,8 +54,6 @@ pub fn scan(root: &Path, pattern: Arc, base: &Path, config: Arc, base: &Path, config: Arc, base: &Path, config: Arc, base: &Path, config: Arc max_buffer_time { // Flush the buffer for v in &buffer { - output::print_entry(&rx_base, v, &rx_config); + output::print_entry(&v, &rx_config); } buffer.clear(); @@ -119,7 +114,7 @@ pub fn scan(root: &Path, pattern: Arc, base: &Path, config: Arc { - output::print_entry(&rx_base, &value, &rx_config); + output::print_entry(&value, &rx_config); } } } @@ -129,7 +124,7 @@ pub fn scan(root: &Path, pattern: Arc, base: &Path, config: Arc, base: &Path, config: Arc, base: &Path, config: Arc Some(path_abs_buf.to_string_lossy().into_owned().into()), + Err(_) => error("Error: unable to get full path."), + } } else { entry_path.file_name().map(|f| f.to_string_lossy()) }; if let Some(search_str) = search_str_o { - pattern.find(&*search_str).map(|_| { - let path_rel_buf = match fshelper::path_relative_from(entry_path, &*base) { - Some(p) => p, - None => error("Error: could not get relative path for directory entry."), - }; - + if pattern.is_match(&*search_str) { // TODO: take care of the unwrap call - tx_thread.send(path_rel_buf.to_owned()).unwrap() - }); + tx_thread.send(entry_path.to_owned()).unwrap() + } } ignore::WalkState::Continue diff --git a/tests/testenv/mod.rs b/tests/testenv/mod.rs index f265445..d1cca69 100644 --- a/tests/testenv/mod.rs +++ b/tests/testenv/mod.rs @@ -136,10 +136,16 @@ impl TestEnv { } /// Get the root directory for the tests. - pub fn root(&self) -> PathBuf { + pub fn test_root(&self) -> PathBuf { self.temp_dir.path().to_path_buf() } + /// Get the root directory of the file system. + pub fn system_root(&self) -> PathBuf { + let mut components = self.temp_dir.path().components(); + PathBuf::from(components.next().expect("root directory").as_os_str()) + } + /// Assert that calling *fd* with the specified arguments produces the expected output. pub fn assert_output(&self, args: &[&str], expected: &str) { self.assert_output_subdirectory(".", args, expected) diff --git a/tests/tests.rs b/tests/tests.rs index 676c312..750d501 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -1,11 +1,14 @@ //! Integration tests for the CLI interface of fd. +extern crate regex; + mod testenv; use testenv::TestEnv; +use regex::escape; fn get_absolute_root_path(env: &TestEnv) -> String { - let path = env.root() + let path = env.test_root() .canonicalize() .expect("absolute path") .to_str() @@ -177,8 +180,14 @@ fn test_case_insensitive() { fn test_full_path() { let te = TestEnv::new(); + let root = te.system_root(); + let prefix = escape(&root.to_string_lossy()); + te.assert_output( - &["--full-path", "three.*foo"], + &[ + "--full-path", + &format!("^{prefix}.*three.*foo$", prefix = prefix), + ], "one/two/three/d.foo one/two/three/directory_foo", ); @@ -343,7 +352,7 @@ fn test_absolute_path() { {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo {abs_path}/symlink", - abs_path = abs_path + abs_path = &abs_path ), ); @@ -356,7 +365,7 @@ fn test_absolute_path() { {abs_path}/one/two/C.Foo2 {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo", - abs_path = abs_path + abs_path = &abs_path ), ); @@ -369,7 +378,7 @@ fn test_absolute_path() { {abs_path}/one/two/C.Foo2 {abs_path}/one/two/three/d.foo {abs_path}/one/two/three/directory_foo", - abs_path = abs_path + abs_path = &abs_path ), ); } @@ -435,9 +444,13 @@ fn test_symlink() { // the array pointed to by buf, and return buf. The pathname shall contain no components that // are dot or dot-dot, or are symbolic links. // - // Symlinks on Unix are aliases to real paths, only has one redirection. + // Key points: + // 1. The path of the current working directory of a Unix process cannot contain symlinks. + // 2. The path of the current working directory of a Windows process can contain symlinks. // - // Symlinks on Windows can refer to symlinks, and are resolved after logical step "..". + // More: + // 1. On Windows, symlinks are resolved after the ".." component. + // 2. On Unix, symlinks are resolved immediately as encountered. let parent_parent = if cfg!(windows) { ".." } else { "../.." }; te.assert_output_subdirectory( @@ -462,12 +475,13 @@ fn test_symlink() { "symlink", &["--absolute-path"], &format!( - "{abs_path}/one/two/c.foo - {abs_path}/one/two/C.Foo2 - {abs_path}/one/two/three - {abs_path}/one/two/three/d.foo - {abs_path}/one/two/three/directory_foo", - abs_path = abs_path + "{abs_path}/{dir}/c.foo + {abs_path}/{dir}/C.Foo2 + {abs_path}/{dir}/three + {abs_path}/{dir}/three/d.foo + {abs_path}/{dir}/three/directory_foo", + dir = if cfg!(windows) { "symlink" } else { "one/two" }, + abs_path = &abs_path ), ); @@ -479,7 +493,40 @@ fn test_symlink() { {abs_path}/symlink/three {abs_path}/symlink/three/d.foo {abs_path}/symlink/three/directory_foo", - abs_path = abs_path + abs_path = &abs_path + ), + ); + + let root = te.system_root(); + let prefix = escape(&root.to_string_lossy()); + + te.assert_output_subdirectory( + "symlink", + &[ + "--absolute-path", + "--full-path", + &format!("^{prefix}.*three", prefix = prefix), + ], + &format!( + "{abs_path}/{dir}/three + {abs_path}/{dir}/three/d.foo + {abs_path}/{dir}/three/directory_foo", + dir = if cfg!(windows) { "symlink" } else { "one/two" }, + abs_path = &abs_path + ), + ); + + te.assert_output( + &[ + "--full-path", + &format!("^{prefix}.*symlink.*three", prefix = prefix), + &format!("{abs_path}/symlink", abs_path = abs_path), + ], + &format!( + "{abs_path}/symlink/three + {abs_path}/symlink/three/d.foo + {abs_path}/symlink/three/directory_foo", + abs_path = &abs_path ), ); }