mirror of
https://github.com/nushell/nushell
synced 2024-12-25 12:33:17 +00:00
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:
parent
5ca9e12b7f
commit
b6363f3ce1
7 changed files with 80 additions and 40 deletions
|
@ -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",
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue