mirror of
https://github.com/nushell/nushell
synced 2024-12-28 14:03:09 +00:00
making ls
and du
supports rest parameters. (#12327)
# Description Close: #12147 Close: #11796 About the change: it make pattern handling into a function: `ls_for_one_pattern`(for ls), `du_for_one_pattern`(for du). Then iterates on user input pattern, call these core function, and chaining these iterator to one pipelinedata.
This commit is contained in:
parent
56cdee1fd8
commit
0110345755
5 changed files with 462 additions and 332 deletions
|
@ -1,9 +1,11 @@
|
|||
use super::util::opt_for_glob_pattern;
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo, FileInfo};
|
||||
use nu_engine::{command_prelude::*, current_dir};
|
||||
use nu_glob::Pattern;
|
||||
use nu_protocol::NuGlob;
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
use std::sync::{atomic::AtomicBool, Arc};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Du;
|
||||
|
@ -33,7 +35,7 @@ impl Command for Du {
|
|||
Signature::build("du")
|
||||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
.allow_variants_without_examples(true)
|
||||
.optional(
|
||||
.rest(
|
||||
"path",
|
||||
SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]),
|
||||
"Starting directory.",
|
||||
|
@ -93,81 +95,59 @@ impl Command for Du {
|
|||
});
|
||||
}
|
||||
}
|
||||
let all = call.has_flag(engine_state, stack, "all")?;
|
||||
let deref = call.has_flag(engine_state, stack, "deref")?;
|
||||
let exclude = call.get_flag(engine_state, stack, "exclude")?;
|
||||
let current_dir = current_dir(engine_state, stack)?;
|
||||
|
||||
let args = DuArgs {
|
||||
path: opt_for_glob_pattern(engine_state, stack, call, 0)?,
|
||||
all: call.has_flag(engine_state, stack, "all")?,
|
||||
deref: call.has_flag(engine_state, stack, "deref")?,
|
||||
exclude: call.get_flag(engine_state, stack, "exclude")?,
|
||||
max_depth,
|
||||
min_size,
|
||||
let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let paths = if call.rest_iter(0).count() == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(paths)
|
||||
};
|
||||
|
||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||
Pattern::new(x.item.as_ref())
|
||||
.map(Some)
|
||||
.map_err(|e| ShellError::InvalidGlobPattern {
|
||||
msg: e.msg.into(),
|
||||
span: x.span,
|
||||
})
|
||||
})?;
|
||||
|
||||
let include_files = args.all;
|
||||
let mut paths = match args.path {
|
||||
Some(p) => nu_engine::glob_from(&p, ¤t_dir, call.head, None),
|
||||
// The * pattern should never fail.
|
||||
None => nu_engine::glob_from(
|
||||
&Spanned {
|
||||
item: NuGlob::Expand("*".into()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
¤t_dir,
|
||||
call.head,
|
||||
None,
|
||||
),
|
||||
}
|
||||
.map(|f| f.1)?
|
||||
.filter(move |p| {
|
||||
if include_files {
|
||||
true
|
||||
} else {
|
||||
matches!(p, Ok(f) if f.is_dir())
|
||||
match paths {
|
||||
None => {
|
||||
let args = DuArgs {
|
||||
path: None,
|
||||
all,
|
||||
deref,
|
||||
exclude,
|
||||
max_depth,
|
||||
min_size,
|
||||
};
|
||||
Ok(
|
||||
du_for_one_pattern(args, ¤t_dir, tag, engine_state.ctrlc.clone())?
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()),
|
||||
)
|
||||
}
|
||||
});
|
||||
|
||||
let all = args.all;
|
||||
let deref = args.deref;
|
||||
let max_depth = args.max_depth.map(|f| f.item as u64);
|
||||
let min_size = args.min_size.map(|f| f.item as u64);
|
||||
|
||||
let params = DirBuilder {
|
||||
tag,
|
||||
min: min_size,
|
||||
deref,
|
||||
exclude,
|
||||
all,
|
||||
};
|
||||
|
||||
let mut output: Vec<Value> = vec![];
|
||||
for p in paths.by_ref() {
|
||||
match p {
|
||||
Ok(a) => {
|
||||
if a.is_dir() {
|
||||
output.push(
|
||||
DirInfo::new(a, ¶ms, max_depth, engine_state.ctrlc.clone()).into(),
|
||||
);
|
||||
} else if let Ok(v) = FileInfo::new(a, deref, tag) {
|
||||
output.push(v.into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
output.push(Value::error(e, tag));
|
||||
Some(paths) => {
|
||||
let mut result_iters = vec![];
|
||||
for p in paths {
|
||||
let args = DuArgs {
|
||||
path: Some(p),
|
||||
all,
|
||||
deref,
|
||||
exclude: exclude.clone(),
|
||||
max_depth,
|
||||
min_size,
|
||||
};
|
||||
result_iters.push(du_for_one_pattern(
|
||||
args,
|
||||
¤t_dir,
|
||||
tag,
|
||||
engine_state.ctrlc.clone(),
|
||||
)?)
|
||||
}
|
||||
|
||||
// chain all iterators on result.
|
||||
Ok(result_iters
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(output.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -179,6 +159,75 @@ impl Command for Du {
|
|||
}
|
||||
}
|
||||
|
||||
fn du_for_one_pattern(
|
||||
args: DuArgs,
|
||||
current_dir: &Path,
|
||||
call_span: Span,
|
||||
ctrl_c: Option<Arc<AtomicBool>>,
|
||||
) -> Result<impl Iterator<Item = Value> + Send, ShellError> {
|
||||
let exclude = args.exclude.map_or(Ok(None), move |x| {
|
||||
Pattern::new(x.item.as_ref())
|
||||
.map(Some)
|
||||
.map_err(|e| ShellError::InvalidGlobPattern {
|
||||
msg: e.msg.into(),
|
||||
span: x.span,
|
||||
})
|
||||
})?;
|
||||
|
||||
let include_files = args.all;
|
||||
let mut paths = match args.path {
|
||||
Some(p) => nu_engine::glob_from(&p, current_dir, call_span, None),
|
||||
// The * pattern should never fail.
|
||||
None => nu_engine::glob_from(
|
||||
&Spanned {
|
||||
item: NuGlob::Expand("*".into()),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
current_dir,
|
||||
call_span,
|
||||
None,
|
||||
),
|
||||
}
|
||||
.map(|f| f.1)?
|
||||
.filter(move |p| {
|
||||
if include_files {
|
||||
true
|
||||
} else {
|
||||
matches!(p, Ok(f) if f.is_dir())
|
||||
}
|
||||
});
|
||||
|
||||
let all = args.all;
|
||||
let deref = args.deref;
|
||||
let max_depth = args.max_depth.map(|f| f.item as u64);
|
||||
let min_size = args.min_size.map(|f| f.item as u64);
|
||||
|
||||
let params = DirBuilder {
|
||||
tag: call_span,
|
||||
min: min_size,
|
||||
deref,
|
||||
exclude,
|
||||
all,
|
||||
};
|
||||
|
||||
let mut output: Vec<Value> = vec![];
|
||||
for p in paths.by_ref() {
|
||||
match p {
|
||||
Ok(a) => {
|
||||
if a.is_dir() {
|
||||
output.push(DirInfo::new(a, ¶ms, max_depth, ctrl_c.clone()).into());
|
||||
} else if let Ok(v) = FileInfo::new(a, deref, call_span) {
|
||||
output.push(v.into());
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
output.push(Value::error(e, call_span));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(output.into_iter())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Du;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use super::util::opt_for_glob_pattern;
|
||||
use super::util::get_rest_for_glob_pattern;
|
||||
use crate::{DirBuilder, DirInfo};
|
||||
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
|
||||
use nu_engine::{command_prelude::*, env::current_dir};
|
||||
|
@ -18,6 +18,18 @@ use std::{
|
|||
#[derive(Clone)]
|
||||
pub struct Ls;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Args {
|
||||
all: bool,
|
||||
long: bool,
|
||||
short_names: bool,
|
||||
full_paths: bool,
|
||||
du: bool,
|
||||
directory: bool,
|
||||
use_mime_type: bool,
|
||||
call_span: Span,
|
||||
}
|
||||
|
||||
impl Command for Ls {
|
||||
fn name(&self) -> &str {
|
||||
"ls"
|
||||
|
@ -36,7 +48,7 @@ impl Command for Ls {
|
|||
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
|
||||
// LsGlobPattern is similar to string, it won't auto-expand
|
||||
// and we use it to track if the user input is quoted.
|
||||
.optional("pattern", SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]), "The glob pattern to use.")
|
||||
.rest("pattern", SyntaxShape::OneOf(vec![SyntaxShape::GlobPattern, SyntaxShape::String]), "The glob pattern to use.")
|
||||
.switch("all", "Show hidden files", Some('a'))
|
||||
.switch(
|
||||
"long",
|
||||
|
@ -81,241 +93,55 @@ impl Command for Ls {
|
|||
let call_span = call.head;
|
||||
let cwd = current_dir(engine_state, stack)?;
|
||||
|
||||
let pattern_arg = opt_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let pattern_arg = {
|
||||
if let Some(path) = pattern_arg {
|
||||
// it makes no sense to list an empty string.
|
||||
if path.item.as_ref().is_empty() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "empty string('') directory or file does not exist".to_string(),
|
||||
span: path.span,
|
||||
});
|
||||
}
|
||||
match path.item {
|
||||
NuGlob::DoNotExpand(p) => Some(Spanned {
|
||||
item: NuGlob::DoNotExpand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
NuGlob::Expand(p) => Some(Spanned {
|
||||
item: NuGlob::Expand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
pattern_arg
|
||||
}
|
||||
let args = Args {
|
||||
all,
|
||||
long,
|
||||
short_names,
|
||||
full_paths,
|
||||
du,
|
||||
directory,
|
||||
use_mime_type,
|
||||
call_span,
|
||||
};
|
||||
|
||||
// it indicates we need to append an extra '*' after pattern for listing given directory
|
||||
// Example: 'ls directory' -> 'ls directory/*'
|
||||
let mut extra_star_under_given_directory = false;
|
||||
let (path, p_tag, absolute_path, quoted) = match pattern_arg {
|
||||
Some(pat) => {
|
||||
let p_tag = pat.span;
|
||||
let expanded = nu_path::expand_path_with(
|
||||
pat.item.as_ref(),
|
||||
&cwd,
|
||||
matches!(pat.item, NuGlob::Expand(..)),
|
||||
);
|
||||
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
|
||||
if !directory && expanded.is_dir() {
|
||||
if permission_denied(&expanded) {
|
||||
#[cfg(unix)]
|
||||
let error_msg = format!(
|
||||
"The permissions of {:o} do not allow access for this user",
|
||||
expanded
|
||||
.metadata()
|
||||
.expect(
|
||||
"this shouldn't be called since we already know there is a dir"
|
||||
)
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o0777
|
||||
);
|
||||
#[cfg(not(unix))]
|
||||
let error_msg = String::from("Permission denied");
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Permission denied".into(),
|
||||
msg: error_msg,
|
||||
span: Some(p_tag),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
if is_empty_dir(&expanded) {
|
||||
return Ok(Value::list(vec![], call_span).into_pipeline_data());
|
||||
}
|
||||
extra_star_under_given_directory = true;
|
||||
}
|
||||
|
||||
// it's absolute path if:
|
||||
// 1. pattern is absolute.
|
||||
// 2. pattern can be expanded, and after expands to real_path, it's absolute.
|
||||
// here `expand_to_real_path` call is required, because `~/aaa` should be absolute
|
||||
// path.
|
||||
let absolute_path = Path::new(pat.item.as_ref()).is_absolute()
|
||||
|| (pat.item.is_expand()
|
||||
&& expand_to_real_path(pat.item.as_ref()).is_absolute());
|
||||
(
|
||||
expanded,
|
||||
p_tag,
|
||||
absolute_path,
|
||||
matches!(pat.item, NuGlob::DoNotExpand(_)),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
|
||||
if directory {
|
||||
(PathBuf::from("."), call_span, false, false)
|
||||
} else if is_empty_dir(current_dir(engine_state, stack)?) {
|
||||
return Ok(Value::list(vec![], call_span).into_pipeline_data());
|
||||
} else {
|
||||
(PathBuf::from("*"), call_span, false, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let hidden_dir_specified = is_hidden_dir(&path);
|
||||
// when it's quoted, we need to escape our glob pattern(but without the last extra
|
||||
// start which may be added under given directory)
|
||||
// so we can do ls for a file or directory like `a[123]b`
|
||||
let path = if quoted {
|
||||
let p = path.display().to_string();
|
||||
let mut glob_escaped = Pattern::escape(&p);
|
||||
if extra_star_under_given_directory {
|
||||
glob_escaped.push(std::path::MAIN_SEPARATOR);
|
||||
glob_escaped.push('*');
|
||||
}
|
||||
glob_escaped
|
||||
} else {
|
||||
let mut p = path.display().to_string();
|
||||
if extra_star_under_given_directory {
|
||||
p.push(std::path::MAIN_SEPARATOR);
|
||||
p.push('*');
|
||||
}
|
||||
p
|
||||
};
|
||||
|
||||
let glob_path = Spanned {
|
||||
// use NeedExpand, the relative escaping logic is handled previously
|
||||
item: NuGlob::Expand(path.clone()),
|
||||
span: p_tag,
|
||||
};
|
||||
|
||||
let glob_options = if all {
|
||||
let pattern_arg = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
|
||||
let input_pattern_arg = if call.rest_iter(0).count() == 0 {
|
||||
None
|
||||
} else {
|
||||
let glob_options = MatchOptions {
|
||||
recursive_match_hidden_dir: false,
|
||||
..Default::default()
|
||||
};
|
||||
Some(glob_options)
|
||||
Some(pattern_arg)
|
||||
};
|
||||
let (prefix, paths) = nu_engine::glob_from(&glob_path, &cwd, call_span, glob_options)?;
|
||||
|
||||
let mut paths_peek = paths.peekable();
|
||||
if paths_peek.peek().is_none() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("No matches found for {}", &path),
|
||||
msg: "Pattern, file or folder not found".into(),
|
||||
span: Some(p_tag),
|
||||
help: Some("no matches found".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let mut hidden_dirs = vec![];
|
||||
|
||||
Ok(paths_peek
|
||||
.filter_map(move |x| match x {
|
||||
Ok(path) => {
|
||||
let metadata = match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) => Some(metadata),
|
||||
Err(_) => None,
|
||||
};
|
||||
if path_contains_hidden_folder(&path, &hidden_dirs) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
|
||||
if path.is_dir() {
|
||||
hidden_dirs.push(path);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
let display_name = if short_names {
|
||||
path.file_name().map(|os| os.to_string_lossy().to_string())
|
||||
} else if full_paths || absolute_path {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
} else if let Some(prefix) = &prefix {
|
||||
if let Ok(remainder) = path.strip_prefix(prefix) {
|
||||
if directory {
|
||||
// When the path is the same as the cwd, path_diff should be "."
|
||||
let path_diff =
|
||||
if let Some(path_diff_not_dot) = diff_paths(&path, &cwd) {
|
||||
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
||||
if path_diff_not_dot.is_empty() {
|
||||
".".to_string()
|
||||
} else {
|
||||
path_diff_not_dot.to_string()
|
||||
}
|
||||
} else {
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
Some(path_diff)
|
||||
} else {
|
||||
let new_prefix = if let Some(pfx) = diff_paths(prefix, &cwd) {
|
||||
pfx
|
||||
} else {
|
||||
prefix.to_path_buf()
|
||||
};
|
||||
|
||||
Some(new_prefix.join(remainder).to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
.ok_or_else(|| ShellError::GenericError {
|
||||
error: format!("Invalid file name: {:}", path.to_string_lossy()),
|
||||
msg: "invalid file name".into(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
|
||||
match display_name {
|
||||
Ok(name) => {
|
||||
let entry = dir_entry_dict(
|
||||
&path,
|
||||
&name,
|
||||
metadata.as_ref(),
|
||||
call_span,
|
||||
long,
|
||||
du,
|
||||
ctrl_c.clone(),
|
||||
use_mime_type,
|
||||
);
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
match input_pattern_arg {
|
||||
None => Ok(ls_for_one_pattern(None, args, ctrl_c.clone(), cwd)?
|
||||
.into_pipeline_data_with_metadata(
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
},
|
||||
ctrl_c,
|
||||
)),
|
||||
Some(pattern) => {
|
||||
let mut result_iters = vec![];
|
||||
for pat in pattern {
|
||||
result_iters.push(ls_for_one_pattern(
|
||||
Some(pat),
|
||||
args,
|
||||
ctrl_c.clone(),
|
||||
cwd.clone(),
|
||||
)?)
|
||||
}
|
||||
_ => Some(Value::nothing(call_span)),
|
||||
})
|
||||
.into_pipeline_data_with_metadata(
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
},
|
||||
engine_state.ctrlc.clone(),
|
||||
))
|
||||
|
||||
// Here nushell needs to use
|
||||
// use `flatten` to chain all iterators into one.
|
||||
Ok(result_iters
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.into_pipeline_data_with_metadata(
|
||||
PipelineMetadata {
|
||||
data_source: DataSource::Ls,
|
||||
},
|
||||
ctrl_c,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
|
@ -365,6 +191,248 @@ impl Command for Ls {
|
|||
}
|
||||
}
|
||||
|
||||
fn ls_for_one_pattern(
|
||||
pattern_arg: Option<Spanned<NuGlob>>,
|
||||
args: Args,
|
||||
ctrl_c: Option<Arc<AtomicBool>>,
|
||||
cwd: PathBuf,
|
||||
) -> Result<Box<dyn Iterator<Item = Value> + Send>, ShellError> {
|
||||
let Args {
|
||||
all,
|
||||
long,
|
||||
short_names,
|
||||
full_paths,
|
||||
du,
|
||||
directory,
|
||||
use_mime_type,
|
||||
call_span,
|
||||
} = args;
|
||||
let pattern_arg = {
|
||||
if let Some(path) = pattern_arg {
|
||||
// it makes no sense to list an empty string.
|
||||
if path.item.as_ref().is_empty() {
|
||||
return Err(ShellError::FileNotFoundCustom {
|
||||
msg: "empty string('') directory or file does not exist".to_string(),
|
||||
span: path.span,
|
||||
});
|
||||
}
|
||||
match path.item {
|
||||
NuGlob::DoNotExpand(p) => Some(Spanned {
|
||||
item: NuGlob::DoNotExpand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
NuGlob::Expand(p) => Some(Spanned {
|
||||
item: NuGlob::Expand(nu_utils::strip_ansi_string_unlikely(p)),
|
||||
span: path.span,
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
pattern_arg
|
||||
}
|
||||
};
|
||||
|
||||
// it indicates we need to append an extra '*' after pattern for listing given directory
|
||||
// Example: 'ls directory' -> 'ls directory/*'
|
||||
let mut extra_star_under_given_directory = false;
|
||||
let (path, p_tag, absolute_path, quoted) = match pattern_arg {
|
||||
Some(pat) => {
|
||||
let p_tag = pat.span;
|
||||
let expanded = nu_path::expand_path_with(
|
||||
pat.item.as_ref(),
|
||||
&cwd,
|
||||
matches!(pat.item, NuGlob::Expand(..)),
|
||||
);
|
||||
// Avoid checking and pushing "*" to the path when directory (do not show contents) flag is true
|
||||
if !directory && expanded.is_dir() {
|
||||
if permission_denied(&expanded) {
|
||||
#[cfg(unix)]
|
||||
let error_msg = format!(
|
||||
"The permissions of {:o} do not allow access for this user",
|
||||
expanded
|
||||
.metadata()
|
||||
.expect("this shouldn't be called since we already know there is a dir")
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o0777
|
||||
);
|
||||
#[cfg(not(unix))]
|
||||
let error_msg = String::from("Permission denied");
|
||||
return Err(ShellError::GenericError {
|
||||
error: "Permission denied".into(),
|
||||
msg: error_msg,
|
||||
span: Some(p_tag),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
if is_empty_dir(&expanded) {
|
||||
return Ok(Box::new(vec![].into_iter()));
|
||||
}
|
||||
extra_star_under_given_directory = true;
|
||||
}
|
||||
|
||||
// it's absolute path if:
|
||||
// 1. pattern is absolute.
|
||||
// 2. pattern can be expanded, and after expands to real_path, it's absolute.
|
||||
// here `expand_to_real_path` call is required, because `~/aaa` should be absolute
|
||||
// path.
|
||||
let absolute_path = Path::new(pat.item.as_ref()).is_absolute()
|
||||
|| (pat.item.is_expand() && expand_to_real_path(pat.item.as_ref()).is_absolute());
|
||||
(
|
||||
expanded,
|
||||
p_tag,
|
||||
absolute_path,
|
||||
matches!(pat.item, NuGlob::DoNotExpand(_)),
|
||||
)
|
||||
}
|
||||
None => {
|
||||
// Avoid pushing "*" to the default path when directory (do not show contents) flag is true
|
||||
if directory {
|
||||
(PathBuf::from("."), call_span, false, false)
|
||||
} else if is_empty_dir(&cwd) {
|
||||
return Ok(Box::new(vec![].into_iter()));
|
||||
} else {
|
||||
(PathBuf::from("*"), call_span, false, false)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let hidden_dir_specified = is_hidden_dir(&path);
|
||||
// when it's quoted, we need to escape our glob pattern(but without the last extra
|
||||
// start which may be added under given directory)
|
||||
// so we can do ls for a file or directory like `a[123]b`
|
||||
let path = if quoted {
|
||||
let p = path.display().to_string();
|
||||
let mut glob_escaped = Pattern::escape(&p);
|
||||
if extra_star_under_given_directory {
|
||||
glob_escaped.push(std::path::MAIN_SEPARATOR);
|
||||
glob_escaped.push('*');
|
||||
}
|
||||
glob_escaped
|
||||
} else {
|
||||
let mut p = path.display().to_string();
|
||||
if extra_star_under_given_directory {
|
||||
p.push(std::path::MAIN_SEPARATOR);
|
||||
p.push('*');
|
||||
}
|
||||
p
|
||||
};
|
||||
|
||||
let glob_path = Spanned {
|
||||
// use NeedExpand, the relative escaping logic is handled previously
|
||||
item: NuGlob::Expand(path.clone()),
|
||||
span: p_tag,
|
||||
};
|
||||
|
||||
let glob_options = if all {
|
||||
None
|
||||
} else {
|
||||
let glob_options = MatchOptions {
|
||||
recursive_match_hidden_dir: false,
|
||||
..Default::default()
|
||||
};
|
||||
Some(glob_options)
|
||||
};
|
||||
let (prefix, paths) = nu_engine::glob_from(&glob_path, &cwd, call_span, glob_options)?;
|
||||
|
||||
let mut paths_peek = paths.peekable();
|
||||
if paths_peek.peek().is_none() {
|
||||
return Err(ShellError::GenericError {
|
||||
error: format!("No matches found for {}", &path),
|
||||
msg: "Pattern, file or folder not found".into(),
|
||||
span: Some(p_tag),
|
||||
help: Some("no matches found".into()),
|
||||
inner: vec![],
|
||||
});
|
||||
}
|
||||
|
||||
let mut hidden_dirs = vec![];
|
||||
|
||||
let one_ctrl_c = ctrl_c.clone();
|
||||
Ok(Box::new(paths_peek.filter_map(move |x| match x {
|
||||
Ok(path) => {
|
||||
let metadata = match std::fs::symlink_metadata(&path) {
|
||||
Ok(metadata) => Some(metadata),
|
||||
Err(_) => None,
|
||||
};
|
||||
if path_contains_hidden_folder(&path, &hidden_dirs) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !all && !hidden_dir_specified && is_hidden_dir(&path) {
|
||||
if path.is_dir() {
|
||||
hidden_dirs.push(path);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
let display_name = if short_names {
|
||||
path.file_name().map(|os| os.to_string_lossy().to_string())
|
||||
} else if full_paths || absolute_path {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
} else if let Some(prefix) = &prefix {
|
||||
if let Ok(remainder) = path.strip_prefix(prefix) {
|
||||
if directory {
|
||||
// When the path is the same as the cwd, path_diff should be "."
|
||||
let path_diff = if let Some(path_diff_not_dot) = diff_paths(&path, &cwd) {
|
||||
let path_diff_not_dot = path_diff_not_dot.to_string_lossy();
|
||||
if path_diff_not_dot.is_empty() {
|
||||
".".to_string()
|
||||
} else {
|
||||
path_diff_not_dot.to_string()
|
||||
}
|
||||
} else {
|
||||
path.to_string_lossy().to_string()
|
||||
};
|
||||
|
||||
Some(path_diff)
|
||||
} else {
|
||||
let new_prefix = if let Some(pfx) = diff_paths(prefix, &cwd) {
|
||||
pfx
|
||||
} else {
|
||||
prefix.to_path_buf()
|
||||
};
|
||||
|
||||
Some(new_prefix.join(remainder).to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
} else {
|
||||
Some(path.to_string_lossy().to_string())
|
||||
}
|
||||
.ok_or_else(|| ShellError::GenericError {
|
||||
error: format!("Invalid file name: {:}", path.to_string_lossy()),
|
||||
msg: "invalid file name".into(),
|
||||
span: Some(call_span),
|
||||
help: None,
|
||||
inner: vec![],
|
||||
});
|
||||
|
||||
match display_name {
|
||||
Ok(name) => {
|
||||
let entry = dir_entry_dict(
|
||||
&path,
|
||||
&name,
|
||||
metadata.as_ref(),
|
||||
call_span,
|
||||
long,
|
||||
du,
|
||||
one_ctrl_c.clone(),
|
||||
use_mime_type,
|
||||
);
|
||||
match entry {
|
||||
Ok(value) => Some(value),
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
Err(err) => Some(Value::error(err, call_span)),
|
||||
}
|
||||
}
|
||||
_ => Some(Value::nothing(call_span)),
|
||||
})))
|
||||
}
|
||||
|
||||
fn permission_denied(dir: impl AsRef<Path>) -> bool {
|
||||
match dir.as_ref().read_dir() {
|
||||
Err(e) => matches!(e.kind(), std::io::ErrorKind::PermissionDenied),
|
||||
|
|
|
@ -131,34 +131,3 @@ pub fn get_rest_for_glob_pattern(
|
|||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Get optional arguments from given `call` with position `pos`.
|
||||
///
|
||||
/// It's similar to `call.opt`, except that it always returns NuGlob.
|
||||
pub fn opt_for_glob_pattern(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
pos: usize,
|
||||
) -> Result<Option<Spanned<NuGlob>>, ShellError> {
|
||||
if let Some(expr) = call.positional_nth(pos) {
|
||||
let eval_expression = get_eval_expression(engine_state);
|
||||
let result = eval_expression(engine_state, stack, expr)?;
|
||||
let result_span = result.span();
|
||||
let result = match result {
|
||||
Value::String { val, .. }
|
||||
if matches!(
|
||||
&expr.expr,
|
||||
Expr::FullCellPath(_) | Expr::StringInterpolation(_)
|
||||
) =>
|
||||
{
|
||||
// should quote if given input type is not glob.
|
||||
Value::glob(val, expr.ty != Type::Glob, result_span)
|
||||
}
|
||||
other => other,
|
||||
};
|
||||
FromValue::from_value(result).map(Some)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ fn test_du_flag_max_depth() {
|
|||
#[case("a[bc]d")]
|
||||
#[case("a][c")]
|
||||
fn du_files_with_glob_metachars(#[case] src_name: &str) {
|
||||
Playground::setup("umv_test_16", |dirs, sandbox| {
|
||||
Playground::setup("du_test_16", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![EmptyFile(src_name)]);
|
||||
|
||||
let src = dirs.test().join(src_name);
|
||||
|
@ -82,3 +82,21 @@ fn du_files_with_glob_metachars(#[case] src_name: &str) {
|
|||
fn du_files_with_glob_metachars_nw(#[case] src_name: &str) {
|
||||
du_files_with_glob_metachars(src_name);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn du_with_multiple_path() {
|
||||
let actual = nu!(cwd: "tests/fixtures", "du cp formats | get path | path basename");
|
||||
assert!(actual.out.contains("cp"));
|
||||
assert!(actual.out.contains("formats"));
|
||||
assert!(!actual.out.contains("lsp"));
|
||||
assert!(actual.status.success());
|
||||
|
||||
// report errors if one path not exists
|
||||
let actual = nu!(cwd: "tests/fixtures", "du cp asdf | get path | path basename");
|
||||
assert!(actual.err.contains("directory not found"));
|
||||
assert!(!actual.status.success());
|
||||
|
||||
// du with spreading empty list should returns nothing.
|
||||
let actual = nu!(cwd: "tests/fixtures", "du ...[] | length");
|
||||
assert_eq!(actual.out, "0");
|
||||
}
|
||||
|
|
|
@ -733,3 +733,29 @@ fn list_with_tilde() {
|
|||
assert!(actual.out.contains("~tilde"));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_with_multiple_path() {
|
||||
Playground::setup("ls_multiple_path", |dirs, sandbox| {
|
||||
sandbox.with_files(vec![
|
||||
EmptyFile("f1.txt"),
|
||||
EmptyFile("f2.txt"),
|
||||
EmptyFile("f3.txt"),
|
||||
]);
|
||||
|
||||
let actual = nu!(cwd: dirs.test(), "ls f1.txt f2.txt");
|
||||
assert!(actual.out.contains("f1.txt"));
|
||||
assert!(actual.out.contains("f2.txt"));
|
||||
assert!(!actual.out.contains("f3.txt"));
|
||||
assert!(actual.status.success());
|
||||
|
||||
// report errors if one path not exists
|
||||
let actual = nu!(cwd: dirs.test(), "ls asdf f1.txt");
|
||||
assert!(actual.err.contains("directory not found"));
|
||||
assert!(!actual.status.success());
|
||||
|
||||
// ls with spreading empty list should returns nothing.
|
||||
let actual = nu!(cwd: dirs.test(), "ls ...[] | length");
|
||||
assert_eq!(actual.out, "0");
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue