mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add ctrl_c to RunnablePerItemContext. (#1239)
Also, this commit makes `ls` a per-item command. A command that processes things item by item may still take some time to stream out the results from a single item. For example, `ls` on a directory with a lot of files could be interrupted in the middle of showing all of these files.
This commit is contained in:
parent
3abfefc025
commit
47d987d37f
14 changed files with 79 additions and 42 deletions
|
@ -251,7 +251,7 @@ pub async fn cli() -> Result<(), Box<dyn Error>> {
|
||||||
context.add_commands(vec![
|
context.add_commands(vec![
|
||||||
// System/file operations
|
// System/file operations
|
||||||
whole_stream_command(Pwd),
|
whole_stream_command(Pwd),
|
||||||
whole_stream_command(Ls),
|
per_item_command(Ls),
|
||||||
whole_stream_command(Cd),
|
whole_stream_command(Cd),
|
||||||
whole_stream_command(Env),
|
whole_stream_command(Env),
|
||||||
per_item_command(Remove),
|
per_item_command(Remove),
|
||||||
|
|
|
@ -42,6 +42,7 @@ pub trait CallInfoExt {
|
||||||
fn process<'de, T: Deserialize<'de>>(
|
fn process<'de, T: Deserialize<'de>>(
|
||||||
&self,
|
&self,
|
||||||
shell_manager: &ShellManager,
|
shell_manager: &ShellManager,
|
||||||
|
ctrl_c: Arc<AtomicBool>,
|
||||||
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
||||||
) -> Result<RunnablePerItemArgs<T>, ShellError>;
|
) -> Result<RunnablePerItemArgs<T>, ShellError>;
|
||||||
}
|
}
|
||||||
|
@ -50,6 +51,7 @@ impl CallInfoExt for CallInfo {
|
||||||
fn process<'de, T: Deserialize<'de>>(
|
fn process<'de, T: Deserialize<'de>>(
|
||||||
&self,
|
&self,
|
||||||
shell_manager: &ShellManager,
|
shell_manager: &ShellManager,
|
||||||
|
ctrl_c: Arc<AtomicBool>,
|
||||||
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
callback: fn(T, &RunnablePerItemContext) -> Result<OutputStream, ShellError>,
|
||||||
) -> Result<RunnablePerItemArgs<T>, ShellError> {
|
) -> Result<RunnablePerItemArgs<T>, ShellError> {
|
||||||
let mut deserializer = ConfigDeserializer::from_call_info(self.clone());
|
let mut deserializer = ConfigDeserializer::from_call_info(self.clone());
|
||||||
|
@ -59,6 +61,7 @@ impl CallInfoExt for CallInfo {
|
||||||
context: RunnablePerItemContext {
|
context: RunnablePerItemContext {
|
||||||
shell_manager: shell_manager.clone(),
|
shell_manager: shell_manager.clone(),
|
||||||
name: self.name_tag.clone(),
|
name: self.name_tag.clone(),
|
||||||
|
ctrl_c,
|
||||||
},
|
},
|
||||||
callback,
|
callback,
|
||||||
})
|
})
|
||||||
|
@ -219,6 +222,7 @@ impl CommandArgs {
|
||||||
pub struct RunnablePerItemContext {
|
pub struct RunnablePerItemContext {
|
||||||
pub shell_manager: ShellManager,
|
pub shell_manager: ShellManager,
|
||||||
pub name: Tag,
|
pub name: Tag,
|
||||||
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RunnableContext {
|
pub struct RunnableContext {
|
||||||
|
|
|
@ -38,7 +38,9 @@ impl PerItemCommand for Cpy {
|
||||||
raw_args: &RawCommandArgs,
|
raw_args: &RawCommandArgs,
|
||||||
_input: Value,
|
_input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
call_info.process(&raw_args.shell_manager, cp)?.run()
|
call_info
|
||||||
|
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), cp)?
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::commands::WholeStreamCommand;
|
use crate::commands::command::RunnablePerItemContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Signature, SyntaxShape};
|
use nu_protocol::{CallInfo, Signature, SyntaxShape, Value};
|
||||||
use nu_source::Tagged;
|
use nu_source::Tagged;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -9,11 +9,11 @@ pub struct Ls;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LsArgs {
|
pub struct LsArgs {
|
||||||
path: Option<Tagged<PathBuf>>,
|
pub path: Option<Tagged<PathBuf>>,
|
||||||
full: bool,
|
pub full: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WholeStreamCommand for Ls {
|
impl PerItemCommand for Ls {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> &str {
|
||||||
"ls"
|
"ls"
|
||||||
}
|
}
|
||||||
|
@ -34,14 +34,17 @@ impl WholeStreamCommand for Ls {
|
||||||
|
|
||||||
fn run(
|
fn run(
|
||||||
&self,
|
&self,
|
||||||
args: CommandArgs,
|
call_info: &CallInfo,
|
||||||
registry: &CommandRegistry,
|
_registry: &CommandRegistry,
|
||||||
|
raw_args: &RawCommandArgs,
|
||||||
|
_input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
args.process(registry, ls)?.run()
|
call_info
|
||||||
// ls(args, registry)
|
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), ls)?
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ls(LsArgs { path, full }: LsArgs, context: RunnableContext) -> Result<OutputStream, ShellError> {
|
fn ls(args: LsArgs, context: &RunnablePerItemContext) -> Result<OutputStream, ShellError> {
|
||||||
context.shell_manager.ls(path, &context, full)
|
context.shell_manager.ls(args, context)
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,9 @@ impl PerItemCommand for Mkdir {
|
||||||
raw_args: &RawCommandArgs,
|
raw_args: &RawCommandArgs,
|
||||||
_input: Value,
|
_input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
call_info.process(&raw_args.shell_manager, mkdir)?.run()
|
call_info
|
||||||
|
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mkdir)?
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,9 @@ impl PerItemCommand for Move {
|
||||||
raw_args: &RawCommandArgs,
|
raw_args: &RawCommandArgs,
|
||||||
_input: Value,
|
_input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
call_info.process(&raw_args.shell_manager, mv)?.run()
|
call_info
|
||||||
|
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), mv)?
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,9 @@ impl PerItemCommand for Remove {
|
||||||
raw_args: &RawCommandArgs,
|
raw_args: &RawCommandArgs,
|
||||||
_input: Value,
|
_input: Value,
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
call_info.process(&raw_args.shell_manager, rm)?.run()
|
call_info
|
||||||
|
.process(&raw_args.shell_manager, raw_args.ctrl_c.clone(), rm)?
|
||||||
|
.run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,7 @@ pub(crate) use nu_protocol::{errln, outln};
|
||||||
|
|
||||||
pub(crate) use crate::commands::command::{
|
pub(crate) use crate::commands::command::{
|
||||||
CallInfoExt, CommandArgs, PerItemCommand, RawCommandArgs, RunnableContext,
|
CallInfoExt, CommandArgs, PerItemCommand, RawCommandArgs, RunnableContext,
|
||||||
|
RunnablePerItemContext,
|
||||||
};
|
};
|
||||||
pub(crate) use crate::context::CommandRegistry;
|
pub(crate) use crate::context::CommandRegistry;
|
||||||
pub(crate) use crate::context::Context;
|
pub(crate) use crate::context::Context;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||||
use crate::commands::cp::CopyArgs;
|
use crate::commands::cp::CopyArgs;
|
||||||
|
use crate::commands::ls::LsArgs;
|
||||||
use crate::commands::mkdir::MkdirArgs;
|
use crate::commands::mkdir::MkdirArgs;
|
||||||
use crate::commands::mv::MoveArgs;
|
use crate::commands::mv::MoveArgs;
|
||||||
use crate::commands::rm::RemoveArgs;
|
use crate::commands::rm::RemoveArgs;
|
||||||
|
@ -10,7 +11,6 @@ use crate::shell::shell::Shell;
|
||||||
use crate::utils::FileStructure;
|
use crate::utils::FileStructure;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
|
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
|
||||||
use nu_source::Tagged;
|
|
||||||
use rustyline::completion::FilenameCompleter;
|
use rustyline::completion::FilenameCompleter;
|
||||||
use rustyline::hint::{Hinter, HistoryHinter};
|
use rustyline::hint::{Hinter, HistoryHinter};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
@ -84,14 +84,13 @@ impl Shell for FilesystemShell {
|
||||||
|
|
||||||
fn ls(
|
fn ls(
|
||||||
&self,
|
&self,
|
||||||
pattern: Option<Tagged<PathBuf>>,
|
LsArgs { path, full }: LsArgs,
|
||||||
context: &RunnableContext,
|
context: &RunnablePerItemContext,
|
||||||
full: bool,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let cwd = self.path();
|
let cwd = self.path();
|
||||||
let mut full_path = PathBuf::from(self.path());
|
let mut full_path = PathBuf::from(self.path());
|
||||||
|
|
||||||
if let Some(value) = &pattern {
|
if let Some(value) = &path {
|
||||||
full_path.push((*value).as_ref())
|
full_path.push((*value).as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +105,7 @@ impl Shell for FilesystemShell {
|
||||||
let entries = std::fs::read_dir(&entry);
|
let entries = std::fs::read_dir(&entry);
|
||||||
let entries = match entries {
|
let entries = match entries {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if let Some(s) = pattern {
|
if let Some(s) = path {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
e.to_string(),
|
e.to_string(),
|
||||||
|
@ -160,7 +159,7 @@ impl Shell for FilesystemShell {
|
||||||
let entries = match glob::glob(&full_path.to_string_lossy()) {
|
let entries = match glob::glob(&full_path.to_string_lossy()) {
|
||||||
Ok(files) => files,
|
Ok(files) => files,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if let Some(source) = pattern {
|
if let Some(source) = path {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Invalid pattern",
|
"Invalid pattern",
|
||||||
"Invalid pattern",
|
"Invalid pattern",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||||
use crate::commands::cp::CopyArgs;
|
use crate::commands::cp::CopyArgs;
|
||||||
|
use crate::commands::ls::LsArgs;
|
||||||
use crate::commands::mkdir::MkdirArgs;
|
use crate::commands::mkdir::MkdirArgs;
|
||||||
use crate::commands::mv::MoveArgs;
|
use crate::commands::mv::MoveArgs;
|
||||||
use crate::commands::rm::RemoveArgs;
|
use crate::commands::rm::RemoveArgs;
|
||||||
|
@ -10,7 +11,6 @@ use nu_errors::ShellError;
|
||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
Primitive, ReturnSuccess, ShellTypeName, TaggedDictBuilder, UntaggedValue, Value,
|
||||||
};
|
};
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -135,9 +135,8 @@ impl Shell for HelpShell {
|
||||||
|
|
||||||
fn ls(
|
fn ls(
|
||||||
&self,
|
&self,
|
||||||
_pattern: Option<Tagged<PathBuf>>,
|
_args: LsArgs,
|
||||||
_context: &RunnableContext,
|
_context: &RunnablePerItemContext,
|
||||||
_full: bool,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
Ok(self.commands().map(ReturnSuccess::value).to_output_stream())
|
Ok(self.commands().map(ReturnSuccess::value).to_output_stream())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||||
use crate::commands::cp::CopyArgs;
|
use crate::commands::cp::CopyArgs;
|
||||||
|
use crate::commands::ls::LsArgs;
|
||||||
use crate::commands::mkdir::MkdirArgs;
|
use crate::commands::mkdir::MkdirArgs;
|
||||||
use crate::commands::mv::MoveArgs;
|
use crate::commands::mv::MoveArgs;
|
||||||
use crate::commands::rm::RemoveArgs;
|
use crate::commands::rm::RemoveArgs;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::stream::OutputStream;
|
use crate::stream::OutputStream;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub trait Shell: std::fmt::Debug {
|
pub trait Shell: std::fmt::Debug {
|
||||||
|
@ -15,9 +15,8 @@ pub trait Shell: std::fmt::Debug {
|
||||||
|
|
||||||
fn ls(
|
fn ls(
|
||||||
&self,
|
&self,
|
||||||
pattern: Option<Tagged<PathBuf>>,
|
args: LsArgs,
|
||||||
context: &RunnableContext,
|
context: &RunnablePerItemContext,
|
||||||
full: bool,
|
|
||||||
) -> Result<OutputStream, ShellError>;
|
) -> Result<OutputStream, ShellError>;
|
||||||
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
fn cd(&self, args: EvaluatedWholeStreamCommandArgs) -> Result<OutputStream, ShellError>;
|
||||||
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
fn cp(&self, args: CopyArgs, name: Tag, path: &str) -> Result<OutputStream, ShellError>;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::commands::command::{EvaluatedWholeStreamCommandArgs, RunnablePerItemContext};
|
use crate::commands::command::{EvaluatedWholeStreamCommandArgs, RunnablePerItemContext};
|
||||||
use crate::commands::cp::CopyArgs;
|
use crate::commands::cp::CopyArgs;
|
||||||
|
use crate::commands::ls::LsArgs;
|
||||||
use crate::commands::mkdir::MkdirArgs;
|
use crate::commands::mkdir::MkdirArgs;
|
||||||
use crate::commands::mv::MoveArgs;
|
use crate::commands::mv::MoveArgs;
|
||||||
use crate::commands::rm::RemoveArgs;
|
use crate::commands::rm::RemoveArgs;
|
||||||
|
@ -8,7 +9,6 @@ use crate::shell::filesystem_shell::FilesystemShell;
|
||||||
use crate::shell::shell::Shell;
|
use crate::shell::shell::Shell;
|
||||||
use crate::stream::OutputStream;
|
use crate::stream::OutputStream;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
@ -202,12 +202,11 @@ impl ShellManager {
|
||||||
|
|
||||||
pub fn ls(
|
pub fn ls(
|
||||||
&self,
|
&self,
|
||||||
path: Option<Tagged<PathBuf>>,
|
args: LsArgs,
|
||||||
context: &RunnableContext,
|
context: &RunnablePerItemContext,
|
||||||
full: bool,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
if let Ok(shells) = self.shells.lock() {
|
if let Ok(shells) = self.shells.lock() {
|
||||||
shells[self.current_shell()].ls(path, context, full)
|
shells[self.current_shell()].ls(args, context)
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::untagged_runtime_error(
|
Err(ShellError::untagged_runtime_error(
|
||||||
"Internal error: could not lock shells ring buffer (ls)",
|
"Internal error: could not lock shells ring buffer (ls)",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
use crate::commands::command::EvaluatedWholeStreamCommandArgs;
|
||||||
use crate::commands::cp::CopyArgs;
|
use crate::commands::cp::CopyArgs;
|
||||||
|
use crate::commands::ls::LsArgs;
|
||||||
use crate::commands::mkdir::MkdirArgs;
|
use crate::commands::mkdir::MkdirArgs;
|
||||||
use crate::commands::mv::MoveArgs;
|
use crate::commands::mv::MoveArgs;
|
||||||
use crate::commands::rm::RemoveArgs;
|
use crate::commands::rm::RemoveArgs;
|
||||||
|
@ -8,7 +9,6 @@ use crate::shell::shell::Shell;
|
||||||
use crate::utils::ValueStructure;
|
use crate::utils::ValueStructure;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
use nu_protocol::{ReturnSuccess, ShellTypeName, UntaggedValue, Value};
|
||||||
use nu_source::Tagged;
|
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
@ -90,14 +90,13 @@ impl Shell for ValueShell {
|
||||||
|
|
||||||
fn ls(
|
fn ls(
|
||||||
&self,
|
&self,
|
||||||
target: Option<Tagged<PathBuf>>,
|
LsArgs { path, .. }: LsArgs,
|
||||||
context: &RunnableContext,
|
context: &RunnablePerItemContext,
|
||||||
_full: bool,
|
|
||||||
) -> Result<OutputStream, ShellError> {
|
) -> Result<OutputStream, ShellError> {
|
||||||
let mut full_path = PathBuf::from(self.path());
|
let mut full_path = PathBuf::from(self.path());
|
||||||
let name_tag = context.name.clone();
|
let name_tag = context.name.clone();
|
||||||
|
|
||||||
if let Some(value) = &target {
|
if let Some(value) = &path {
|
||||||
full_path.push(value.as_ref());
|
full_path.push(value.as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +104,7 @@ impl Shell for ValueShell {
|
||||||
value_system.walk_decorate(&self.value)?;
|
value_system.walk_decorate(&self.value)?;
|
||||||
|
|
||||||
if !value_system.exists(&full_path) {
|
if !value_system.exists(&full_path) {
|
||||||
if let Some(target) = &target {
|
if let Some(target) = &path {
|
||||||
return Err(ShellError::labeled_error(
|
return Err(ShellError::labeled_error(
|
||||||
"Can not list entries inside",
|
"Can not list entries inside",
|
||||||
"No such path exists",
|
"No such path exists",
|
||||||
|
|
|
@ -69,3 +69,29 @@ fn lists_regular_files_using_question_mark_wildcard() {
|
||||||
assert_eq!(actual, "3");
|
assert_eq!(actual, "3");
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lists_all_files_in_directories_from_stream() {
|
||||||
|
Playground::setup("ls_test_4", |dirs, sandbox| {
|
||||||
|
sandbox.mkdir("dir_a").mkdir("dir_b").with_files(vec![
|
||||||
|
EmptyFile("root1.txt"),
|
||||||
|
EmptyFile("root2.txt"),
|
||||||
|
EmptyFile("dir_a/yehuda.10.txt"),
|
||||||
|
EmptyFile("dir_a/jonathan.10.txt"),
|
||||||
|
EmptyFile("dir_b/andres.10.txt"),
|
||||||
|
EmptyFile("dir_b/chicken_not_to_be_picked_up.100.txt"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: dirs.test(), pipeline(
|
||||||
|
r#"
|
||||||
|
echo dir_a dir_b
|
||||||
|
| ls $it
|
||||||
|
| count
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
|
||||||
|
assert_eq!(actual, "4");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue