mirror of
https://github.com/sharkdp/fd
synced 2024-09-05 00:03:20 +00:00
Refactor path handling (fixes #113)
* Fix path check * Fix full path matching * Allow more simple driver names in Windows tests * Factor out special is_dir() check for "." and ".."
This commit is contained in:
parent
37def9bb83
commit
8d85debc12
|
@ -9,7 +9,6 @@ use super::TokenizedCommand;
|
||||||
/// be executed, and this process will continue until the receiver's sender has closed.
|
/// be executed, and this process will continue until the receiver's sender has closed.
|
||||||
pub fn job(
|
pub fn job(
|
||||||
rx: Arc<Mutex<Receiver<PathBuf>>>,
|
rx: Arc<Mutex<Receiver<PathBuf>>>,
|
||||||
base: Arc<Option<PathBuf>>,
|
|
||||||
cmd: Arc<TokenizedCommand>,
|
cmd: Arc<TokenizedCommand>,
|
||||||
out_perm: Arc<Mutex<()>>,
|
out_perm: Arc<Mutex<()>>,
|
||||||
) {
|
) {
|
||||||
|
@ -23,12 +22,7 @@ pub fn job(
|
||||||
// Obtain the next path from the receiver, else if the channel
|
// Obtain the next path 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 value: PathBuf = match lock.recv() {
|
||||||
Ok(value) => {
|
Ok(value) => value,
|
||||||
match *base {
|
|
||||||
Some(ref base) => base.join(&value),
|
|
||||||
None => value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => break,
|
Err(_) => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -109,6 +109,8 @@ impl TokenizedCommand {
|
||||||
input: &Path,
|
input: &Path,
|
||||||
out_perm: Arc<Mutex<()>>,
|
out_perm: Arc<Mutex<()>>,
|
||||||
) -> CommandTicket<'a> {
|
) -> CommandTicket<'a> {
|
||||||
|
let input = input.strip_prefix(".").unwrap_or(input);
|
||||||
|
|
||||||
for token in &self.tokens {
|
for token in &self.tokens {
|
||||||
match *token {
|
match *token {
|
||||||
Token::Basename => *command += basename(&input.to_string_lossy()),
|
Token::Basename => *command += basename(&input.to_string_lossy()),
|
||||||
|
|
|
@ -1,50 +1,18 @@
|
||||||
|
use std::env::current_dir;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Get a relative path with respect to a certain base path.
|
pub fn path_absolute_form(path: &Path) -> io::Result<PathBuf> {
|
||||||
/// See: https://stackoverflow.com/a/39343127/704831
|
if path.is_absolute() {
|
||||||
pub fn path_relative_from(path: &Path, base: &Path) -> Option<PathBuf> {
|
Ok(path.to_path_buf())
|
||||||
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)))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
let mut ita = path.components();
|
let path = path.strip_prefix(".").unwrap_or(path);
|
||||||
let mut itb = base.components();
|
current_dir().map(|path_buf| path_buf.join(path))
|
||||||
let mut comps: Vec<Component> = 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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
pub fn absolute_path(path: &Path) -> io::Result<PathBuf> {
|
||||||
let path_buf = path.canonicalize()?;
|
let path_buf = path_absolute_form(path)?;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let path_buf = Path::new(path_buf.as_path().to_string_lossy().trim_left_matches(
|
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<PathBuf> {
|
||||||
|
|
||||||
Ok(path_buf)
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,13 +7,6 @@ use lscolors::LsColors;
|
||||||
use walk::FileType;
|
use walk::FileType;
|
||||||
use regex_syntax::{Expr, ExprBuilder};
|
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.
|
/// Defines how to display search result paths.
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum PathDisplay {
|
pub enum PathDisplay {
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -29,7 +29,7 @@ use atty::Stream;
|
||||||
use regex::RegexBuilder;
|
use regex::RegexBuilder;
|
||||||
|
|
||||||
use exec::TokenizedCommand;
|
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 lscolors::LsColors;
|
||||||
use walk::FileType;
|
use walk::FileType;
|
||||||
|
|
||||||
|
@ -42,8 +42,7 @@ fn main() {
|
||||||
|
|
||||||
// Get the current working directory
|
// Get the current working directory
|
||||||
let current_dir = Path::new(".");
|
let current_dir = Path::new(".");
|
||||||
// .is_dir() is not guarandteed to be intuitively correct for "." and ".."
|
if !fshelper::is_dir(¤t_dir) {
|
||||||
if let Err(_) = current_dir.canonicalize() {
|
|
||||||
error("Error: could not get current directory.");
|
error("Error: could not get current directory.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +51,7 @@ fn main() {
|
||||||
Some(path) => PathBuf::from(path),
|
Some(path) => PathBuf::from(path),
|
||||||
None => current_dir.to_path_buf(),
|
None => current_dir.to_path_buf(),
|
||||||
};
|
};
|
||||||
if let Err(_) = root_dir_buf.canonicalize() {
|
if !fshelper::is_dir(&root_dir_buf) {
|
||||||
error(&format!(
|
error(&format!(
|
||||||
"Error: '{}' is not a directory.",
|
"Error: '{}' is not a directory.",
|
||||||
root_dir_buf.to_string_lossy()
|
root_dir_buf.to_string_lossy()
|
||||||
|
@ -132,21 +131,11 @@ fn main() {
|
||||||
command,
|
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)
|
match RegexBuilder::new(pattern)
|
||||||
.case_insensitive(!config.case_sensitive)
|
.case_insensitive(!config.case_sensitive)
|
||||||
.dot_matches_new_line(true)
|
.dot_matches_new_line(true)
|
||||||
.build() {
|
.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()),
|
Err(err) => error(err.description()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use internal::{FdOptions, PathDisplay};
|
use internal::FdOptions;
|
||||||
use lscolors::LsColors;
|
use lscolors::LsColors;
|
||||||
|
|
||||||
use std::{fs, process};
|
use std::{fs, process};
|
||||||
|
@ -10,23 +10,13 @@ use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
use ansi_term;
|
use ansi_term;
|
||||||
|
|
||||||
pub fn print_entry(base: &Path, entry: &PathBuf, config: &FdOptions) {
|
pub fn print_entry(entry: &PathBuf, config: &FdOptions) {
|
||||||
let path_full = if !entry.as_os_str().is_empty() {
|
let path = entry.strip_prefix(".").unwrap_or(entry);
|
||||||
base.join(entry)
|
|
||||||
} else {
|
|
||||||
base.to_path_buf()
|
|
||||||
};
|
|
||||||
|
|
||||||
let path_to_print = if config.path_display == PathDisplay::Absolute {
|
|
||||||
&path_full
|
|
||||||
} else {
|
|
||||||
entry
|
|
||||||
};
|
|
||||||
|
|
||||||
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(base, path_to_print, config, ls_colors)
|
print_entry_colorized(path, config, ls_colors)
|
||||||
} else {
|
} else {
|
||||||
print_entry_uncolorized(path_to_print, config)
|
print_entry_uncolorized(path, config)
|
||||||
};
|
};
|
||||||
|
|
||||||
if r.is_err() {
|
if r.is_err() {
|
||||||
|
@ -35,12 +25,7 @@ pub fn print_entry(base: &Path, entry: &PathBuf, config: &FdOptions) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_entry_colorized(
|
fn print_entry_colorized(path: &Path, config: &FdOptions, ls_colors: &LsColors) -> io::Result<()> {
|
||||||
base: &Path,
|
|
||||||
path: &Path,
|
|
||||||
config: &FdOptions,
|
|
||||||
ls_colors: &LsColors,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
let default_style = ansi_term::Style::default();
|
let default_style = ansi_term::Style::default();
|
||||||
|
|
||||||
let stdout = io::stdout();
|
let stdout = io::stdout();
|
||||||
|
@ -50,7 +35,7 @@ fn print_entry_colorized(
|
||||||
let mut separator = String::new();
|
let mut separator = String::new();
|
||||||
|
|
||||||
// Full path to the current component.
|
// 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
|
// Traverse the path and colorize each component
|
||||||
for component in path.components() {
|
for component in path.components() {
|
||||||
|
|
34
src/walk.rs
34
src/walk.rs
|
@ -1,6 +1,6 @@
|
||||||
use exec::{self, TokenizedCommand};
|
use exec::{self, TokenizedCommand};
|
||||||
use fshelper;
|
use fshelper;
|
||||||
use internal::{error, FdOptions, PathDisplay};
|
use internal::{error, FdOptions};
|
||||||
use output;
|
use output;
|
||||||
|
|
||||||
use std::path::Path;
|
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
|
/// 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
|
/// jobs in parallel from a given command line and the discovered paths. Otherwise, each
|
||||||
/// path will simply be written to standard output.
|
/// path will simply be written to standard output.
|
||||||
pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions>) {
|
pub fn scan(root: &Path, pattern: Arc<Regex>, config: Arc<FdOptions>) {
|
||||||
let (tx, rx) = channel();
|
let (tx, rx) = channel();
|
||||||
let threads = config.threads;
|
let threads = config.threads;
|
||||||
|
|
||||||
|
@ -54,8 +54,6 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
|
|
||||||
// Spawn the thread that receives all results through the channel.
|
// Spawn the thread that receives all results through the channel.
|
||||||
let rx_config = Arc::clone(&config);
|
let rx_config = Arc::clone(&config);
|
||||||
let rx_base = base.to_owned();
|
|
||||||
let is_absolute = config.path_display == PathDisplay::Absolute;
|
|
||||||
let receiver_thread = thread::spawn(move || {
|
let receiver_thread = thread::spawn(move || {
|
||||||
// This will be set to `Some` if the `--exec` argument was supplied.
|
// This will be set to `Some` if the `--exec` argument was supplied.
|
||||||
if let Some(ref cmd) = rx_config.command {
|
if let Some(ref cmd) = rx_config.command {
|
||||||
|
@ -63,8 +61,6 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
|
|
||||||
let out_perm = Arc::new(Mutex::new(()));
|
let out_perm = Arc::new(Mutex::new(()));
|
||||||
|
|
||||||
let base = Arc::new(if is_absolute { Some(rx_base) } else { None });
|
|
||||||
|
|
||||||
// This is safe because `cmd` will exist beyond the end of this scope.
|
// This is safe because `cmd` will exist beyond the end of this scope.
|
||||||
// It's required to tell Rust that it's safe to share across threads.
|
// It's required to tell Rust that it's safe to share across threads.
|
||||||
let cmd = unsafe { Arc::from_raw(cmd as *const TokenizedCommand) };
|
let cmd = unsafe { Arc::from_raw(cmd as *const TokenizedCommand) };
|
||||||
|
@ -74,11 +70,10 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
for _ in 0..threads {
|
for _ in 0..threads {
|
||||||
let rx = shared_rx.clone();
|
let rx = shared_rx.clone();
|
||||||
let cmd = cmd.clone();
|
let cmd = cmd.clone();
|
||||||
let base = base.clone();
|
|
||||||
let out_perm = out_perm.clone();
|
let out_perm = out_perm.clone();
|
||||||
|
|
||||||
// Spawn a job thread that will listen for and execute inputs.
|
// Spawn a job thread that will listen for and execute inputs.
|
||||||
let handle = thread::spawn(move || exec::job(rx, base, cmd, out_perm));
|
let handle = thread::spawn(move || exec::job(rx, cmd, out_perm));
|
||||||
|
|
||||||
// Push the handle of the spawned thread into the vector for later joining.
|
// Push the handle of the spawned thread into the vector for later joining.
|
||||||
handles.push(handle);
|
handles.push(handle);
|
||||||
|
@ -110,7 +105,7 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
if time::Instant::now() - start > max_buffer_time {
|
if time::Instant::now() - start > max_buffer_time {
|
||||||
// Flush the buffer
|
// Flush the buffer
|
||||||
for v in &buffer {
|
for v in &buffer {
|
||||||
output::print_entry(&rx_base, v, &rx_config);
|
output::print_entry(&v, &rx_config);
|
||||||
}
|
}
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
|
||||||
|
@ -119,7 +114,7 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ReceiverMode::Streaming => {
|
ReceiverMode::Streaming => {
|
||||||
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<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
if !buffer.is_empty() {
|
if !buffer.is_empty() {
|
||||||
buffer.sort();
|
buffer.sort();
|
||||||
for value in buffer {
|
for value in buffer {
|
||||||
output::print_entry(&rx_base, &value, &rx_config);
|
output::print_entry(&value, &rx_config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,7 +132,6 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
|
|
||||||
// Spawn the sender threads.
|
// Spawn the sender threads.
|
||||||
walker.run(|| {
|
walker.run(|| {
|
||||||
let base = base.to_owned();
|
|
||||||
let config = Arc::clone(&config);
|
let config = Arc::clone(&config);
|
||||||
let pattern = Arc::clone(&pattern);
|
let pattern = Arc::clone(&pattern);
|
||||||
let tx_thread = tx.clone();
|
let tx_thread = tx.clone();
|
||||||
|
@ -186,21 +180,19 @@ pub fn scan(root: &Path, pattern: Arc<Regex>, base: &Path, config: Arc<FdOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
let search_str_o = if config.search_full_path {
|
let search_str_o = if config.search_full_path {
|
||||||
Some(entry_path.to_string_lossy())
|
match fshelper::path_absolute_form(&entry_path) {
|
||||||
|
Ok(path_abs_buf) => Some(path_abs_buf.to_string_lossy().into_owned().into()),
|
||||||
|
Err(_) => error("Error: unable to get full path."),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
entry_path.file_name().map(|f| f.to_string_lossy())
|
entry_path.file_name().map(|f| f.to_string_lossy())
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(search_str) = search_str_o {
|
if let Some(search_str) = search_str_o {
|
||||||
pattern.find(&*search_str).map(|_| {
|
if pattern.is_match(&*search_str) {
|
||||||
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."),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: take care of the unwrap call
|
// 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
|
ignore::WalkState::Continue
|
||||||
|
|
|
@ -136,10 +136,16 @@ impl TestEnv {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the root directory for the tests.
|
/// 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()
|
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.
|
/// Assert that calling *fd* with the specified arguments produces the expected output.
|
||||||
pub fn assert_output(&self, args: &[&str], expected: &str) {
|
pub fn assert_output(&self, args: &[&str], expected: &str) {
|
||||||
self.assert_output_subdirectory(".", args, expected)
|
self.assert_output_subdirectory(".", args, expected)
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
//! Integration tests for the CLI interface of fd.
|
//! Integration tests for the CLI interface of fd.
|
||||||
|
|
||||||
|
extern crate regex;
|
||||||
|
|
||||||
mod testenv;
|
mod testenv;
|
||||||
|
|
||||||
use testenv::TestEnv;
|
use testenv::TestEnv;
|
||||||
|
use regex::escape;
|
||||||
|
|
||||||
fn get_absolute_root_path(env: &TestEnv) -> String {
|
fn get_absolute_root_path(env: &TestEnv) -> String {
|
||||||
let path = env.root()
|
let path = env.test_root()
|
||||||
.canonicalize()
|
.canonicalize()
|
||||||
.expect("absolute path")
|
.expect("absolute path")
|
||||||
.to_str()
|
.to_str()
|
||||||
|
@ -177,8 +180,14 @@ fn test_case_insensitive() {
|
||||||
fn test_full_path() {
|
fn test_full_path() {
|
||||||
let te = TestEnv::new();
|
let te = TestEnv::new();
|
||||||
|
|
||||||
|
let root = te.system_root();
|
||||||
|
let prefix = escape(&root.to_string_lossy());
|
||||||
|
|
||||||
te.assert_output(
|
te.assert_output(
|
||||||
&["--full-path", "three.*foo"],
|
&[
|
||||||
|
"--full-path",
|
||||||
|
&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",
|
||||||
);
|
);
|
||||||
|
@ -343,7 +352,7 @@ fn test_absolute_path() {
|
||||||
{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
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -356,7 +365,7 @@ fn test_absolute_path() {
|
||||||
{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
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -369,7 +378,7 @@ fn test_absolute_path() {
|
||||||
{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
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -435,9 +444,13 @@ fn test_symlink() {
|
||||||
// the array pointed to by buf, and return buf. The pathname shall contain no components that
|
// 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.
|
// 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 { "../.." };
|
let parent_parent = if cfg!(windows) { ".." } else { "../.." };
|
||||||
te.assert_output_subdirectory(
|
te.assert_output_subdirectory(
|
||||||
|
@ -462,12 +475,13 @@ fn test_symlink() {
|
||||||
"symlink",
|
"symlink",
|
||||||
&["--absolute-path"],
|
&["--absolute-path"],
|
||||||
&format!(
|
&format!(
|
||||||
"{abs_path}/one/two/c.foo
|
"{abs_path}/{dir}/c.foo
|
||||||
{abs_path}/one/two/C.Foo2
|
{abs_path}/{dir}/C.Foo2
|
||||||
{abs_path}/one/two/three
|
{abs_path}/{dir}/three
|
||||||
{abs_path}/one/two/three/d.foo
|
{abs_path}/{dir}/three/d.foo
|
||||||
{abs_path}/one/two/three/directory_foo",
|
{abs_path}/{dir}/three/directory_foo",
|
||||||
abs_path = abs_path
|
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
|
||||||
{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
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
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
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue