1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

Merge pull request #2077 from tertsdiepraam/ls/dereference-command-line

ls: dereference command line
This commit is contained in:
Sylvestre Ledru 2021-04-17 13:31:52 +02:00 committed by GitHub
commit 481d1ee659
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 335 additions and 34 deletions

View file

@ -120,6 +120,11 @@ pub mod options {
pub static FILE_TYPE: &str = "file-type"; pub static FILE_TYPE: &str = "file-type";
pub static CLASSIFY: &str = "classify"; pub static CLASSIFY: &str = "classify";
} }
pub mod dereference {
pub static ALL: &str = "dereference";
pub static ARGS: &str = "dereference-command-line";
pub static DIR_ARGS: &str = "dereference-command-line-symlink-to-dir";
}
pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars"; pub static HIDE_CONTROL_CHARS: &str = "hide-control-chars";
pub static SHOW_CONTROL_CHARS: &str = "show-control-chars"; pub static SHOW_CONTROL_CHARS: &str = "show-control-chars";
pub static WIDTH: &str = "width"; pub static WIDTH: &str = "width";
@ -134,7 +139,6 @@ pub mod options {
pub static FILE_TYPE: &str = "file-type"; pub static FILE_TYPE: &str = "file-type";
pub static SLASH: &str = "p"; pub static SLASH: &str = "p";
pub static INODE: &str = "inode"; pub static INODE: &str = "inode";
pub static DEREFERENCE: &str = "dereference";
pub static REVERSE: &str = "reverse"; pub static REVERSE: &str = "reverse";
pub static RECURSIVE: &str = "recursive"; pub static RECURSIVE: &str = "recursive";
pub static COLOR: &str = "color"; pub static COLOR: &str = "color";
@ -180,6 +184,13 @@ enum Time {
Change, Change,
} }
enum Dereference {
None,
DirArgs,
Args,
All,
}
#[derive(PartialEq, Eq)] #[derive(PartialEq, Eq)]
enum IndicatorStyle { enum IndicatorStyle {
None, None,
@ -194,7 +205,7 @@ struct Config {
sort: Sort, sort: Sort,
recursive: bool, recursive: bool,
reverse: bool, reverse: bool,
dereference: bool, dereference: Dereference,
ignore_patterns: GlobSet, ignore_patterns: GlobSet,
size_format: SizeFormat, size_format: SizeFormat,
directory: bool, directory: bool,
@ -483,13 +494,28 @@ impl Config {
let ignore_patterns = ignore_patterns.build().unwrap(); let ignore_patterns = ignore_patterns.build().unwrap();
let dereference = if options.is_present(options::dereference::ALL) {
Dereference::All
} else if options.is_present(options::dereference::ARGS) {
Dereference::Args
} else if options.is_present(options::dereference::DIR_ARGS) {
Dereference::DirArgs
} else if options.is_present(options::DIRECTORY)
|| indicator_style == IndicatorStyle::Classify
|| format == Format::Long
{
Dereference::None
} else {
Dereference::DirArgs
};
Config { Config {
format, format,
files, files,
sort, sort,
recursive: options.is_present(options::RECURSIVE), recursive: options.is_present(options::RECURSIVE),
reverse: options.is_present(options::REVERSE), reverse: options.is_present(options::REVERSE),
dereference: options.is_present(options::DEREFERENCE), dereference,
ignore_patterns, ignore_patterns,
size_format, size_format,
directory: options.is_present(options::DIRECTORY), directory: options.is_present(options::DIRECTORY),
@ -820,6 +846,48 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
]) ])
) )
// Dereferencing
.arg(
Arg::with_name(options::dereference::ALL)
.short("L")
.long(options::dereference::ALL)
.help(
"When showing file information for a symbolic link, show information for the \
file the link references rather than the link itself.",
)
.overrides_with_all(&[
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
)
.arg(
Arg::with_name(options::dereference::DIR_ARGS)
.long(options::dereference::DIR_ARGS)
.help(
"Do not dereference symlinks except when they link to directories and are \
given as command line arguments.",
)
.overrides_with_all(&[
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
)
.arg(
Arg::with_name(options::dereference::ARGS)
.short("H")
.long(options::dereference::ARGS)
.help(
"Do not dereference symlinks except when given as command line arguments.",
)
.overrides_with_all(&[
options::dereference::ALL,
options::dereference::DIR_ARGS,
options::dereference::ARGS,
])
)
// Long format options // Long format options
.arg( .arg(
Arg::with_name(options::NO_GROUP) Arg::with_name(options::NO_GROUP)
@ -878,15 +946,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::INODE) .long(options::INODE)
.help("print the index number of each file"), .help("print the index number of each file"),
) )
.arg(
Arg::with_name(options::DEREFERENCE)
.short("L")
.long(options::DEREFERENCE)
.help(
"When showing file information for a symbolic link, show information for the \
file the link references rather than the link itself.",
),
)
.arg( .arg(
Arg::with_name(options::REVERSE) Arg::with_name(options::REVERSE)
.short("r") .short("r")
@ -994,26 +1053,32 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
has_failed = true; has_failed = true;
continue; continue;
} }
let mut dir = false;
if p.is_dir() && !config.directory { let show_dir_contents = if !config.directory {
dir = true; match config.dereference {
if config.format == Format::Long && !config.dereference { Dereference::None => {
if let Ok(md) = p.symlink_metadata() { if let Ok(md) = p.symlink_metadata() {
if md.file_type().is_symlink() && !p.ends_with("/") { md.is_dir()
dir = false; } else {
show_error!("'{}': {}", &loc, "No such file or directory");
has_failed = true;
continue;
} }
} }
_ => p.is_dir(),
} }
} } else {
if dir { false
};
if show_dir_contents {
dirs.push(p); dirs.push(p);
} else { } else {
files.push(p); files.push(p);
} }
} }
sort_entries(&mut files, &config); sort_entries(&mut files, &config);
display_items(&files, None, &config); display_items(&files, None, &config, true);
sort_entries(&mut dirs, &config); sort_entries(&mut dirs, &config);
for dir in dirs { for dir in dirs {
@ -1033,14 +1098,15 @@ fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) {
match config.sort { match config.sort {
Sort::Time => entries.sort_by_key(|k| { Sort::Time => entries.sort_by_key(|k| {
Reverse( Reverse(
get_metadata(k, config) get_metadata(k, false)
.ok() .ok()
.and_then(|md| get_system_time(&md, config)) .and_then(|md| get_system_time(&md, config))
.unwrap_or(UNIX_EPOCH), .unwrap_or(UNIX_EPOCH),
) )
}), }),
Sort::Size => entries Sort::Size => {
.sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))), entries.sort_by_key(|k| Reverse(get_metadata(k, false).map(|md| md.len()).unwrap_or(0)))
}
// The default sort in GNU ls is case insensitive // The default sort in GNU ls is case insensitive
Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()), Sort::Name => entries.sort_by_key(|k| k.to_string_lossy().to_lowercase()),
Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)), Sort::Version => entries.sort_by(|a, b| version_cmp::version_cmp(a, b)),
@ -1089,9 +1155,9 @@ fn enter_directory(dir: &Path, config: &Config) {
let mut display_entries = entries.clone(); let mut display_entries = entries.clone();
display_entries.insert(0, dir.join("..")); display_entries.insert(0, dir.join(".."));
display_entries.insert(0, dir.join(".")); display_entries.insert(0, dir.join("."));
display_items(&display_entries, Some(dir), config); display_items(&display_entries, Some(dir), config, false);
} else { } else {
display_items(&entries, Some(dir), config); display_items(&entries, Some(dir), config, false);
} }
if config.recursive { if config.recursive {
@ -1102,8 +1168,8 @@ fn enter_directory(dir: &Path, config: &Config) {
} }
} }
fn get_metadata(entry: &Path, config: &Config) -> std::io::Result<Metadata> { fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
if config.dereference { if dereference {
entry.metadata().or_else(|_| entry.symlink_metadata()) entry.metadata().or_else(|_| entry.symlink_metadata())
} else { } else {
entry.symlink_metadata() entry.symlink_metadata()
@ -1111,7 +1177,7 @@ fn get_metadata(entry: &Path, config: &Config) -> std::io::Result<Metadata> {
} }
fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) { fn display_dir_entry_size(entry: &Path, config: &Config) -> (usize, usize) {
if let Ok(md) = get_metadata(entry, config) { if let Ok(md) = get_metadata(entry, false) {
( (
display_symlink_count(&md).len(), display_symlink_count(&md).len(),
display_file_size(&md, config).len(), display_file_size(&md, config).len(),
@ -1125,7 +1191,7 @@ fn pad_left(string: String, count: usize) -> String {
format!("{:>width$}", string, width = count) format!("{:>width$}", string, width = count)
} }
fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) { fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config, command_line: bool) {
if config.format == Format::Long { if config.format == Format::Long {
let (mut max_links, mut max_size) = (1, 1); let (mut max_links, mut max_size) = (1, 1);
for item in items { for item in items {
@ -1134,11 +1200,11 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) {
max_size = size.max(max_size); max_size = size.max(max_size);
} }
for item in items { for item in items {
display_item_long(item, strip, max_links, max_size, config); display_item_long(item, strip, max_links, max_size, config, command_line);
} }
} else { } else {
let names = items.iter().filter_map(|i| { let names = items.iter().filter_map(|i| {
let md = get_metadata(i, config); let md = get_metadata(i, false);
match md { match md {
Err(e) => { Err(e) => {
let filename = get_file_name(i, strip); let filename = get_file_name(i, strip);
@ -1210,8 +1276,26 @@ fn display_item_long(
max_links: usize, max_links: usize,
max_size: usize, max_size: usize,
config: &Config, config: &Config,
command_line: bool,
) { ) {
let md = match get_metadata(item, config) { let dereference = match &config.dereference {
Dereference::All => true,
Dereference::Args => command_line,
Dereference::DirArgs => {
if command_line {
if let Ok(md) = item.metadata() {
md.is_dir()
} else {
false
}
} else {
false
}
}
Dereference::None => false,
};
let md = match get_metadata(item, dereference) {
Err(e) => { Err(e) => {
let filename = get_file_name(&item, strip); let filename = get_file_name(&item, strip);
show_error!("{}: {}", filename, e); show_error!("{}: {}", filename, e);

View file

@ -5,6 +5,7 @@ use crate::common::util::*;
extern crate regex; extern crate regex;
use self::regex::Regex; use self::regex::Regex;
use std::path::Path;
use std::thread::sleep; use std::thread::sleep;
use std::time::Duration; use std::time::Duration;
@ -1314,3 +1315,219 @@ fn test_ls_ignore_hide() {
.stderr_contains(&"Invalid pattern") .stderr_contains(&"Invalid pattern")
.stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n"); .stdout_is("CONTRIBUTING.md\nREADME.md\nREADMECAREFULLY.md\nsome_other_file\n");
} }
#[test]
fn test_ls_directory() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("some_dir");
at.symlink_dir("some_dir", "sym_dir");
at.touch(Path::new("some_dir").join("nested_file").to_str().unwrap());
scene
.ucmd()
.arg("some_dir")
.succeeds()
.stdout_is("nested_file\n");
scene
.ucmd()
.arg("--directory")
.arg("some_dir")
.succeeds()
.stdout_is("some_dir\n");
scene
.ucmd()
.arg("sym_dir")
.succeeds()
.stdout_is("nested_file\n");
}
#[test]
fn test_ls_deref_command_line() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("some_file");
at.symlink_file("some_file", "sym_file");
scene
.ucmd()
.arg("sym_file")
.succeeds()
.stdout_is("sym_file\n");
// -l changes the default to no dereferencing
scene
.ucmd()
.arg("-l")
.arg("sym_file")
.succeeds()
.stdout_contains("sym_file ->");
scene
.ucmd()
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_file")
.succeeds()
.stdout_is("sym_file\n");
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_file")
.succeeds()
.stdout_contains("sym_file ->");
scene
.ucmd()
.arg("--dereference-command-line")
.arg("sym_file")
.succeeds()
.stdout_is("sym_file\n");
let result = scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line")
.arg("sym_file")
.succeeds();
assert!(!result.stdout_str().contains("->"));
let result = scene.ucmd().arg("-lH").arg("sym_file").succeeds();
assert!(!result.stdout_str().contains("sym_file ->"));
// If the symlink is not a command line argument, it must be shown normally
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line")
.succeeds()
.stdout_contains("sym_file ->");
}
#[test]
fn test_ls_deref_command_line_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("some_dir");
at.symlink_dir("some_dir", "sym_dir");
at.touch(Path::new("some_dir").join("nested_file").to_str().unwrap());
scene
.ucmd()
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
scene
.ucmd()
.arg("-l")
.arg("sym_dir")
.succeeds()
.stdout_contains("sym_dir ->");
scene
.ucmd()
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
scene
.ucmd()
.arg("--dereference-command-line")
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line")
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
scene
.ucmd()
.arg("-lH")
.arg("sym_dir")
.succeeds()
.stdout_contains("nested_file");
// If the symlink is not a command line argument, it must be shown normally
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line")
.succeeds()
.stdout_contains("sym_dir ->");
scene
.ucmd()
.arg("-lH")
.succeeds()
.stdout_contains("sym_dir ->");
scene
.ucmd()
.arg("-l")
.arg("--dereference-command-line-symlink-to-dir")
.succeeds()
.stdout_contains("sym_dir ->");
// --directory does not dereference anything by default
scene
.ucmd()
.arg("-l")
.arg("--directory")
.arg("sym_dir")
.succeeds()
.stdout_contains("sym_dir ->");
let result = scene
.ucmd()
.arg("-l")
.arg("--directory")
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_dir")
.succeeds();
assert!(!result.stdout_str().ends_with("sym_dir"));
// --classify does not dereference anything by default
scene
.ucmd()
.arg("-l")
.arg("--directory")
.arg("sym_dir")
.succeeds()
.stdout_contains("sym_dir ->");
let result = scene
.ucmd()
.arg("-l")
.arg("--directory")
.arg("--dereference-command-line-symlink-to-dir")
.arg("sym_dir")
.succeeds();
assert!(!result.stdout_str().ends_with("sym_dir"));
}