mirror of
https://github.com/uutils/coreutils
synced 2024-11-17 02:08:09 +00:00
ls: dereference command line
This commit is contained in:
parent
9ae4928b7b
commit
5c28ac1b0d
2 changed files with 273 additions and 34 deletions
|
@ -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,
|
||||
|
@ -482,13 +493,27 @@ 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
|
||||
{
|
||||
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),
|
||||
|
@ -819,6 +844,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)
|
||||
|
@ -877,15 +944,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")
|
||||
|
@ -993,26 +1051,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 {
|
||||
|
@ -1032,14 +1096,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(version_cmp::version_cmp),
|
||||
|
@ -1088,9 +1153,9 @@ fn enter_directory(dir: &PathBuf, 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 {
|
||||
|
@ -1101,8 +1166,8 @@ fn enter_directory(dir: &PathBuf, config: &Config) {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result<Metadata> {
|
||||
if config.dereference {
|
||||
fn get_metadata(entry: &PathBuf, dereference: bool) -> std::io::Result<Metadata> {
|
||||
if dereference {
|
||||
entry.metadata().or_else(|_| entry.symlink_metadata())
|
||||
} else {
|
||||
entry.symlink_metadata()
|
||||
|
@ -1110,7 +1175,7 @@ fn get_metadata(entry: &PathBuf, config: &Config) -> std::io::Result<Metadata> {
|
|||
}
|
||||
|
||||
fn display_dir_entry_size(entry: &PathBuf, 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(),
|
||||
|
@ -1124,7 +1189,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 {
|
||||
|
@ -1133,11 +1198,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);
|
||||
|
@ -1209,8 +1274,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);
|
||||
|
|
|
@ -1314,3 +1314,159 @@ 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("some_dir/nested_file");
|
||||
|
||||
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("-l")
|
||||
.arg("sym_file")
|
||||
.succeeds()
|
||||
.stdout_contains("sym_file ->");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.arg("--dereference-command-line-symlink-to-dir")
|
||||
.arg("sym_file")
|
||||
.succeeds()
|
||||
.stdout_contains("sym_file ->");
|
||||
|
||||
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("some_dir/nested_file");
|
||||
|
||||
scene
|
||||
.ucmd()
|
||||
.arg("-l")
|
||||
.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("-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"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue