Added new flag '--all/-a' for Ls, also refactor some code (#1483)

* Utility function to detect hidden folders.
Implemented for Unix and Windows.

* Rename function argument.

* Revert "Rename function argument."

This reverts commit e7ab70f0f0.

* Add flag '--all/-a' to Ls

* Rename function argument.

* Check if flag '--all/-a' is present and path is hidden.
Replace match with map_err for glob result.
Remove redundancy in stream body.
Included comments on new stream body.
Replace async_stream::stream with async_stream::try_stream.
Minor tweaks to is_empty_dir.
Fix and refactor is_hidden_dir.

* Fix "implicit" bool coerse

* Fixed clippy errors
This commit is contained in:
Kevin DCR
2020-03-13 12:27:04 -05:00
committed by GitHub
parent 5ca9e12b7f
commit b6363f3ce1
7 changed files with 80 additions and 40 deletions

View File

@ -14,7 +14,7 @@ use nu_parser::ExpandContext;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
use rustyline::completion::FilenameCompleter;
use rustyline::hint::{Hinter, HistoryHinter};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering;
use trash as SendToTrash;
@ -93,6 +93,7 @@ impl Shell for FilesystemShell {
&self,
LsArgs {
path,
all,
full,
short_names,
with_symlink_targets,
@ -107,7 +108,7 @@ impl Shell for FilesystemShell {
let p_tag = p.tag;
let mut p = p.item;
if p.is_dir() {
if is_dir_empty(&p) {
if is_empty_dir(&p) {
return Ok(OutputStream::empty());
}
p.push("*");
@ -115,7 +116,7 @@ impl Shell for FilesystemShell {
(p, p_tag)
}
None => {
if is_dir_empty(&self.path().into()) {
if is_empty_dir(&self.path()) {
return Ok(OutputStream::empty());
} else {
(PathBuf::from("./*"), context.name.clone())
@ -123,11 +124,9 @@ impl Shell for FilesystemShell {
}
};
let mut paths = match glob::glob(&path.to_string_lossy()) {
Ok(g) => Ok(g),
Err(e) => Err(ShellError::labeled_error("Glob error", e.msg, &p_tag)),
}?
.peekable();
let mut paths = glob::glob(&path.to_string_lossy())
.map_err(|e| ShellError::labeled_error("Glob error", e.to_string(), &p_tag))?
.peekable();
if paths.peek().is_none() {
return Err(ShellError::labeled_error(
@ -137,35 +136,53 @@ impl Shell for FilesystemShell {
));
}
let stream = async_stream! {
// Generated stream: impl Stream<Item = Result<ReturnSuccess, ShellError>
let stream = async_stream::try_stream! {
for path in paths {
// Handle CTRL+C presence
if ctrl_c.load(Ordering::SeqCst) {
break;
}
match path {
Ok(p) => match std::fs::symlink_metadata(&p) {
Ok(m) => {
match dir_entry_dict(&p, Some(&m), name_tag.clone(), full, short_names, with_symlink_targets) {
Ok(d) => yield ReturnSuccess::value(d),
Err(e) => yield Err(e)
}
}
Err(e) => {
match e.kind() {
PermissionDenied => {
match dir_entry_dict(&p, None, name_tag.clone(), full, short_names, with_symlink_targets) {
Ok(d) => yield ReturnSuccess::value(d),
Err(e) => yield Err(e)
}
},
_ => yield Err(ShellError::from(e))
}
}
}
Err(e) => yield Err(e.into_error().into()),
// Map GlobError to ShellError and gracefully try to unwrap the path
let path = path.map_err(|e| ShellError::from(e.into_error()))?;
// Skip if '--all/-a' flag is present and this path is hidden
if !all && is_hidden_dir(&path) {
continue;
}
// Get metadata from current path, if we don't have enough
// permissions to stat on file don't use any metadata, otherwise
// return the error and gracefully unwrap metadata (which yields
// Option<Metadata>)
let metadata = match std::fs::symlink_metadata(&path) {
Ok(metadata) => Ok(Some(metadata)),
Err(e) => if let PermissionDenied = e.kind() {
Ok(None)
} else {
Err(e)
},
}?;
// Build dict entry for this path and possibly using some metadata.
// Map the possible dict entry into a Value, gracefully unwrap it
// with '?'
let entry = dir_entry_dict(
&path,
metadata.as_ref(),
name_tag.clone(),
full,
short_names,
with_symlink_targets
)
.map(|entry| ReturnSuccess::Value(entry.into()))?;
// Finally yield the generated entry that was mapped to Value
yield entry;
}
};
Ok(stream.to_output_stream())
}
@ -1129,9 +1146,30 @@ impl Shell for FilesystemShell {
}
}
fn is_dir_empty(d: &PathBuf) -> bool {
match d.read_dir() {
Err(_e) => true,
fn is_empty_dir(dir: impl AsRef<Path>) -> bool {
match dir.as_ref().read_dir() {
Err(_) => true,
Ok(mut s) => s.next().is_none(),
}
}
fn is_hidden_dir(dir: impl AsRef<Path>) -> bool {
cfg_if::cfg_if! {
if #[cfg(windows)] {
use std::os::windows::fs::MetadataExt;
if let Ok(metadata) = dir.as_ref().metadata() {
let attributes = metadata.file_attributes();
// https://docs.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants
(attributes & 0x2) != 0
} else {
false
}
} else {
dir.as_ref()
.file_name()
.map(|name| name.to_string_lossy().starts_with('.'))
.unwrap_or(false)
}
}
}