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 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 SHOW_CONTROL_CHARS: &str = "show-control-chars";
pub static WIDTH: &str = "width";
@ -134,7 +139,6 @@ pub mod options {
pub static FILE_TYPE: &str = "file-type";
pub static SLASH: &str = "p";
pub static INODE: &str = "inode";
pub static DEREFERENCE: &str = "dereference";
pub static REVERSE: &str = "reverse";
pub static RECURSIVE: &str = "recursive";
pub static COLOR: &str = "color";
@ -180,6 +184,13 @@ enum Time {
Change,
}
enum Dereference {
None,
DirArgs,
Args,
All,
}
#[derive(PartialEq, Eq)]
enum IndicatorStyle {
None,
@ -194,7 +205,7 @@ struct Config {
sort: Sort,
recursive: bool,
reverse: bool,
dereference: bool,
dereference: Dereference,
ignore_patterns: GlobSet,
size_format: SizeFormat,
directory: bool,
@ -483,13 +494,28 @@ impl Config {
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 {
format,
files,
sort,
recursive: options.is_present(options::RECURSIVE),
reverse: options.is_present(options::REVERSE),
dereference: options.is_present(options::DEREFERENCE),
dereference,
ignore_patterns,
size_format,
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
.arg(
Arg::with_name(options::NO_GROUP)
@ -878,15 +946,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 {
.long(options::INODE)
.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::with_name(options::REVERSE)
.short("r")
@ -994,26 +1053,32 @@ fn list(locs: Vec<String>, config: Config) -> i32 {
has_failed = true;
continue;
}
let mut dir = false;
if p.is_dir() && !config.directory {
dir = true;
if config.format == Format::Long && !config.dereference {
if let Ok(md) = p.symlink_metadata() {
if md.file_type().is_symlink() && !p.ends_with("/") {
dir = false;
let show_dir_contents = if !config.directory {
match config.dereference {
Dereference::None => {
if let Ok(md) = p.symlink_metadata() {
md.is_dir()
} else {
show_error!("'{}': {}", &loc, "No such file or directory");
has_failed = true;
continue;
}
}
_ => p.is_dir(),
}
}
if dir {
} else {
false
};
if show_dir_contents {
dirs.push(p);
} else {
files.push(p);
}
}
sort_entries(&mut files, &config);
display_items(&files, None, &config);
display_items(&files, None, &config, true);
sort_entries(&mut dirs, &config);
for dir in dirs {
@ -1033,14 +1098,15 @@ fn sort_entries(entries: &mut Vec<PathBuf>, config: &Config) {
match config.sort {
Sort::Time => entries.sort_by_key(|k| {
Reverse(
get_metadata(k, config)
get_metadata(k, false)
.ok()
.and_then(|md| get_system_time(&md, config))
.unwrap_or(UNIX_EPOCH),
)
}),
Sort::Size => entries
.sort_by_key(|k| Reverse(get_metadata(k, config).map(|md| md.len()).unwrap_or(0))),
Sort::Size => {
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
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)),
@ -1089,9 +1155,9 @@ fn enter_directory(dir: &Path, config: &Config) {
let mut display_entries = entries.clone();
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 {
display_items(&entries, Some(dir), config);
display_items(&entries, Some(dir), config, false);
}
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> {
if config.dereference {
fn get_metadata(entry: &Path, dereference: bool) -> std::io::Result<Metadata> {
if dereference {
entry.metadata().or_else(|_| entry.symlink_metadata())
} else {
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) {
if let Ok(md) = get_metadata(entry, config) {
if let Ok(md) = get_metadata(entry, false) {
(
display_symlink_count(&md).len(),
display_file_size(&md, config).len(),
@ -1125,7 +1191,7 @@ fn pad_left(string: String, count: usize) -> String {
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 {
let (mut max_links, mut max_size) = (1, 1);
for item in items {
@ -1134,11 +1200,11 @@ fn display_items(items: &[PathBuf], strip: Option<&Path>, config: &Config) {
max_size = size.max(max_size);
}
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 {
let names = items.iter().filter_map(|i| {
let md = get_metadata(i, config);
let md = get_metadata(i, false);
match md {
Err(e) => {
let filename = get_file_name(i, strip);
@ -1210,8 +1276,26 @@ fn display_item_long(
max_links: usize,
max_size: usize,
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) => {
let filename = get_file_name(&item, strip);
show_error!("{}: {}", filename, e);

View file

@ -5,6 +5,7 @@ use crate::common::util::*;
extern crate regex;
use self::regex::Regex;
use std::path::Path;
use std::thread::sleep;
use std::time::Duration;
@ -1314,3 +1315,219 @@ fn test_ls_ignore_hide() {
.stderr_contains(&"Invalid pattern")
.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"));
}