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

View file

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

View file

@ -14,7 +14,7 @@ use nu_parser::ExpandContext;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
use rustyline::completion::FilenameCompleter; use rustyline::completion::FilenameCompleter;
use rustyline::hint::{Hinter, HistoryHinter}; use rustyline::hint::{Hinter, HistoryHinter};
use std::path::PathBuf; use std::path::{Path, PathBuf};
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use trash as SendToTrash; use trash as SendToTrash;
@ -93,6 +93,7 @@ impl Shell for FilesystemShell {
&self, &self,
LsArgs { LsArgs {
path, path,
all,
full, full,
short_names, short_names,
with_symlink_targets, with_symlink_targets,
@ -107,7 +108,7 @@ impl Shell for FilesystemShell {
let p_tag = p.tag; let p_tag = p.tag;
let mut p = p.item; let mut p = p.item;
if p.is_dir() { if p.is_dir() {
if is_dir_empty(&p) { if is_empty_dir(&p) {
return Ok(OutputStream::empty()); return Ok(OutputStream::empty());
} }
p.push("*"); p.push("*");
@ -115,7 +116,7 @@ impl Shell for FilesystemShell {
(p, p_tag) (p, p_tag)
} }
None => { None => {
if is_dir_empty(&self.path().into()) { if is_empty_dir(&self.path()) {
return Ok(OutputStream::empty()); return Ok(OutputStream::empty());
} else { } else {
(PathBuf::from("./*"), context.name.clone()) (PathBuf::from("./*"), context.name.clone())
@ -123,10 +124,8 @@ impl Shell for FilesystemShell {
} }
}; };
let mut paths = match glob::glob(&path.to_string_lossy()) { let mut paths = glob::glob(&path.to_string_lossy())
Ok(g) => Ok(g), .map_err(|e| ShellError::labeled_error("Glob error", e.to_string(), &p_tag))?
Err(e) => Err(ShellError::labeled_error("Glob error", e.msg, &p_tag)),
}?
.peekable(); .peekable();
if paths.peek().is_none() { if paths.peek().is_none() {
@ -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 { for path in paths {
// Handle CTRL+C presence
if ctrl_c.load(Ordering::SeqCst) { if ctrl_c.load(Ordering::SeqCst) {
break; break;
} }
match path {
Ok(p) => match std::fs::symlink_metadata(&p) { // Map GlobError to ShellError and gracefully try to unwrap the path
Ok(m) => { let path = path.map_err(|e| ShellError::from(e.into_error()))?;
match dir_entry_dict(&p, Some(&m), name_tag.clone(), full, short_names, with_symlink_targets) {
Ok(d) => yield ReturnSuccess::value(d), // Skip if '--all/-a' flag is present and this path is hidden
Err(e) => yield Err(e) if !all && is_hidden_dir(&path) {
} continue;
}
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)
} }
// 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)
}, },
_ => yield Err(ShellError::from(e)) }?;
}
} // Build dict entry for this path and possibly using some metadata.
} // Map the possible dict entry into a Value, gracefully unwrap it
Err(e) => yield Err(e.into_error().into()), // 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()) Ok(stream.to_output_stream())
} }
@ -1129,9 +1146,30 @@ impl Shell for FilesystemShell {
} }
} }
fn is_dir_empty(d: &PathBuf) -> bool { fn is_empty_dir(dir: impl AsRef<Path>) -> bool {
match d.read_dir() { match dir.as_ref().read_dir() {
Err(_e) => true, Err(_) => true,
Ok(mut s) => s.next().is_none(), 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(); cwd.pop();
} else { } else {
match target.to_str() { 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), Some(x) if x == '/' => cwd = PathBuf::from(target),
_ => cwd.push(target), _ => cwd.push(target),
}, },

View file

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

View file

@ -137,7 +137,7 @@ pub struct ExpandContext<'context> {
impl<'context> ExpandContext<'context> { impl<'context> ExpandContext<'context> {
pub(crate) fn homedir(&self) -> Option<&Path> { 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 { 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, _) = start_predicate(input)?;
let (input, _) = many0(next_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 { match next_char {
Some('.') => {} Some('.') => {}
@ -609,7 +609,7 @@ fn tight<'a>(
let (input, tail) = opt(alt((many1(range_continuation), many1(dot_member))))(input)?; 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 is_boundary(*next_char) {
if let Some(tail) = tail { if let Some(tail) = tail {