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
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 80 additions and 40 deletions

View file

@ -10,6 +10,7 @@ pub struct Ls;
#[derive(Deserialize)]
pub struct LsArgs {
pub path: Option<Tagged<PathBuf>>,
pub all: bool,
pub full: bool,
#[serde(rename = "short-names")]
pub short_names: bool,
@ -29,6 +30,7 @@ impl PerItemCommand for Ls {
SyntaxShape::Pattern,
"a path to get the directory contents from",
)
.switch("all", "also show hidden files", Some('a'))
.switch(
"full",
"list all available columns for each entry",

View file

@ -36,13 +36,13 @@ pub(crate) fn dir_entry_dict(
metadata: Option<&std::fs::Metadata>,
tag: impl Into<Tag>,
full: bool,
name_only: bool,
short_name: bool,
with_symlink_targets: bool,
) -> Result<Value, ShellError> {
let tag = tag.into();
let mut dict = TaggedDictBuilder::new(&tag);
let name = if name_only {
let name = if short_name {
filename.file_name().and_then(|s| s.to_str())
} else {
filename.to_str()

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)
}
}
}

View file

@ -162,7 +162,7 @@ impl Shell for HelpShell {
cwd.pop();
} else {
match target.to_str() {
Some(target) => match target.chars().nth(0) {
Some(target) => match target.chars().next() {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target),
},

View file

@ -144,7 +144,7 @@ impl Shell for ValueShell {
cwd = PathBuf::from(&self.last_path);
} else {
match target.to_str() {
Some(target) => match target.chars().nth(0) {
Some(target) => match target.chars().next() {
Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target),
},

View file

@ -137,7 +137,7 @@ pub struct ExpandContext<'context> {
impl<'context> ExpandContext<'context> {
pub(crate) fn homedir(&self) -> Option<&Path> {
self.homedir.as_ref().map(|h| h.as_path())
self.homedir.as_deref()
}
pub(crate) fn source(&self) -> &'context Text {

View file

@ -372,7 +372,7 @@ fn word<'a, T, U, V>(
let (input, _) = start_predicate(input)?;
let (input, _) = many0(next_predicate)(input)?;
let next_char = &input.fragment.chars().nth(0);
let next_char = &input.fragment.chars().next();
match next_char {
Some('.') => {}
@ -609,7 +609,7 @@ fn tight<'a>(
let (input, tail) = opt(alt((many1(range_continuation), many1(dot_member))))(input)?;
let next_char = &input.fragment.chars().nth(0);
let next_char = &input.fragment.chars().next();
if is_boundary(*next_char) {
if let Some(tail) = tail {