Merge branch 'main' of https://github.com/nushell/nushell into patch/bump-tabled-to-0.17.0

This commit is contained in:
Maxim Zhiburt
2024-12-17 15:22:26 +03:00
278 changed files with 6190 additions and 3832 deletions

View File

@ -18,17 +18,17 @@ workspace = true
[dependencies]
nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" }
nu-color-config = { path = "../nu-color-config", version = "0.100.1" }
nu-engine = { path = "../nu-engine", version = "0.100.1" }
nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false }
nu-glob = { path = "../nu-glob", version = "0.100.1" }
nu-json = { path = "../nu-json", version = "0.100.1" }
nu-parser = { path = "../nu-parser", version = "0.100.1" }
nu-path = { path = "../nu-path", version = "0.100.1" }
nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1" }
nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false }
nu-system = { path = "../nu-system", version = "0.100.1" }
nu-table = { path = "../nu-table", version = "0.100.1" }
nu-term-grid = { path = "../nu-term-grid", version = "0.100.1" }
nu-utils = { path = "../nu-utils", version = "0.100.1" }
nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false }
nu-ansi-term = { workspace = true }
nuon = { path = "../nuon", version = "0.100.1" }
@ -43,7 +43,7 @@ chardetng = { workspace = true }
chrono = { workspace = true, features = ["std", "unstable-locales", "clock"], default-features = false }
chrono-humanize = { workspace = true }
chrono-tz = { workspace = true }
crossterm = { workspace = true }
crossterm = { workspace = true, optional = true }
csv = { workspace = true }
dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] }
digest = { workspace = true, default-features = false }
@ -61,24 +61,26 @@ lscolors = { workspace = true, default-features = false, features = ["nu-ansi-te
md5 = { workspace = true }
mime = { workspace = true }
mime_guess = { workspace = true }
multipart-rs = { workspace = true }
native-tls = { workspace = true }
notify-debouncer-full = { workspace = true, default-features = false }
multipart-rs = { workspace = true, optional = true }
native-tls = { workspace = true, optional = true }
notify-debouncer-full = { workspace = true, default-features = false, optional = true }
num-format = { workspace = true }
num-traits = { workspace = true }
oem_cp = { workspace = true }
open = { workspace = true }
os_pipe = { workspace = true }
open = { workspace = true, optional = true }
os_pipe = { workspace = true, optional = true }
pathdiff = { workspace = true }
percent-encoding = { workspace = true }
print-positions = { workspace = true }
quick-xml = { workspace = true }
rand = { workspace = true }
rand = { workspace = true, optional = true }
getrandom = { workspace = true, optional = true }
rayon = { workspace = true }
regex = { workspace = true }
roxmltree = { workspace = true }
rusqlite = { workspace = true, features = ["bundled", "backup", "chrono"], optional = true }
rmp = { workspace = true }
scopeguard = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
serde_urlencoded = { workspace = true }
@ -86,30 +88,29 @@ serde_yaml = { workspace = true }
sha2 = { workspace = true }
sysinfo = { workspace = true }
tabled = { workspace = true, features = ["ansi"], default-features = false }
terminal_size = { workspace = true }
titlecase = { workspace = true }
toml = { workspace = true, features = ["preserve_order"] }
unicode-segmentation = { workspace = true }
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] }
ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json"] }
url = { workspace = true }
uu_cp = { workspace = true }
uu_mkdir = { workspace = true }
uu_mktemp = { workspace = true }
uu_mv = { workspace = true }
uu_touch = { workspace = true }
uu_uname = { workspace = true }
uu_whoami = { workspace = true }
uuid = { workspace = true, features = ["v4"] }
uu_cp = { workspace = true, optional = true }
uu_mkdir = { workspace = true, optional = true }
uu_mktemp = { workspace = true, optional = true }
uu_mv = { workspace = true, optional = true }
uu_touch = { workspace = true, optional = true }
uu_uname = { workspace = true, optional = true }
uu_whoami = { workspace = true, optional = true }
uuid = { workspace = true, features = ["v4"], optional = true }
v_htmlescape = { workspace = true }
wax = { workspace = true }
which = { workspace = true }
which = { workspace = true, optional = true }
unicode-width = { workspace = true }
data-encoding = { version = "2.6.0", features = ["alloc"] }
[target.'cfg(windows)'.dependencies]
winreg = { workspace = true }
[target.'cfg(not(windows))'.dependencies]
[target.'cfg(all(not(windows), not(target_arch = "wasm32")))'.dependencies]
uucore = { workspace = true, features = ["mode"] }
[target.'cfg(unix)'.dependencies]
@ -135,7 +136,53 @@ features = [
workspace = true
[features]
plugin = ["nu-parser/plugin"]
default = ["os"]
os = [
# include other features
"js",
"network",
"nu-protocol/os",
"nu-utils/os",
# os-dependant dependencies
"crossterm",
"notify-debouncer-full",
"open",
"os_pipe",
"uu_cp",
"uu_mkdir",
"uu_mktemp",
"uu_mv",
"uu_touch",
"uu_uname",
"uu_whoami",
"which",
]
# The dependencies listed below need 'getrandom'.
# They work with JS (usually with wasm-bindgen) or regular OS support.
# Hence they are also put under the 'os' feature to avoid repetition.
js = [
"getrandom",
"getrandom/js",
"rand",
"uuid",
]
# These dependencies require networking capabilities, especially the http
# interface requires openssl which is not easy to embed into wasm,
# using rustls could solve this issue.
network = [
"multipart-rs",
"native-tls",
"ureq/native-tls",
"uuid",
]
plugin = [
"nu-parser/plugin",
"os",
]
sqlite = ["rusqlite"]
trash-support = ["trash"]
@ -150,4 +197,4 @@ quickcheck_macros = { workspace = true }
rstest = { workspace = true, default-features = false }
pretty_assertions = { workspace = true }
tempfile = { workspace = true }
rand_chacha = { workspace = true }
rand_chacha = { workspace = true }

View File

@ -1,4 +1,4 @@
use nu_engine::{command_prelude::*, get_eval_expression};
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct BytesBuild;
@ -49,8 +49,7 @@ impl Command for BytesBuild {
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let mut output = vec![];
let eval_expression = get_eval_expression(engine_state);
for val in call.rest_iter_flattened(engine_state, stack, eval_expression, 0)? {
for val in call.rest::<Value>(engine_state, stack, 0)? {
let val_span = val.span();
match val {
Value::Binary { mut val, .. } => output.append(&mut val),

View File

@ -1,5 +1,5 @@
use chrono::{DateTime, FixedOffset};
use nu_protocol::{ShellError, Span, Value};
use nu_protocol::{Filesize, ShellError, Span, Value};
use std::hash::{Hash, Hasher};
/// A subset of [`Value`], which is hashable.
@ -30,7 +30,7 @@ pub enum HashableValue {
span: Span,
},
Filesize {
val: i64,
val: Filesize,
span: Span,
},
Duration {
@ -198,7 +198,10 @@ mod test {
(Value::int(1, span), HashableValue::Int { val: 1, span }),
(
Value::filesize(1, span),
HashableValue::Filesize { val: 1, span },
HashableValue::Filesize {
val: 1.into(),
span,
},
),
(
Value::duration(1, span),

View File

@ -167,7 +167,7 @@ fn fill(
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
match input {
Value::Int { val, .. } => fill_int(*val, args, span),
Value::Filesize { val, .. } => fill_int(*val, args, span),
Value::Filesize { val, .. } => fill_int(val.get(), args, span),
Value::Float { val, .. } => fill_float(*val, args, span),
Value::String { val, .. } => fill_string(val, args, span),
// Propagate errors by explicitly matching them before the final case.

View File

@ -147,7 +147,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
Value::Float { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
Value::Filesize { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),
Value::Filesize { val, .. } => Value::binary(val.get().to_ne_bytes().to_vec(), span),
Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span),
Value::Bool { val, .. } => Value::binary(i64::from(*val).to_ne_bytes().to_vec(), span),
Value::Duration { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span),

View File

@ -253,7 +253,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
convert_int(input, span, radix)
}
}
Value::Filesize { val, .. } => Value::int(*val, span),
Value::Filesize { val, .. } => Value::int(val.get(), span),
Value::Float { val, .. } => Value::int(
{
if radix == 10 {

View File

@ -99,6 +99,11 @@ impl Command for SubCommand {
"timezone" => Value::test_string("+02:00"),
})),
},
Example {
description: "convert date components to table columns",
example: "2020-04-12T22:10:57+02:00 | into record | transpose | transpose -r",
result: None,
},
]
}
}

View File

@ -359,7 +359,6 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> {
| Type::Custom(_)
| Type::Error
| Type::List(_)
| Type::ListStream
| Type::Range
| Type::Record(_)
| Type::Signature

View File

@ -421,7 +421,7 @@ pub fn value_to_sql(value: Value) -> Result<Box<dyn rusqlite::ToSql>, ShellError
Value::Bool { val, .. } => Box::new(val),
Value::Int { val, .. } => Box::new(val),
Value::Float { val, .. } => Box::new(val),
Value::Filesize { val, .. } => Box::new(val),
Value::Filesize { val, .. } => Box::new(val.get()),
Value::Duration { val, .. } => Box::new(val),
Value::Date { val, .. } => Box::new(val),
Value::String { val, .. } => Box::new(val),

View File

@ -1,6 +1,7 @@
use crate::date::utils::parse_date_from_string;
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct SubCommand;
@ -17,7 +18,7 @@ impl Command for SubCommand {
(Type::String, Type::record()),
])
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
.category(Category::Date)
.category(Category::Deprecated)
}
fn description(&self) -> &str {
@ -35,6 +36,17 @@ impl Command for SubCommand {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "date to-record".into(),
new_suggestion: "see `into record` command examples".into(),
span: head,
url: "`help into record`".into(),
},
);
let head = call.head;
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {

View File

@ -1,6 +1,7 @@
use crate::date::utils::parse_date_from_string;
use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
use nu_engine::command_prelude::*;
use nu_protocol::{report_parse_warning, ParseWarning};
#[derive(Clone)]
pub struct SubCommand;
@ -17,7 +18,7 @@ impl Command for SubCommand {
(Type::String, Type::table()),
])
.allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032
.category(Category::Date)
.category(Category::Deprecated)
}
fn description(&self) -> &str {
@ -36,6 +37,16 @@ impl Command for SubCommand {
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
report_parse_warning(
&StateWorkingSet::new(engine_state),
&ParseWarning::DeprecatedWarning {
old_command: "date to-table".into(),
new_suggestion: "see `into record` command examples".into(),
span: head,
url: "`help into record`".into(),
},
);
// This doesn't match explicit nulls
if matches!(input, PipelineData::Empty) {
return Err(ShellError::PipelineEmpty { dst_span: head });

View File

@ -177,4 +177,9 @@ fn get_thread_id() -> u64 {
{
nix::sys::pthread::pthread_self() as u64
}
#[cfg(target_arch = "wasm32")]
{
// wasm doesn't have any threads accessible, so we return 0 as a fallback
0
}
}

View File

@ -1,6 +1,6 @@
use super::inspect_table;
use nu_engine::command_prelude::*;
use terminal_size::{terminal_size, Height, Width};
use nu_utils::terminal_size;
#[derive(Clone)]
pub struct Inspect;
@ -38,12 +38,9 @@ impl Command for Inspect {
let original_input = input_val.clone();
let description = input_val.get_type().to_string();
let (cols, _rows) = match terminal_size() {
Some((w, h)) => (Width(w.0), Height(h.0)),
None => (Width(0), Height(0)),
};
let (cols, _rows) = terminal_size().unwrap_or((0, 0));
let table = inspect_table::build_table(input_val, description, cols.0 as usize);
let table = inspect_table::build_table(input_val, description, cols as usize);
// Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell
// tabular output. If we printed to stdout, nushell would get confused with two outputs.

View File

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression_with_input};
use nu_engine::{command_prelude::*, ClosureEvalOnce};
use nu_protocol::engine::Closure;
use std::time::Instant;
#[derive(Clone)]
@ -10,16 +11,18 @@ impl Command for TimeIt {
}
fn description(&self) -> &str {
"Time the running time of a block."
"Time how long it takes a closure to run."
}
fn extra_description(&self) -> &str {
"Any pipeline input given to this command is passed to the closure. Note that streaming inputs may affect timing results, and it is recommended to add a `collect` command before this if the input is a stream.
This command will bubble up any errors encountered when running the closure. The return pipeline of the closure is collected into a value and then discarded."
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("timeit")
.required(
"command",
SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]),
"The command or block to run.",
)
.required("command", SyntaxShape::Closure(None), "The closure to run.")
.input_output_types(vec![
(Type::Any, Type::Duration),
(Type::Nothing, Type::Duration),
@ -46,51 +49,38 @@ impl Command for TimeIt {
// reset outdest, so the command can write to stdout and stderr.
let stack = &mut stack.push_redirection(None, None);
let command_to_run = call.positional_nth(stack, 0);
let closure: Closure = call.req(engine_state, stack, 0)?;
let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure);
// Get the start time after all other computation has been done.
let start_time = Instant::now();
closure.run_with_input(input)?.into_value(call.head)?;
let time = start_time.elapsed();
if let Some(command_to_run) = command_to_run {
if let Some(block_id) = command_to_run.as_block() {
let eval_block = get_eval_block(engine_state);
let block = engine_state.get_block(block_id);
eval_block(engine_state, stack, block, input)?
} else {
let eval_expression_with_input = get_eval_expression_with_input(engine_state);
let expression = &command_to_run.clone();
eval_expression_with_input(engine_state, stack, expression, input)?
}
} else {
PipelineData::empty()
}
.into_value(call.head)?;
let end_time = Instant::now();
let output = Value::duration(
end_time.saturating_duration_since(start_time).as_nanos() as i64,
call.head,
);
let output = Value::duration(time.as_nanos() as i64, call.head);
Ok(output.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Times a command within a closure",
description: "Time a closure containing one command",
example: "timeit { sleep 500ms }",
result: None,
},
Example {
description: "Times a command using an existing input",
example: "http get https://www.nushell.sh/book/ | timeit { split chars }",
description: "Time a closure with an input value",
example: "'A really long string' | timeit { split chars }",
result: None,
},
Example {
description: "Times a command invocation",
example: "timeit ls -la",
description: "Time a closure with an input stream",
example: "open some_file.txt | collect | timeit { split chars }",
result: None,
},
Example {
description: "Time a closure containing a pipeline",
example: "timeit { open some_file.txt | split chars }",
result: None,
},
]

View File

@ -27,6 +27,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
}
// Filters
#[cfg(feature = "rand")]
bind_command! {
Shuffle
}
bind_command! {
All,
Any,
@ -64,6 +68,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Length,
Lines,
ParEach,
ChunkBy,
Prepend,
Range,
Reduce,
@ -71,7 +76,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
Rename,
Reverse,
Select,
Shuffle,
Skip,
SkipUntil,
SkipWhile,
@ -102,6 +106,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
bind_command! {
Path,
PathBasename,
PathSelf,
PathDirname,
PathExists,
PathExpand,
@ -113,6 +118,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
};
// System
#[cfg(feature = "os")]
bind_command! {
Complete,
External,
@ -160,17 +166,20 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
ViewSpan,
};
#[cfg(windows)]
#[cfg(all(feature = "os", windows))]
bind_command! { RegistryQuery }
#[cfg(any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "macos",
target_os = "windows"
#[cfg(all(
feature = "os",
any(
target_os = "android",
target_os = "linux",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "macos",
target_os = "windows"
)
))]
bind_command! { Ps };
@ -218,6 +227,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
};
// FileSystem
#[cfg(feature = "os")]
bind_command! {
Cd,
Ls,
@ -236,6 +246,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
};
// Platform
#[cfg(feature = "os")]
bind_command! {
Ansi,
AnsiLink,
@ -248,11 +259,13 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
IsTerminal,
Kill,
Sleep,
Term,
TermSize,
TermQuery,
Whoami,
};
#[cfg(unix)]
#[cfg(all(unix, feature = "os"))]
bind_command! { ULimit };
// Date
@ -377,6 +390,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
}
// Network
#[cfg(feature = "network")]
bind_command! {
Http,
HttpDelete,
@ -386,6 +400,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
HttpPost,
HttpPut,
HttpOptions,
Port,
}
bind_command! {
Url,
UrlBuildQuery,
UrlSplitQuery,
@ -393,10 +410,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState {
UrlEncode,
UrlJoin,
UrlParse,
Port,
}
// Random
#[cfg(feature = "rand")]
bind_command! {
Random,
RandomBool,

View File

@ -1,4 +1,6 @@
use nu_engine::{command_prelude::*, get_full_help};
use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings, get_full_help};
use nu_system::ForegroundChild;
#[derive(Clone)]
pub struct ConfigMeta;
@ -36,3 +38,79 @@ impl Command for ConfigMeta {
vec!["options", "setup"]
}
}
#[cfg(not(feature = "os"))]
pub(super) fn start_editor(
_: &'static str,
_: &EngineState,
_: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
Err(ShellError::DisabledOsSupport {
msg: "Running external commands is not available without OS support.".to_string(),
span: Some(call.head),
})
}
#[cfg(feature = "os")]
pub(super) fn start_editor(
config_path: &'static str,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
) -> Result<PipelineData, ShellError> {
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable =
crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
})?;
let Some(config_path) = engine_state.get_config_path(config_path) else {
return Err(ShellError::GenericError {
error: format!("Could not find $nu.{config_path}"),
msg: format!("Could not find $nu.{config_path}"),
span: None,
help: None,
inner: vec![],
});
};
let config_path = config_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(config_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
}

View File

@ -1,7 +1,4 @@
use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings};
use nu_protocol::{process::ChildProcess, ByteStream};
use nu_system::ForegroundChild;
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct ConfigEnv;
@ -81,60 +78,6 @@ impl Command for ConfigEnv {
return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data());
}
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(
ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
},
)?;
let Some(env_path) = engine_state.get_config_path("env-path") else {
return Err(ShellError::GenericError {
error: "Could not find $nu.env-path".into(),
msg: "Could not find $nu.env-path".into(),
span: None,
help: None,
inner: vec![],
});
};
let env_path = env_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(env_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
super::config_::start_editor("env-path", engine_state, stack, call)
}
}

View File

@ -1,7 +1,4 @@
use nu_cmd_base::util::get_editor;
use nu_engine::{command_prelude::*, env_to_strings};
use nu_protocol::{process::ChildProcess, ByteStream};
use nu_system::ForegroundChild;
use nu_engine::command_prelude::*;
#[derive(Clone)]
pub struct ConfigNu;
@ -83,60 +80,6 @@ impl Command for ConfigNu {
return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data());
}
// Find the editor executable.
let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?;
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let cwd = engine_state.cwd(Some(stack))?;
let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(
ShellError::ExternalCommand {
label: format!("`{editor_name}` not found"),
help: "Failed to find the editor executable".into(),
span: call.head,
},
)?;
let Some(config_path) = engine_state.get_config_path("config-path") else {
return Err(ShellError::GenericError {
error: "Could not find $nu.config-path".into(),
msg: "Could not find $nu.config-path".into(),
span: None,
help: None,
inner: vec![],
});
};
let config_path = config_path.to_string_lossy().to_string();
// Create the command.
let mut command = std::process::Command::new(editor_executable);
// Configure PWD.
command.current_dir(cwd);
// Configure environment variables.
let envs = env_to_strings(engine_state, stack)?;
command.env_clear();
command.envs(envs);
// Configure args.
command.arg(config_path);
command.args(editor_args);
// Spawn the child process. On Unix, also put the child process to
// foreground if we're in an interactive session.
#[cfg(windows)]
let child = ForegroundChild::spawn(command)?;
#[cfg(unix)]
let child = ForegroundChild::spawn(
command,
engine_state.is_interactive,
&engine_state.pipeline_externals_state,
)?;
// Wrap the output into a `PipelineData::ByteStream`.
let child = ChildProcess::new(child, None, false, call.head)?;
Ok(PipelineData::ByteStream(
ByteStream::child(child, call.head),
None,
))
super::config_::start_editor("config-path", engine_state, stack, call)
}
}

View File

@ -103,3 +103,9 @@ fn is_root_impl() -> bool {
elevated
}
#[cfg(target_arch = "wasm32")]
fn is_root_impl() -> bool {
// in wasm we don't have a user system, so technically we are never root
false
}

View File

@ -1,4 +1,3 @@
use super::util::get_rest_for_glob_pattern;
use crate::{DirBuilder, DirInfo, FileInfo};
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir};
@ -13,8 +12,8 @@ pub struct Du;
#[derive(Deserialize, Clone, Debug)]
pub struct DuArgs {
path: Option<Spanned<NuGlob>>,
all: bool,
deref: bool,
long: bool,
exclude: Option<Spanned<NuGlob>>,
#[serde(rename = "max-depth")]
max_depth: Option<Spanned<i64>>,
@ -50,6 +49,11 @@ impl Command for Du {
"Dereference symlinks to their targets for size",
Some('r'),
)
.switch(
"long",
"Get underlying directories and files for each entry",
Some('l'),
)
.named(
"exclude",
SyntaxShape::GlobPattern,
@ -95,13 +99,13 @@ impl Command for Du {
});
}
}
let all = call.has_flag(engine_state, stack, "all")?;
let deref = call.has_flag(engine_state, stack, "deref")?;
let long = call.has_flag(engine_state, stack, "long")?;
let exclude = call.get_flag(engine_state, stack, "exclude")?;
#[allow(deprecated)]
let current_dir = current_dir(engine_state, stack)?;
let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let paths = if !call.has_positional_args(stack, 0) {
None
} else {
@ -112,8 +116,8 @@ impl Command for Du {
None => {
let args = DuArgs {
path: None,
all,
deref,
long,
exclude,
max_depth,
min_size,
@ -128,8 +132,8 @@ impl Command for Du {
for p in paths {
let args = DuArgs {
path: Some(p),
all,
deref,
long,
exclude: exclude.clone(),
max_depth,
min_size,
@ -175,7 +179,6 @@ fn du_for_one_pattern(
})
})?;
let include_files = args.all;
let mut paths = match args.path {
Some(p) => nu_engine::glob_from(&p, current_dir, span, None),
// The * pattern should never fail.
@ -189,17 +192,10 @@ fn du_for_one_pattern(
None,
),
}
.map(|f| f.1)?
.filter(move |p| {
if include_files {
true
} else {
matches!(p, Ok(f) if f.is_dir())
}
});
.map(|f| f.1)?;
let all = args.all;
let deref = args.deref;
let long = args.long;
let max_depth = args.max_depth.map(|f| f.item as u64);
let min_size = args.min_size.map(|f| f.item as u64);
@ -208,7 +204,7 @@ fn du_for_one_pattern(
min: min_size,
deref,
exclude,
all,
long,
};
let mut output: Vec<Value> = vec![];
@ -217,7 +213,7 @@ fn du_for_one_pattern(
Ok(a) => {
if a.is_dir() {
output.push(DirInfo::new(a, &params, max_depth, span, signals)?.into());
} else if let Ok(v) = FileInfo::new(a, deref, span) {
} else if let Ok(v) = FileInfo::new(a, deref, span, params.long) {
output.push(v.into());
}
}

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::Signals;
use nu_protocol::{ListStream, Signals};
use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry};
#[derive(Clone)]
@ -223,6 +223,7 @@ impl Command for Glob {
..Default::default()
},
)
.into_owned()
.not(np)
.map_err(|err| ShellError::GenericError {
error: "error with glob's not pattern".into(),
@ -249,6 +250,7 @@ impl Command for Glob {
..Default::default()
},
)
.into_owned()
.flatten();
glob_to_value(
engine_state.signals(),
@ -258,11 +260,9 @@ impl Command for Glob {
no_symlinks,
span,
)
}?;
};
Ok(result
.into_iter()
.into_pipeline_data(span, engine_state.signals().clone()))
Ok(result.into_pipeline_data(span, engine_state.signals().clone()))
}
}
@ -281,29 +281,33 @@ fn convert_patterns(columns: &[Value]) -> Result<Vec<String>, ShellError> {
Ok(res)
}
fn glob_to_value<'a>(
fn glob_to_value(
signals: &Signals,
glob_results: impl Iterator<Item = WalkEntry<'a>>,
glob_results: impl Iterator<Item = WalkEntry<'static>> + Send + 'static,
no_dirs: bool,
no_files: bool,
no_symlinks: bool,
span: Span,
) -> Result<Vec<Value>, ShellError> {
let mut result: Vec<Value> = Vec::new();
for entry in glob_results {
signals.check(span)?;
) -> ListStream {
let map_signals = signals.clone();
let result = glob_results.filter_map(move |entry| {
if let Err(err) = map_signals.check(span) {
return Some(Value::error(err, span));
};
let file_type = entry.file_type();
if !(no_dirs && file_type.is_dir()
|| no_files && file_type.is_file()
|| no_symlinks && file_type.is_symlink())
{
result.push(Value::string(
Some(Value::string(
entry.into_path().to_string_lossy().to_string(),
span,
));
))
} else {
None
}
}
});
Ok(result)
ListStream::new(result, span, signals.clone())
}

View File

@ -1,4 +1,3 @@
use super::util::get_rest_for_glob_pattern;
use crate::{DirBuilder, DirInfo};
use chrono::{DateTime, Local, LocalResult, TimeZone, Utc};
use nu_engine::glob_from;
@ -114,7 +113,7 @@ impl Command for Ls {
call_span,
};
let pattern_arg = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let pattern_arg = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let input_pattern_arg = if !call.has_positional_args(stack, 0) {
None
} else {

View File

@ -1,7 +1,6 @@
use super::util::get_rest_for_glob_pattern;
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir, get_eval_block};
use nu_protocol::{ast, ByteStream, DataSource, NuGlob, PipelineMetadata};
use nu_protocol::{ast, DataSource, NuGlob, PipelineMetadata};
use std::path::Path;
#[cfg(feature = "sqlite")]
@ -53,7 +52,7 @@ impl Command for Open {
let call_span = call.head;
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let eval_block = get_eval_block(engine_state);
if paths.is_empty() && !call.has_positional_args(stack, 0) {

View File

@ -1,4 +1,4 @@
use super::util::{get_rest_for_glob_pattern, try_interaction};
use super::util::try_interaction;
#[allow(deprecated)]
use nu_engine::{command_prelude::*, env::current_dir};
use nu_glob::MatchOptions;
@ -118,7 +118,7 @@ fn rm(
let interactive = call.has_flag(engine_state, stack, "interactive")?;
let interactive_once = call.has_flag(engine_state, stack, "interactive-once")? && !interactive;
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
if paths.is_empty() {
return Err(ShellError::MissingParameter {

View File

@ -102,6 +102,7 @@ impl Command for Save {
ByteStreamSource::File(source) => {
stream_to_file(source, size, signals, file, span, progress)?;
}
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => {
fn write_or_consume_stderr(
stderr: ChildPipe,

View File

@ -2,11 +2,8 @@ use filetime::FileTime;
use nu_engine::command_prelude::*;
use nu_path::expand_path_with;
use nu_protocol::NuGlob;
use std::{fs::OpenOptions, time::SystemTime};
use super::util::get_rest_for_glob_pattern;
#[derive(Clone)]
pub struct Touch;
@ -72,7 +69,7 @@ impl Command for Touch {
let no_follow_symlinks: bool = call.has_flag(engine_state, stack, "no-deref")?;
let reference: Option<Spanned<String>> = call.get_flag(engine_state, stack, "reference")?;
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
let files: Vec<Spanned<NuGlob>> = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let files = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let cwd = engine_state.cwd(Some(stack))?;

View File

@ -1,6 +1,6 @@
use super::util::get_rest_for_glob_pattern;
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir};
use nu_protocol::NuGlob;
use std::path::PathBuf;
use uu_cp::{BackupMode, CopyMode, UpdateMode};
@ -156,7 +156,7 @@ impl Command for UCp {
target_os = "macos"
)))]
let reflink_mode = uu_cp::ReflinkMode::Never;
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
if paths.is_empty() {
return Err(ShellError::GenericError {
error: "Missing file operand".into(),

View File

@ -1,12 +1,10 @@
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir};
use nu_protocol::NuGlob;
use uu_mkdir::mkdir;
#[cfg(not(windows))]
use uucore::mode;
use super::util::get_rest_for_glob_pattern;
#[derive(Clone)]
pub struct UMkdir;
@ -61,7 +59,8 @@ impl Command for UMkdir {
) -> Result<PipelineData, ShellError> {
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
let mut directories = get_rest_for_glob_pattern(engine_state, stack, call, 0)?
let mut directories = call
.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?
.into_iter()
.map(|dir| nu_path::expand_path_with(dir.item.as_ref(), &cwd, dir.item.is_expand()))
.peekable();

View File

@ -1,4 +1,3 @@
use super::util::get_rest_for_glob_pattern;
#[allow(deprecated)]
use nu_engine::{command_prelude::*, current_dir};
use nu_path::expand_path_with;
@ -100,7 +99,7 @@ impl Command for UMv {
#[allow(deprecated)]
let cwd = current_dir(engine_state, stack)?;
let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let mut paths = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
if paths.is_empty() {
return Err(ShellError::GenericError {
error: "Missing file operand".into(),

View File

@ -1,6 +1,4 @@
use dialoguer::Input;
use nu_engine::{command_prelude::*, get_eval_expression};
use nu_protocol::{FromValue, NuGlob};
use std::{
error::Error,
path::{Path, PathBuf},
@ -89,22 +87,3 @@ pub fn is_older(src: &Path, dst: &Path) -> Option<bool> {
Some(src_ctime <= dst_ctime)
}
}
/// Get rest arguments from given `call`, starts with `starting_pos`.
///
/// It's similar to `call.rest`, except that it always returns NuGlob.
pub fn get_rest_for_glob_pattern(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
starting_pos: usize,
) -> Result<Vec<Spanned<NuGlob>>, ShellError> {
let eval_expression = get_eval_expression(engine_state);
call.rest_iter_flattened(engine_state, stack, eval_expression, starting_pos)?
.into_iter()
// This used to be much more complex, but I think `FromValue` should be able to handle the
// nuance here.
.map(FromValue::from_value)
.collect()
}

View File

@ -1,19 +1,10 @@
use std::io::ErrorKind;
use std::path::PathBuf;
use chrono::{DateTime, FixedOffset};
use filetime::FileTime;
use nu_engine::CallExt;
use nu_engine::command_prelude::*;
use nu_path::expand_path_with;
use nu_protocol::engine::{Call, Command, EngineState, Stack};
use nu_protocol::{
Category, Example, NuGlob, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type,
};
use uu_touch::error::TouchError;
use uu_touch::{ChangeTimes, InputFile, Options, Source};
use super::util::get_rest_for_glob_pattern;
use nu_protocol::NuGlob;
use std::{io::ErrorKind, path::PathBuf};
use uu_touch::{error::TouchError, ChangeTimes, InputFile, Options, Source};
#[derive(Clone)]
pub struct UTouch;
@ -24,7 +15,7 @@ impl Command for UTouch {
}
fn search_terms(&self) -> Vec<&str> {
vec!["create", "file"]
vec!["create", "file", "coreutils"]
}
fn signature(&self) -> Signature {
@ -91,8 +82,7 @@ impl Command for UTouch {
let change_atime: bool = call.has_flag(engine_state, stack, "access")?;
let no_create: bool = call.has_flag(engine_state, stack, "no-create")?;
let no_deref: bool = call.has_flag(engine_state, stack, "no-dereference")?;
let file_globs: Vec<Spanned<NuGlob>> =
get_rest_for_glob_pattern(engine_state, stack, call, 0)?;
let file_globs = call.rest::<Spanned<NuGlob>>(engine_state, stack, 0)?;
let cwd = engine_state.cwd(Some(stack))?;
if file_globs.is_empty() {

View File

@ -14,7 +14,7 @@ impl Command for All {
.input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Bool)])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"A closure that must evaluate to a boolean.",
)
.category(Category::Filters)

View File

@ -14,7 +14,7 @@ impl Command for Any {
.input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Bool)])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"A closure that must evaluate to a boolean.",
)
.category(Category::Filters)

View File

@ -0,0 +1,256 @@
use super::utils::chain_error_with_input;
use nu_engine::{command_prelude::*, ClosureEval};
use nu_protocol::engine::Closure;
use nu_protocol::Signals;
#[derive(Clone)]
pub struct ChunkBy;
impl Command for ChunkBy {
fn name(&self) -> &str {
"chunk-by"
}
fn signature(&self) -> Signature {
Signature::build("chunk-by")
.input_output_types(vec![
(
Type::List(Box::new(Type::Any)),
Type::list(Type::list(Type::Any)),
),
(Type::Range, Type::list(Type::list(Type::Any))),
])
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run.",
)
.category(Category::Filters)
}
fn description(&self) -> &str {
r#"Divides a sequence into sub-sequences based on a closure."#
}
fn extra_description(&self) -> &str {
r#"chunk-by applies the given closure to each value of the input list, and groups
consecutive elements that share the same closure result value into lists."#
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
chunk_by(engine_state, stack, call, input)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Chunk data into runs of larger than zero or not.",
example: "[1, 3, -2, -2, 0, 1, 2] | chunk-by {|it| $it >= 0 }",
result: Some(Value::test_list(vec![
Value::test_list(vec![Value::test_int(1), Value::test_int(3)]),
Value::test_list(vec![Value::test_int(-2), Value::test_int(-2)]),
Value::test_list(vec![
Value::test_int(0),
Value::test_int(1),
Value::test_int(2),
]),
])),
},
Example {
description: "Identify repetitions in a string",
example: r#"[a b b c c c] | chunk-by { |it| $it }"#,
result: Some(Value::test_list(vec![
Value::test_list(vec![Value::test_string("a")]),
Value::test_list(vec![Value::test_string("b"), Value::test_string("b")]),
Value::test_list(vec![
Value::test_string("c"),
Value::test_string("c"),
Value::test_string("c"),
]),
])),
},
Example {
description: "Chunk values of range by predicate",
example: r#"(0..8) | chunk-by { |it| $it // 3 }"#,
result: Some(Value::test_list(vec![
Value::test_list(vec![
Value::test_int(0),
Value::test_int(1),
Value::test_int(2),
]),
Value::test_list(vec![
Value::test_int(3),
Value::test_int(4),
Value::test_int(5),
]),
Value::test_list(vec![
Value::test_int(6),
Value::test_int(7),
Value::test_int(8),
]),
])),
},
]
}
}
struct Chunk<I, T, F, K> {
iterator: I,
last_value: Option<(T, K)>,
closure: F,
done: bool,
signals: Signals,
}
impl<I, T, F, K> Chunk<I, T, F, K>
where
I: Iterator<Item = T>,
F: FnMut(&T) -> K,
K: PartialEq,
{
fn inner_iterator_next(&mut self) -> Option<I::Item> {
if self.signals.interrupted() {
self.done = true;
return None;
}
self.iterator.next()
}
}
impl<I, T, F, K> Iterator for Chunk<I, T, F, K>
where
I: Iterator<Item = T>,
F: FnMut(&T) -> K,
K: PartialEq,
{
type Item = Vec<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
let (head, head_key) = match self.last_value.take() {
None => {
let head = self.inner_iterator_next()?;
let key = (self.closure)(&head);
(head, key)
}
Some((value, key)) => (value, key),
};
let mut result = vec![head];
loop {
match self.inner_iterator_next() {
None => {
self.done = true;
return Some(result);
}
Some(value) => {
let value_key = (self.closure)(&value);
if value_key == head_key {
result.push(value);
} else {
self.last_value = Some((value, value_key));
return Some(result);
}
}
}
}
}
}
/// An iterator with the semantics of the chunk_by operation.
fn chunk_iter_by<I, T, F, K>(iterator: I, signals: Signals, closure: F) -> Chunk<I, T, F, K>
where
I: Iterator<Item = T>,
F: FnMut(&T) -> K,
K: PartialEq,
{
Chunk {
closure,
iterator,
last_value: None,
done: false,
signals,
}
}
pub fn chunk_by(
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
let head = call.head;
let closure: Closure = call.req(engine_state, stack, 0)?;
let metadata = input.metadata();
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(Value::Range { .. }, ..)
| PipelineData::Value(Value::List { .. }, ..)
| PipelineData::ListStream(..) => {
let closure = ClosureEval::new(engine_state, stack, closure);
let result = chunk_value_stream(
input.into_iter(),
closure,
head,
engine_state.signals().clone(),
);
Ok(result.into_pipeline_data(head, engine_state.signals().clone()))
}
PipelineData::ByteStream(..) | PipelineData::Value(..) => {
Err(input.unsupported_input_error("list", head))
}
}
.map(|data| data.set_metadata(metadata))
}
fn chunk_value_stream<I>(
iterator: I,
mut closure: ClosureEval,
head: Span,
signals: Signals,
) -> impl Iterator<Item = Value> + 'static + Send
where
I: Iterator<Item = Value> + 'static + Send,
{
chunk_iter_by(iterator, signals, move |value| {
match closure.run_with_value(value.clone()) {
Ok(data) => data.into_value(head).unwrap_or_else(|error| {
Value::error(chain_error_with_input(error, value.is_error(), head), head)
}),
Err(error) => Value::error(chain_error_with_input(error, value.is_error(), head), head),
}
})
.map(move |it| Value::list(it, head))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(ChunkBy {})
}
}

View File

@ -29,7 +29,7 @@ impl Command for DropColumn {
}
fn search_terms(&self) -> Vec<&str> {
vec!["delete"]
vec!["delete", "remove"]
}
fn run(

View File

@ -26,7 +26,7 @@ impl Command for Drop {
}
fn search_terms(&self) -> Vec<&str> {
vec!["delete"]
vec!["delete", "remove"]
}
fn examples(&self) -> Vec<Example> {

View File

@ -32,7 +32,7 @@ impl Command for DropNth {
}
fn search_terms(&self) -> Vec<&str> {
vec!["delete"]
vec!["delete", "remove", "index"]
}
fn examples(&self) -> Vec<Example> {

View File

@ -30,7 +30,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."#
])
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"Predicate closure.",
)
.category(Category::Filters)

View File

@ -129,6 +129,8 @@ fn insert(
let replacement: Value = call.req(engine_state, stack, 1)?;
match input {
// Propagate errors in the pipeline
PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error),
PipelineData::Value(mut value, metadata) => {
if let Value::Closure { val, .. } = replacement {
match (cell_path.members.first(), &mut value) {

View File

@ -19,6 +19,7 @@ impl Command for Length {
.input_output_types(vec![
(Type::List(Box::new(Type::Any)), Type::Int),
(Type::Binary, Type::Int),
(Type::Nothing, Type::Int),
])
.category(Category::Filters)
}
@ -54,6 +55,11 @@ impl Command for Length {
example: "0x[01 02] | length",
result: Some(Value::test_int(2)),
},
Example {
description: "Count the length a null value",
example: "null | length",
result: Some(Value::test_int(0)),
},
]
}
}
@ -61,23 +67,19 @@ impl Command for Length {
fn length_row(call: &Call, input: PipelineData) -> Result<PipelineData, ShellError> {
let span = input.span().unwrap_or(call.head);
match input {
PipelineData::Value(Value::Nothing { .. }, ..) => {
PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..) => {
Ok(Value::int(0, call.head).into_pipeline_data())
}
// I added this here because input_output_type() wasn't catching a record
// being sent in as input from echo. e.g. "echo {a:1 b:2} | length"
PipelineData::Value(Value::Record { .. }, ..) => {
Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "list, and table".into(),
wrong_type: "record".into(),
dst_span: call.head,
src_span: span,
})
}
PipelineData::Value(Value::Binary { val, .. }, ..) => {
Ok(Value::int(val.len() as i64, call.head).into_pipeline_data())
}
PipelineData::ByteStream(stream, _) if stream.type_().is_binary_coercible() => {
PipelineData::Value(Value::List { vals, .. }, ..) => {
Ok(Value::int(vals.len() as i64, call.head).into_pipeline_data())
}
PipelineData::ListStream(stream, ..) => {
Ok(Value::int(stream.into_iter().count() as i64, call.head).into_pipeline_data())
}
PipelineData::ByteStream(stream, ..) if stream.type_().is_binary_coercible() => {
Ok(Value::int(
match stream.reader() {
Some(r) => r.bytes().count() as i64,
@ -87,17 +89,12 @@ fn length_row(call: &Call, input: PipelineData) -> Result<PipelineData, ShellErr
)
.into_pipeline_data())
}
_ => {
let mut count: i64 = 0;
// Check for and propagate errors
for value in input.into_iter() {
if let Value::Error { error, .. } = value {
return Err(*error);
}
count += 1
}
Ok(Value::int(count, call.head).into_pipeline_data())
}
_ => Err(ShellError::OnlySupportsThisInputType {
exp_input_type: "list, table, binary, and nothing".into(),
wrong_type: input.get_type().to_string(),
dst_span: call.head,
src_span: span,
}),
}
}

View File

@ -120,6 +120,8 @@ repeating this process with row 1, and so on."#
PipelineData::Value(Value::Record { val: inp, .. }, ..),
Value::Record { val: to_merge, .. },
) => Ok(Value::record(do_merge(inp, &to_merge), head).into_pipeline_data()),
// Propagate errors in the pipeline
(PipelineData::Value(Value::Error { error, .. }, ..), _) => Err(*error.clone()),
(PipelineData::Value(val, ..), ..) => {
// Only point the "value originates here" arrow at the merge value
// if it was generated from a block. Otherwise, point at the pipeline value. -Leon 2022-10-27

View File

@ -1,6 +1,7 @@
mod all;
mod any;
mod append;
mod chunk_by;
mod chunks;
mod columns;
mod compact;
@ -36,6 +37,7 @@ mod reject;
mod rename;
mod reverse;
mod select;
#[cfg(feature = "rand")]
mod shuffle;
mod skip;
mod sort;
@ -58,6 +60,7 @@ mod zip;
pub use all::All;
pub use any::Any;
pub use append::Append;
pub use chunk_by::ChunkBy;
pub use chunks::Chunks;
pub use columns::Columns;
pub use compact::Compact;
@ -93,6 +96,7 @@ pub use reject::Reject;
pub use rename::Rename;
pub use reverse::Reverse;
pub use select::Select;
#[cfg(feature = "rand")]
pub use shuffle::Shuffle;
pub use skip::*;
pub use sort::Sort;

View File

@ -38,7 +38,7 @@ impl Command for ParEach {
)
.required(
"closure",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The closure to run.",
)
.allow_variants_without_examples(true)

View File

@ -24,11 +24,7 @@ impl Command for Reduce {
)
.required(
"closure",
SyntaxShape::Closure(Some(vec![
SyntaxShape::Any,
SyntaxShape::Any,
SyntaxShape::Int,
])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])),
"Reducing function.",
)
.allow_variants_without_examples(true)
@ -88,6 +84,15 @@ impl Command for Reduce {
"Concatenate a string with itself, using a range to determine the number of times.",
result: Some(Value::test_string("StrStrStr")),
},
Example {
example: r#"[{a: 1} {b: 2} {c: 3}] | reduce {|it| merge $it}"#,
description: "Merge multiple records together, making use of the fact that the accumulated value is also supplied as pipeline input to the closure.",
result: Some(Value::test_record(record!(
"a" => Value::test_int(1),
"b" => Value::test_int(2),
"c" => Value::test_int(3),
))),
}
]
}
@ -135,8 +140,8 @@ mod test {
#[test]
fn test_examples() {
use crate::test_examples;
use crate::{test_examples_with_commands, Merge};
test_examples(Reduce {})
test_examples_with_commands(Reduce {}, &[&Merge])
}
}

View File

@ -206,7 +206,6 @@ fn select(
let columns = new_columns;
let input = if !unique_rows.is_empty() {
// let skip = call.has_flag(engine_state, stack, "skip")?;
let metadata = input.metadata();
let pipeline_iter: PipelineIterator = input.into_iter();
@ -231,37 +230,31 @@ fn select(
Value::List {
vals: input_vals, ..
} => {
let mut output = vec![];
let mut columns_with_value = Vec::new();
for input_val in input_vals {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher);
if !columns_with_value.contains(&path) {
columns_with_value.push(path);
Ok(input_vals
.into_iter()
.map(move |input_val| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match input_val.clone().follow_cell_path(&path.members, false) {
Ok(fetcher) => {
record.push(path.to_column_name(), fetcher);
}
}
Err(e) => {
return Err(e);
Err(e) => return Value::error(e, call_span),
}
}
Value::record(record, span)
} else {
input_val.clone()
}
output.push(Value::record(record, span))
} else {
output.push(input_val)
}
}
Ok(output.into_iter().into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
_ => {
if !columns.is_empty() {
@ -286,31 +279,29 @@ fn select(
}
}
PipelineData::ListStream(stream, metadata, ..) => {
let mut values = vec![];
for x in stream {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value);
Ok(stream
.map(move |x| {
if !columns.is_empty() {
let mut record = Record::new();
for path in &columns {
//FIXME: improve implementation to not clone
match x.clone().follow_cell_path(&path.members, false) {
Ok(value) => {
record.push(path.to_column_name(), value);
}
Err(e) => return Value::error(e, call_span),
}
Err(e) => return Err(e),
}
Value::record(record, call_span)
} else {
x
}
values.push(Value::record(record, call_span));
} else {
values.push(x);
}
}
Ok(values.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
})
.into_pipeline_data_with_metadata(
call_span,
engine_state.signals().clone(),
metadata,
))
}
_ => Ok(PipelineData::empty()),
}

View File

@ -20,7 +20,7 @@ impl Command for SkipUntil {
])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The predicate that skipped element must not match.",
)
.category(Category::Filters)

View File

@ -20,7 +20,7 @@ impl Command for SkipWhile {
])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The predicate that skipped element must match.",
)
.category(Category::Filters)

View File

@ -17,7 +17,7 @@ impl Command for TakeUntil {
)])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The predicate that element(s) must not match.",
)
.category(Category::Filters)

View File

@ -20,7 +20,7 @@ impl Command for TakeWhile {
])
.required(
"predicate",
SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])),
SyntaxShape::Closure(Some(vec![SyntaxShape::Any])),
"The predicate that element(s) must match.",
)
.category(Category::Filters)

View File

@ -1,7 +1,9 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
#[cfg(feature = "os")]
use nu_protocol::process::ChildPipe;
use nu_protocol::{
byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, report_shell_error,
ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals,
byte_stream::copy_with_signals, engine::Closure, report_shell_error, ByteStream,
ByteStreamSource, OutDest, PipelineMetadata, Signals,
};
use std::{
io::{self, Read, Write},
@ -152,6 +154,7 @@ use it in your pipeline."#
metadata,
))
}
#[cfg(feature = "os")]
ByteStreamSource::Child(mut child) => {
let stderr_thread = if use_stderr {
let stderr_thread = if let Some(stderr) = child.stderr.take() {
@ -454,6 +457,7 @@ fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), Shell
Ok(())
}
#[cfg(feature = "os")]
fn copy_pipe(pipe: ChildPipe, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> {
match pipe {
ChildPipe::Pipe(pipe) => copy(pipe, dest, info),
@ -477,6 +481,7 @@ fn copy_on_thread(
.map_err(|e| e.into_spanned(span).into())
}
#[cfg(feature = "os")]
fn copy_pipe_on_thread(
pipe: ChildPipe,
dest: impl Write + Send + 'static,

View File

@ -204,12 +204,45 @@ fn from_csv(
#[cfg(test)]
mod test {
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use super::*;
use crate::{Metadata, MetadataSet};
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(FromCsv {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromCsv {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#""a,b\n1,2" | metadata set --content-type 'text/csv' --datasource-ls | from csv | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -93,9 +93,10 @@ pub(super) fn from_delimited_data(
input: PipelineData,
name: Span,
) -> Result<PipelineData, ShellError> {
let metadata = input.metadata().map(|md| md.with_content_type(None));
match input {
PipelineData::Empty => Ok(PipelineData::Empty),
PipelineData::Value(value, metadata) => {
PipelineData::Value(value, ..) => {
let string = value.into_string()?;
let byte_stream = ByteStream::read_string(string, name, Signals::empty());
Ok(PipelineData::ListStream(
@ -109,7 +110,7 @@ pub(super) fn from_delimited_data(
dst_span: name,
src_span: list_stream.span(),
}),
PipelineData::ByteStream(byte_stream, metadata) => Ok(PipelineData::ListStream(
PipelineData::ByteStream(byte_stream, ..) => Ok(PipelineData::ListStream(
from_delimited_stream(config, byte_stream, name)?,
metadata,
)),

View File

@ -70,23 +70,22 @@ impl Command for FromJson {
let span = call.head;
let strict = call.has_flag(engine_state, stack, "strict")?;
let metadata = input.metadata().map(|md| md.with_content_type(None));
// TODO: turn this into a structured underline of the nu_json error
if call.has_flag(engine_state, stack, "objects")? {
// Return a stream of JSON values, one for each non-empty line
match input {
PipelineData::Value(Value::String { val, .. }, metadata) => {
Ok(PipelineData::ListStream(
read_json_lines(
Cursor::new(val),
span,
strict,
engine_state.signals().clone(),
),
metadata,
))
}
PipelineData::ByteStream(stream, metadata)
PipelineData::Value(Value::String { val, .. }, ..) => Ok(PipelineData::ListStream(
read_json_lines(
Cursor::new(val),
span,
strict,
engine_state.signals().clone(),
),
metadata,
)),
PipelineData::ByteStream(stream, ..)
if stream.type_() != ByteStreamType::Binary =>
{
if let Some(reader) = stream.reader() {
@ -107,7 +106,7 @@ impl Command for FromJson {
}
} else {
// Return a single JSON value
let (string_input, span, metadata) = input.collect_string_strict(span)?;
let (string_input, span, ..) = input.collect_string_strict(span)?;
if string_input.is_empty() {
return Ok(Value::nothing(span).into_pipeline_data());
@ -267,6 +266,10 @@ fn convert_string_to_value_strict(string_input: &str, span: Span) -> Result<Valu
#[cfg(test)]
mod test {
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use crate::{Metadata, MetadataSet};
use super::*;
#[test]
@ -275,4 +278,33 @@ mod test {
test_examples(FromJson {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromJson {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#"'{"a":1,"b":2}' | metadata set --content-type 'application/json' --datasource-ls | from json | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -113,7 +113,8 @@ MessagePack: https://msgpack.org/
objects,
signals: engine_state.signals().clone(),
};
match input {
let metadata = input.metadata().map(|md| md.with_content_type(None));
let out = match input {
// Deserialize from a byte buffer
PipelineData::Value(Value::Binary { val: bytes, .. }, _) => {
read_msgpack(Cursor::new(bytes), opts)
@ -136,7 +137,8 @@ MessagePack: https://msgpack.org/
dst_span: call.head,
src_span: input.span().unwrap_or(call.head),
}),
}
};
out.map(|pd| pd.set_metadata(metadata))
}
}
@ -510,6 +512,10 @@ fn assert_eof(input: &mut impl io::Read, span: Span) -> Result<(), ShellError> {
#[cfg(test)]
mod test {
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use crate::{Metadata, MetadataSet, ToMsgpack};
use super::*;
#[test]
@ -518,4 +524,34 @@ mod test {
test_examples(FromMsgpack {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(ToMsgpack {}));
working_set.add_decl(Box::new(FromMsgpack {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#"{a: 1 b: 2} | to msgpack | metadata set --datasource-ls | from msgpack | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -43,7 +43,8 @@ impl Command for FromMsgpackz {
objects,
signals: engine_state.signals().clone(),
};
match input {
let metadata = input.metadata().map(|md| md.with_content_type(None));
let out = match input {
// Deserialize from a byte buffer
PipelineData::Value(Value::Binary { val: bytes, .. }, _) => {
let reader = brotli::Decompressor::new(Cursor::new(bytes), BUFFER_SIZE);
@ -68,6 +69,7 @@ impl Command for FromMsgpackz {
dst_span: call.head,
src_span: span,
}),
}
};
out.map(|pd| pd.set_metadata(metadata))
}
}

View File

@ -49,7 +49,8 @@ impl Command for FromNuon {
let (string_input, _span, metadata) = input.collect_string_strict(head)?;
match nuon::from_nuon(&string_input, Some(head)) {
Ok(result) => Ok(result.into_pipeline_data_with_metadata(metadata)),
Ok(result) => Ok(result
.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None)))),
Err(err) => Err(ShellError::GenericError {
error: "error when loading nuon text".into(),
msg: "could not load nuon text".into(),
@ -63,6 +64,10 @@ impl Command for FromNuon {
#[cfg(test)]
mod test {
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use crate::{Metadata, MetadataSet};
use super::*;
#[test]
@ -71,4 +76,33 @@ mod test {
test_examples(FromNuon {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromNuon {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#"'[[a, b]; [1, 2]]' | metadata set --content-type 'application/x-nuon' --datasource-ls | from nuon | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -46,7 +46,8 @@ impl Command for FromOds {
vec![]
};
from_ods(input, head, sel_sheets)
let metadata = input.metadata().map(|md| md.with_content_type(None));
from_ods(input, head, sel_sheets).map(|pd| pd.set_metadata(metadata))
}
fn examples(&self) -> Vec<Example> {

View File

@ -29,7 +29,8 @@ impl Command for FromToml {
let span = call.head;
let (mut string_input, span, metadata) = input.collect_string_strict(span)?;
string_input.push('\n');
Ok(convert_string_to_value(string_input, span)?.into_pipeline_data_with_metadata(metadata))
Ok(convert_string_to_value(string_input, span)?
.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None))))
}
fn examples(&self) -> Vec<Example> {
@ -144,8 +145,11 @@ pub fn convert_string_to_value(string_input: String, span: Span) -> Result<Value
#[cfg(test)]
mod tests {
use crate::{Metadata, MetadataSet};
use super::*;
use chrono::TimeZone;
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use toml::value::Datetime;
#[test]
@ -331,4 +335,33 @@ mod tests {
assert_eq!(result, reference_date);
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromToml {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#""[a]\nb = 1\nc = 1" | metadata set --content-type 'text/x-toml' --datasource-ls | from toml | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -165,6 +165,10 @@ fn from_tsv(
#[cfg(test)]
mod test {
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use crate::{Metadata, MetadataSet};
use super::*;
#[test]
@ -173,4 +177,33 @@ mod test {
test_examples(FromTsv {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromTsv {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#""a\tb\n1\t2" | metadata set --content-type 'text/tab-separated-values' --datasource-ls | from tsv | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -47,7 +47,8 @@ impl Command for FromXlsx {
vec![]
};
from_xlsx(input, head, sel_sheets)
let metadata = input.metadata().map(|md| md.with_content_type(None));
from_xlsx(input, head, sel_sheets).map(|pd| pd.set_metadata(metadata))
}
fn examples(&self) -> Vec<Example> {

View File

@ -206,7 +206,9 @@ fn from_xml(input: PipelineData, info: &ParsingInfo) -> Result<PipelineData, She
let (concat_string, span, metadata) = input.collect_string_strict(info.span)?;
match from_xml_string_to_value(&concat_string, info) {
Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)),
Ok(x) => {
Ok(x.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None))))
}
Err(err) => Err(process_xml_parse_error(err, span)),
}
}
@ -322,10 +324,14 @@ fn make_cant_convert_error(help: impl Into<String>, span: Span) -> ShellError {
#[cfg(test)]
mod tests {
use crate::Metadata;
use crate::MetadataSet;
use super::*;
use indexmap::indexmap;
use indexmap::IndexMap;
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
fn string(input: impl Into<String>) -> Value {
Value::test_string(input)
@ -480,4 +486,36 @@ mod tests {
test_examples(FromXml {})
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromXml {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#"'<?xml version="1.0" encoding="UTF-8"?>
<note>
<remember>Event</remember>
</note>' | metadata set --content-type 'application/xml' --datasource-ls | from xml | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -235,14 +235,19 @@ fn from_yaml(input: PipelineData, head: Span) -> Result<PipelineData, ShellError
let (concat_string, span, metadata) = input.collect_string_strict(head)?;
match from_yaml_string_to_value(&concat_string, head, span) {
Ok(x) => Ok(x.into_pipeline_data_with_metadata(metadata)),
Ok(x) => {
Ok(x.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None))))
}
Err(other) => Err(other),
}
}
#[cfg(test)]
mod test {
use crate::{Metadata, MetadataSet};
use super::*;
use nu_cmd_lang::eval_pipeline_without_terminal_expression;
use nu_protocol::Config;
#[test]
@ -395,4 +400,33 @@ mod test {
assert!(result.ok().unwrap() == test_case.expected.ok().unwrap());
}
}
#[test]
fn test_content_type_metadata() {
let mut engine_state = Box::new(EngineState::new());
let delta = {
let mut working_set = StateWorkingSet::new(&engine_state);
working_set.add_decl(Box::new(FromYaml {}));
working_set.add_decl(Box::new(Metadata {}));
working_set.add_decl(Box::new(MetadataSet {}));
working_set.render()
};
engine_state
.merge_delta(delta)
.expect("Error merging delta");
let cmd = r#""a: 1\nb: 2" | metadata set --content-type 'application/yaml' --datasource-ls | from yaml | metadata | $in"#;
let result = eval_pipeline_without_terminal_expression(
cmd,
std::env::temp_dir().as_ref(),
&mut engine_state,
);
assert_eq!(
Value::test_record(record!("source" => Value::test_string("ls"))),
result.expect("There should be a result")
)
}
}

View File

@ -109,7 +109,7 @@ pub fn value_to_json_value(v: &Value) -> Result<nu_json::Value, ShellError> {
let span = v.span();
Ok(match v {
Value::Bool { val, .. } => nu_json::Value::Bool(*val),
Value::Filesize { val, .. } => nu_json::Value::I64(*val),
Value::Filesize { val, .. } => nu_json::Value::I64(val.get()),
Value::Duration { val, .. } => nu_json::Value::I64(*val),
Value::Date { val, .. } => nu_json::Value::String(val.to_string()),
Value::Float { val, .. } => nu_json::Value::F64(*val),

View File

@ -168,7 +168,7 @@ pub(crate) fn write_value(
mp::write_f64(out, *val).err_span(span)?;
}
Value::Filesize { val, .. } => {
mp::write_sint(out, *val).err_span(span)?;
mp::write_sint(out, val.get()).err_span(span)?;
}
Value::Duration { val, .. } => {
mp::write_sint(out, *val).err_span(span)?;

View File

@ -47,7 +47,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result<toml::Value, ShellErr
Ok(match &v {
Value::Bool { val, .. } => toml::Value::Boolean(*val),
Value::Int { val, .. } => toml::Value::Integer(*val),
Value::Filesize { val, .. } => toml::Value::Integer(*val),
Value::Filesize { val, .. } => toml::Value::Integer(val.get()),
Value::Duration { val, .. } => toml::Value::String(val.to_string()),
Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)),
Value::Range { .. } => toml::Value::String("<Range>".to_string()),

View File

@ -44,7 +44,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result<serde_yaml::Value, ShellError> {
Ok(match &v {
Value::Bool { val, .. } => serde_yaml::Value::Bool(*val),
Value::Int { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
Value::Filesize { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)),
Value::Filesize { val, .. } => {
serde_yaml::Value::Number(serde_yaml::Number::from(val.get()))
}
Value::Duration { val, .. } => serde_yaml::Value::String(val.to_string()),
Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()),
Value::Range { .. } => serde_yaml::Value::Null,

View File

@ -87,21 +87,10 @@ pub fn help_commands(
name.push_str(&r.item);
}
let output = engine_state
.get_decls_sorted(false)
.into_iter()
.filter_map(|(_, decl_id)| {
let decl = engine_state.get_decl(decl_id);
(decl.name() == name).then_some(decl)
})
.map(|cmd| get_full_help(cmd, engine_state, stack))
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(
Value::string(output.join("======================\n\n"), call.head)
.into_pipeline_data(),
)
if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) {
let cmd = engine_state.get_decl(decl);
let help_text = get_full_help(cmd, engine_state, stack);
Ok(Value::string(help_text, call.head).into_pipeline_data())
} else {
Err(ShellError::CommandNotFound {
span: Span::merge_many(rest.iter().map(|s| s.span)),

View File

@ -107,21 +107,10 @@ pub fn help_externs(
name.push_str(&r.item);
}
let output = engine_state
.get_decls_sorted(false)
.into_iter()
.filter_map(|(_, decl_id)| {
let decl = engine_state.get_decl(decl_id);
(decl.name() == name).then_some(decl)
})
.map(|cmd| get_full_help(cmd, engine_state, stack))
.collect::<Vec<String>>();
if !output.is_empty() {
Ok(
Value::string(output.join("======================\n\n"), call.head)
.into_pipeline_data(),
)
if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) {
let cmd = engine_state.get_decl(decl);
let help_text = get_full_help(cmd, engine_state, stack);
Ok(Value::string(help_text, call.head).into_pipeline_data())
} else {
Err(ShellError::CommandNotFound {
span: Span::merge_many(rest.iter().map(|s| s.span)),

View File

@ -31,7 +31,7 @@ impl Command for HelpOperators {
let mut operators = [
Operator::Assignment(Assignment::Assign),
Operator::Assignment(Assignment::PlusAssign),
Operator::Assignment(Assignment::AppendAssign),
Operator::Assignment(Assignment::ConcatAssign),
Operator::Assignment(Assignment::MinusAssign),
Operator::Assignment(Assignment::MultiplyAssign),
Operator::Assignment(Assignment::DivideAssign),
@ -48,7 +48,7 @@ impl Command for HelpOperators {
Operator::Comparison(Comparison::StartsWith),
Operator::Comparison(Comparison::EndsWith),
Operator::Math(Math::Plus),
Operator::Math(Math::Append),
Operator::Math(Math::Concat),
Operator::Math(Math::Minus),
Operator::Math(Math::Multiply),
Operator::Math(Math::Divide),
@ -144,8 +144,8 @@ fn description(operator: &Operator) -> &'static str {
Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.",
Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.",
Operator::Math(Math::Plus) => "Adds two values.",
Operator::Math(Math::Append) => {
"Appends two lists, a list and a value, two strings, or two binary values."
Operator::Math(Math::Concat) => {
"Concatenates two lists, two strings, or two binary values."
}
Operator::Math(Math::Minus) => "Subtracts two values.",
Operator::Math(Math::Multiply) => "Multiplies two values.",
@ -163,8 +163,8 @@ fn description(operator: &Operator) -> &'static str {
Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.",
Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.",
Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.",
Operator::Assignment(Assignment::AppendAssign) => {
"Appends a list, a value, a string, or a binary value to a variable."
Operator::Assignment(Assignment::ConcatAssign) => {
"Concatenates two lists, two strings, or two binary values."
}
Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.",
Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.",

View File

@ -1,3 +1,4 @@
#![cfg_attr(not(feature = "os"), allow(unused))]
#![doc = include_str!("../README.md")]
mod bytes;
mod charting;
@ -8,6 +9,7 @@ mod default_context;
mod env;
mod example_test;
mod experimental;
#[cfg(feature = "os")]
mod filesystem;
mod filters;
mod formats;
@ -18,8 +20,10 @@ mod math;
mod misc;
mod network;
mod path;
#[cfg(feature = "os")]
mod platform;
mod progress_bar;
#[cfg(feature = "rand")]
mod random;
mod removed;
mod shells;
@ -27,6 +31,7 @@ mod sort_utils;
#[cfg(feature = "sqlite")]
mod stor;
mod strings;
#[cfg(feature = "os")]
mod system;
mod viewers;
@ -40,6 +45,7 @@ pub use env::*;
#[cfg(test)]
pub use example_test::{test_examples, test_examples_with_commands};
pub use experimental::*;
#[cfg(feature = "os")]
pub use filesystem::*;
pub use filters::*;
pub use formats::*;
@ -50,7 +56,9 @@ pub use math::*;
pub use misc::*;
pub use network::*;
pub use path::*;
#[cfg(feature = "os")]
pub use platform::*;
#[cfg(feature = "rand")]
pub use random::*;
pub use removed::*;
pub use shells::*;
@ -58,6 +66,7 @@ pub use sort_utils::*;
#[cfg(feature = "sqlite")]
pub use stor::*;
pub use strings::*;
#[cfg(feature = "os")]
pub use system::*;
pub use viewers::*;

View File

@ -90,7 +90,7 @@ pub fn average(values: &[Value], span: Span, head: Span) -> Result<Value, ShellE
let total = &sum(Value::int(0, head), values.to_vec(), span, head)?;
let span = total.span();
match total {
Value::Filesize { val, .. } => Ok(Value::filesize(val / values.len() as i64, span)),
Value::Filesize { val, .. } => Ok(Value::filesize(val.get() / values.len() as i64, span)),
Value::Duration { val, .. } => Ok(Value::duration(val / values.len() as i64, span)),
_ => total.div(head, &Value::int(values.len() as i64, head), head),
}

View File

@ -142,9 +142,10 @@ pub fn mode(values: &[Value], _span: Span, head: Span) -> Result<Value, ShellErr
Value::Float { val, .. } => {
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float))
}
Value::Filesize { val, .. } => {
Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize))
}
Value::Filesize { val, .. } => Ok(HashableType::new(
val.get().to_ne_bytes(),
NumberTypes::Filesize,
)),
Value::Error { error, .. } => Err(*error.clone()),
other => Err(ShellError::UnsupportedInput {
msg: "Unable to give a result with this input".to_string(),

View File

@ -1,4 +1,5 @@
use nu_engine::{command_prelude::*, get_eval_block_with_early_return};
use nu_path::canonicalize_with;
use nu_protocol::{engine::CommandType, BlockId};
/// Source a file for environment variables.
@ -41,15 +42,57 @@ impl Command for Source {
call: &Call,
input: PipelineData,
) -> Result<PipelineData, ShellError> {
// Note: this hidden positional is the block_id that corresponded to the 0th position
// it is put here by the parser
// Note: two hidden positionals are used here that are injected by the parser:
// 1. The block_id that corresponded to the 0th position
// 2. The block_id_name that corresponded to the file name at the 0th position
let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?;
let block_id_name: String = call.req_parser_info(engine_state, stack, "block_id_name")?;
let block_id = BlockId::new(block_id as usize);
let block = engine_state.get_block(block_id).clone();
let cwd = engine_state.cwd_as_string(Some(stack))?;
let pb = std::path::PathBuf::from(block_id_name);
let parent = pb.parent().unwrap_or(std::path::Path::new(""));
let file_path =
canonicalize_with(pb.as_path(), cwd).map_err(|err| ShellError::FileNotFoundCustom {
msg: format!("Could not access file '{}': {err}", pb.as_path().display()),
span: Span::unknown(),
})?;
// Note: We intentionally left out PROCESS_PATH since it's supposed to
// to work like argv[0] in C, which is the name of the program being executed.
// Since we're not executing a program, we don't need to set it.
// Save the old env vars so we can restore them after the script has ran
let old_file_pwd = stack.get_env_var(engine_state, "FILE_PWD").cloned();
let old_current_file = stack.get_env_var(engine_state, "CURRENT_FILE").cloned();
// Add env vars so they are available to the script
stack.add_env_var(
"FILE_PWD".to_string(),
Value::string(parent.to_string_lossy(), Span::unknown()),
);
stack.add_env_var(
"CURRENT_FILE".to_string(),
Value::string(file_path.to_string_lossy(), Span::unknown()),
);
let eval_block_with_early_return = get_eval_block_with_early_return(engine_state);
let return_result = eval_block_with_early_return(engine_state, stack, &block, input);
eval_block_with_early_return(engine_state, stack, &block, input)
// After the script has ran, restore the old values unless they didn't exist.
// If they didn't exist prior, remove the env vars
if let Some(old_file_pwd) = old_file_pwd {
stack.add_env_var("FILE_PWD".to_string(), old_file_pwd.clone());
} else {
stack.remove_env_var(engine_state, "FILE_PWD");
}
if let Some(old_current_file) = old_current_file {
stack.add_env_var("CURRENT_FILE".to_string(), old_current_file.clone());
} else {
stack.remove_env_var(engine_state, "CURRENT_FILE");
}
return_result
}
fn examples(&self) -> Vec<Example> {

View File

@ -376,7 +376,7 @@ fn send_multipart_request(
format!("Content-Length: {}", val.len()),
];
builder
.add(&mut Cursor::new(val), &headers.join("\n"))
.add(&mut Cursor::new(val), &headers.join("\r\n"))
.map_err(err)?;
} else {
let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col);

View File

@ -1,8 +1,12 @@
#[cfg(feature = "network")]
mod http;
#[cfg(feature = "network")]
mod port;
mod url;
#[cfg(feature = "network")]
pub use self::http::*;
pub use self::url::*;
#[cfg(feature = "network")]
pub use port::SubCommand as Port;

View File

@ -6,6 +6,7 @@ mod join;
mod parse;
pub mod path_;
mod relative_to;
mod self_;
mod split;
mod r#type;
@ -18,6 +19,7 @@ pub use parse::SubCommand as PathParse;
pub use path_::PathCommand as Path;
pub use r#type::SubCommand as PathType;
pub use relative_to::SubCommand as PathRelativeTo;
pub use self_::SubCommand as PathSelf;
pub use split::SubCommand as PathSplit;
use nu_protocol::{ShellError, Span, Value};

View File

@ -0,0 +1,129 @@
use nu_engine::command_prelude::*;
use nu_path::expand_path_with;
use nu_protocol::engine::StateWorkingSet;
#[derive(Clone)]
pub struct SubCommand;
impl Command for SubCommand {
fn name(&self) -> &str {
"path self"
}
fn signature(&self) -> Signature {
Signature::build("path self")
.input_output_type(Type::Nothing, Type::String)
.allow_variants_without_examples(true)
.optional(
"path",
SyntaxShape::Filepath,
"Path to get instead of the current file.",
)
.category(Category::Path)
}
fn description(&self) -> &str {
"Get the absolute path of the script or module containing this command at parse time."
}
fn is_const(&self) -> bool {
true
}
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Err(ShellError::GenericError {
error: "this command can only run during parse-time".into(),
msg: "can't run after parse-time".into(),
span: Some(call.head),
help: Some("try assigning this command's output to a const variable".into()),
inner: vec![],
})
}
fn run_const(
&self,
working_set: &StateWorkingSet,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let path: Option<String> = call.opt_const(working_set, 0)?;
let cwd = working_set.permanent_state.cwd(None)?;
let current_file =
working_set
.files
.top()
.ok_or_else(|| ShellError::FileNotFoundCustom {
msg: "Couldn't find current file".into(),
span: call.head,
})?;
let out = if let Some(path) = path {
let dir = expand_path_with(
current_file
.parent()
.ok_or_else(|| ShellError::FileNotFoundCustom {
msg: "Couldn't find current file's parent.".into(),
span: call.head,
})?,
&cwd,
true,
);
Value::string(
expand_path_with(path, dir, false).to_string_lossy(),
call.head,
)
} else {
Value::string(
expand_path_with(current_file, &cwd, true).to_string_lossy(),
call.head,
)
};
Ok(out.into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get the path of the current file",
example: r#"const current_file = path self"#,
result: None,
},
Example {
description: "Get the path of the directory containing the current file",
example: r#"const current_file = path self ."#,
result: None,
},
#[cfg(windows)]
Example {
description: "Get the absolute form of a path relative to the current file",
example: r#"const current_file = path self ..\foo"#,
result: None,
},
#[cfg(not(windows))]
Example {
description: "Get the absolute form of a path relative to the current file",
example: r#"const current_file = path self ../foo"#,
result: None,
},
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_examples() {
use crate::test_examples;
test_examples(SubCommand {})
}
}

View File

@ -9,7 +9,7 @@ pub struct DirBuilder {
pub min: Option<u64>,
pub deref: bool,
pub exclude: Option<Pattern>,
pub all: bool,
pub long: bool,
}
impl DirBuilder {
@ -18,14 +18,14 @@ impl DirBuilder {
min: Option<u64>,
deref: bool,
exclude: Option<Pattern>,
all: bool,
long: bool,
) -> DirBuilder {
DirBuilder {
tag,
min,
deref,
exclude,
all,
long,
}
}
}
@ -39,6 +39,7 @@ pub struct DirInfo {
blocks: u64,
path: PathBuf,
tag: Span,
long: bool,
}
#[derive(Debug, Clone)]
@ -47,10 +48,16 @@ pub struct FileInfo {
size: u64,
blocks: Option<u64>,
tag: Span,
long: bool,
}
impl FileInfo {
pub fn new(path: impl Into<PathBuf>, deref: bool, tag: Span) -> Result<Self, ShellError> {
pub fn new(
path: impl Into<PathBuf>,
deref: bool,
tag: Span,
long: bool,
) -> Result<Self, ShellError> {
let path = path.into();
let m = if deref {
std::fs::metadata(&path)
@ -67,6 +74,7 @@ impl FileInfo {
blocks: block_size,
size: d.len(),
tag,
long,
})
}
Err(e) => Err(e.into()),
@ -92,6 +100,7 @@ impl DirInfo {
blocks: 0,
tag: params.tag,
path,
long: params.long,
};
match std::fs::metadata(&s.path) {
@ -154,13 +163,13 @@ impl DirInfo {
.as_ref()
.map_or(true, |x| !x.matches_path(&f));
if include {
match FileInfo::new(f, params.deref, self.tag) {
match FileInfo::new(f, params.deref, self.tag, self.long) {
Ok(file) => {
let inc = params.min.map_or(true, |s| file.size >= s);
if inc {
self.size += file.size;
self.blocks += file.blocks.unwrap_or(0);
if params.all {
if params.long {
self.files.push(file);
}
}
@ -197,16 +206,27 @@ impl From<DirInfo> for Value {
// })
// }
Value::record(
record! {
"path" => Value::string(d.path.display().to_string(), d.tag),
"apparent" => Value::filesize(d.size as i64, d.tag),
"physical" => Value::filesize(d.blocks as i64, d.tag),
"directories" => value_from_vec(d.dirs, d.tag),
"files" => value_from_vec(d.files, d.tag)
},
d.tag,
)
if d.long {
Value::record(
record! {
"path" => Value::string(d.path.display().to_string(), d.tag),
"apparent" => Value::filesize(d.size as i64, d.tag),
"physical" => Value::filesize(d.blocks as i64, d.tag),
"directories" => value_from_vec(d.dirs, d.tag),
"files" => value_from_vec(d.files, d.tag)
},
d.tag,
)
} else {
Value::record(
record! {
"path" => Value::string(d.path.display().to_string(), d.tag),
"apparent" => Value::filesize(d.size as i64, d.tag),
"physical" => Value::filesize(d.blocks as i64, d.tag),
},
d.tag,
)
}
}
}
@ -215,16 +235,27 @@ impl From<FileInfo> for Value {
// cols.push("errors".into());
// vals.push(Value::nothing(Span::unknown()));
Value::record(
record! {
"path" => Value::string(f.path.display().to_string(), f.tag),
"apparent" => Value::filesize(f.size as i64, f.tag),
"physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
"directories" => Value::nothing(Span::unknown()),
"files" => Value::nothing(Span::unknown()),
},
f.tag,
)
if f.long {
Value::record(
record! {
"path" => Value::string(f.path.display().to_string(), f.tag),
"apparent" => Value::filesize(f.size as i64, f.tag),
"physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
"directories" => Value::nothing(Span::unknown()),
"files" => Value::nothing(Span::unknown()),
},
f.tag,
)
} else {
Value::record(
record! {
"path" => Value::string(f.path.display().to_string(), f.tag),
"apparent" => Value::filesize(f.size as i64, f.tag),
"physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag),
},
f.tag,
)
}
}
}

View File

@ -5,7 +5,7 @@ mod input;
mod is_terminal;
mod kill;
mod sleep;
mod term_size;
mod term;
#[cfg(unix)]
mod ulimit;
mod whoami;
@ -19,7 +19,7 @@ pub use input::InputListen;
pub use is_terminal::IsTerminal;
pub use kill::Kill;
pub use sleep::Sleep;
pub use term_size::TermSize;
pub use term::{Term, TermQuery, TermSize};
#[cfg(unix)]
pub use ulimit::ULimit;
pub use whoami::Whoami;

View File

@ -70,8 +70,8 @@ impl Command for Sleep {
result: Some(Value::nothing(Span::test_data())),
},
Example {
description: "Sleep for 3sec",
example: "sleep 1sec 1sec 1sec",
description: "Use multiple arguments to write a duration with multiple units, which is unsupported by duration literals",
example: "sleep 1min 30sec",
result: None,
},
Example {

View File

@ -0,0 +1,7 @@
mod term_;
mod term_query;
mod term_size;
pub use term_::Term;
pub use term_query::TermQuery;
pub use term_size::TermSize;

View File

@ -0,0 +1,34 @@
use nu_engine::{command_prelude::*, get_full_help};
#[derive(Clone)]
pub struct Term;
impl Command for Term {
fn name(&self) -> &str {
"term"
}
fn signature(&self) -> Signature {
Signature::build("term")
.category(Category::Platform)
.input_output_types(vec![(Type::Nothing, Type::String)])
}
fn description(&self) -> &str {
"Commands for querying information about the terminal."
}
fn extra_description(&self) -> &str {
"You must use one of the following subcommands. Using this command as-is will only produce this help message."
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data())
}
}

View File

@ -0,0 +1,173 @@
use std::{
io::{Read, Write},
time::Duration,
};
use nu_engine::command_prelude::*;
const CTRL_C: u8 = 3;
#[derive(Clone)]
pub struct TermQuery;
impl Command for TermQuery {
fn name(&self) -> &str {
"term query"
}
fn description(&self) -> &str {
"Query the terminal for information."
}
fn extra_description(&self) -> &str {
"Print the given query, and read the immediate result from stdin.
The standard input will be read right after `query` is printed, and consumed until the `terminator`
sequence is encountered. The `terminator` is not included in the output.
If `terminator` is not supplied, input will be read until Ctrl-C is pressed.
If `prefix` is supplied, input's initial bytes will be validated against it.
The `prefix` is not included in the output."
}
fn signature(&self) -> Signature {
Signature::build("term query")
.category(Category::Platform)
.input_output_types(vec![(Type::Nothing, Type::Binary)])
.allow_variants_without_examples(true)
.required(
"query",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
"The query that will be printed to stdout.",
)
.named(
"prefix",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
"Prefix sequence for the expected reply.",
Some('p'),
)
.named(
"terminator",
SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]),
"Terminator sequence for the expected reply.",
Some('t'),
)
.switch(
"keep",
"Include prefix and terminator in the output.",
Some('k'),
)
}
fn examples(&self) -> Vec<Example> {
vec![
Example {
description: "Get cursor position.",
example: r#"term query (ansi cursor_position) --prefix (ansi csi) --terminator 'R'"#,
result: None,
},
Example {
description: "Get terminal background color.",
example: r#"term query $'(ansi osc)10;?(ansi st)' --prefix $'(ansi osc)10;' --terminator (ansi st)"#,
result: None,
},
Example {
description: "Get terminal background color. (some terminals prefer `char bel` rather than `ansi st` as string terminator)",
example: r#"term query $'(ansi osc)10;?(char bel)' --prefix $'(ansi osc)10;' --terminator (char bel)"#,
result: None,
},
Example {
description: "Read clipboard content on terminals supporting OSC-52.",
example: r#"term query $'(ansi osc)52;c;?(ansi st)' --prefix $'(ansi osc)52;c;' --terminator (ansi st)"#,
result: None,
},
]
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let query: Vec<u8> = call.req(engine_state, stack, 0)?;
let keep = call.has_flag(engine_state, stack, "keep")?;
let prefix: Option<Vec<u8>> = call.get_flag(engine_state, stack, "prefix")?;
let prefix = prefix.unwrap_or_default();
let terminator: Option<Vec<u8>> = call.get_flag(engine_state, stack, "terminator")?;
crossterm::terminal::enable_raw_mode()?;
scopeguard::defer! {
let _ = crossterm::terminal::disable_raw_mode();
}
// clear terminal events
while crossterm::event::poll(Duration::from_secs(0))? {
// If there's an event, read it to remove it from the queue
let _ = crossterm::event::read()?;
}
let mut b = [0u8; 1];
let mut buf = vec![];
let mut stdin = std::io::stdin().lock();
{
let mut stdout = std::io::stdout().lock();
stdout.write_all(&query)?;
stdout.flush()?;
}
// Validate and skip prefix
for bc in prefix {
stdin.read_exact(&mut b)?;
if b[0] != bc {
return Err(ShellError::GenericError {
error: "Input did not begin with expected sequence".into(),
msg: "".into(),
span: None,
help: Some("Try running without `--prefix` and inspecting the output.".into()),
inner: vec![],
});
}
if keep {
buf.push(b[0]);
}
}
if let Some(terminator) = terminator {
loop {
stdin.read_exact(&mut b)?;
if b[0] == CTRL_C {
return Err(ShellError::InterruptedByUser {
span: Some(call.head),
});
}
buf.push(b[0]);
if buf.ends_with(&terminator) {
if !keep {
// Remove terminator
buf.drain((buf.len() - terminator.len())..);
}
break;
}
}
} else {
loop {
stdin.read_exact(&mut b)?;
if b[0] == CTRL_C {
break;
}
buf.push(b[0]);
}
};
Ok(Value::binary(buf, call.head).into_pipeline_data())
}
}

View File

@ -1,5 +1,5 @@
use crossterm::terminal::size;
use nu_engine::command_prelude::*;
use terminal_size::{terminal_size, Height, Width};
#[derive(Clone)]
pub struct TermSize;
@ -51,15 +51,12 @@ impl Command for TermSize {
) -> Result<PipelineData, ShellError> {
let head = call.head;
let (cols, rows) = match terminal_size() {
Some((w, h)) => (Width(w.0), Height(h.0)),
None => (Width(0), Height(0)),
};
let (cols, rows) = size().unwrap_or((0, 0));
Ok(Value::record(
record! {
"columns" => Value::int(cols.0 as i64, head),
"rows" => Value::int(rows.0 as i64, head),
"columns" => Value::int(cols as i64, head),
"rows" => Value::int(rows as i64, head),
},
head,
)

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::format_filesize_from_conf;
use rand::{thread_rng, RngCore};
#[derive(Clone)]
@ -37,7 +37,27 @@ impl Command for SubCommand {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
let length = call.req(engine_state, stack, 0)?;
let length_val = call.req(engine_state, stack, 0)?;
let length = match length_val {
Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: val.to_string(),
span: length_val.span(),
}),
Value::Filesize { val, .. } => {
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: format_filesize_from_conf(val, engine_state.get_config()),
span: length_val.span(),
})
}
val => Err(ShellError::RuntimeTypeMismatch {
expected: Type::custom("int or filesize"),
actual: val.get_type(),
span: val.span(),
}),
}?;
let mut rng = thread_rng();
let mut out = vec![0u8; length];

View File

@ -1,5 +1,5 @@
use nu_engine::command_prelude::*;
use nu_protocol::format_filesize_from_conf;
use rand::{
distributions::{Alphanumeric, Distribution},
thread_rng,
@ -73,14 +73,36 @@ fn chars(
call: &Call,
) -> Result<PipelineData, ShellError> {
let span = call.head;
let length: Option<usize> = call.get_flag(engine_state, stack, "length")?;
let length: Option<Value> = call.get_flag(engine_state, stack, "length")?;
let length = if let Some(length_val) = length {
match length_val {
Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: val.to_string(),
span: length_val.span(),
}),
Value::Filesize { val, .. } => {
usize::try_from(val).map_err(|_| ShellError::InvalidValue {
valid: "a non-negative int or filesize".into(),
actual: format_filesize_from_conf(val, engine_state.get_config()),
span: length_val.span(),
})
}
val => Err(ShellError::RuntimeTypeMismatch {
expected: Type::custom("int or filesize"),
actual: val.get_type(),
span: val.span(),
}),
}?
} else {
DEFAULT_CHARS_LENGTH
};
let chars_length = length.unwrap_or(DEFAULT_CHARS_LENGTH);
let mut rng = thread_rng();
let random_string = Alphanumeric
.sample_iter(&mut rng)
.take(chars_length)
.take(length)
.map(char::from)
.collect::<String>();

View File

@ -12,7 +12,7 @@ impl Command for SubCommand {
fn signature(&self) -> Signature {
Signature::build("random dice")
.input_output_types(vec![(Type::Nothing, Type::ListStream)])
.input_output_types(vec![(Type::Nothing, Type::list(Type::Int))])
.allow_variants_without_examples(true)
.named(
"dice",

View File

@ -307,7 +307,7 @@ fn test_one_newline() {
correct_counts.insert(Counter::GraphemeClusters, 1);
correct_counts.insert(Counter::Bytes, 1);
correct_counts.insert(Counter::CodePoints, 1);
correct_counts.insert(Counter::UnicodeWidth, 0);
correct_counts.insert(Counter::UnicodeWidth, 1);
assert_eq!(correct_counts, counts);
}
@ -347,7 +347,7 @@ fn test_count_counts_lines() {
// one more than grapheme clusters because of \r\n
correct_counts.insert(Counter::CodePoints, 24);
correct_counts.insert(Counter::UnicodeWidth, 17);
correct_counts.insert(Counter::UnicodeWidth, 23);
assert_eq!(correct_counts, counts);
}

View File

@ -45,6 +45,7 @@ On Windows based systems, Nushell will wait for the command to finish and then e
call.head,
engine_state,
stack,
&cwd,
));
};
executable

View File

@ -15,7 +15,6 @@ impl Command for NuCheck {
Signature::build("nu-check")
.input_output_types(vec![
(Type::String, Type::Bool),
(Type::ListStream, Type::Bool),
(Type::List(Box::new(Type::Any)), Type::Bool),
])
// type is string to avoid automatically canonicalizing the path

View File

@ -1,6 +1,6 @@
use nu_cmd_base::hook::eval_hook;
use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression};
use nu_path::{dots::expand_ndots, expand_tilde};
use nu_engine::{command_prelude::*, env_to_strings};
use nu_path::{dots::expand_ndots, expand_tilde, AbsolutePath};
use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals};
use nu_system::ForegroundChild;
use nu_utils::IgnoreCaseExt;
@ -126,7 +126,13 @@ impl Command for External {
// effect if it's an absolute path already
let paths = nu_engine::env::path_str(engine_state, stack, call.head)?;
let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else {
return Err(command_not_found(&name_str, call.head, engine_state, stack));
return Err(command_not_found(
&name_str,
call.head,
engine_state,
stack,
&cwd,
));
};
executable
};
@ -289,8 +295,7 @@ pub fn eval_arguments_from_call(
call: &Call,
) -> Result<Vec<Spanned<OsString>>, ShellError> {
let cwd = engine_state.cwd(Some(stack))?;
let eval_expression = get_eval_expression(engine_state);
let call_args = call.rest_iter_flattened(engine_state, stack, eval_expression, 1)?;
let call_args = call.rest::<Value>(engine_state, stack, 1)?;
let mut args: Vec<Spanned<OsString>> = Vec::with_capacity(call_args.len());
for arg in call_args {
@ -433,6 +438,7 @@ pub fn command_not_found(
span: Span,
engine_state: &EngineState,
stack: &mut Stack,
cwd: &AbsolutePath,
) -> ShellError {
// Run the `command_not_found` hook if there is one.
if let Some(hook) = &stack.get_config(engine_state).hooks.command_not_found {
@ -543,12 +549,12 @@ pub fn command_not_found(
}
// If we find a file, it's likely that the user forgot to set permissions
if Path::new(name).is_file() {
if cwd.join(name).is_file() {
return ShellError::ExternalCommand {
label: format!("Command `{name}` not found"),
help: format!("`{name}` refers to a file that is not executable. Did you forget to set execute permissions?"),
span,
};
label: format!("Command `{name}` not found"),
help: format!("`{name}` refers to a file that is not executable. Did you forget to set execute permissions?"),
span,
};
}
// We found nothing useful. Give up and return a generic error message.

View File

@ -13,6 +13,11 @@ impl Command for SysCpu {
fn signature(&self) -> Signature {
Signature::build("sys cpu")
.filter()
.switch(
"long",
"Get all available columns (slower, needs to sample CPU over time)",
Some('l'),
)
.category(Category::System)
.input_output_types(vec![(Type::Nothing, Type::table())])
}
@ -23,12 +28,13 @@ impl Command for SysCpu {
fn run(
&self,
_engine_state: &EngineState,
_stack: &mut Stack,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(cpu(call.head).into_pipeline_data())
let long = call.has_flag(engine_state, stack, "long")?;
Ok(cpu(long, call.head).into_pipeline_data())
}
fn examples(&self) -> Vec<Example> {
@ -40,38 +46,44 @@ impl Command for SysCpu {
}
}
fn cpu(span: Span) -> Value {
fn cpu(long: bool, span: Span) -> Value {
let mut sys = System::new();
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
if long {
sys.refresh_cpu_specifics(CpuRefreshKind::everything());
// We must refresh the CPU twice a while apart to get valid usage data.
// In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that
// that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily
std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2);
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage());
} else {
sys.refresh_cpu_specifics(CpuRefreshKind::new().with_frequency());
}
let cpus = sys
.cpus()
.iter()
.map(|cpu| {
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0;
let load_avg = System::load_average();
let load_avg = format!(
"{:.2}, {:.2}, {:.2}",
load_avg.one, load_avg.five, load_avg.fifteen
);
let record = record! {
let mut record = record! {
"name" => Value::string(trim_cstyle_null(cpu.name()), span),
"brand" => Value::string(trim_cstyle_null(cpu.brand()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"cpu_usage" => Value::float(rounded_usage.into(), span),
"load_average" => Value::string(load_avg, span),
"vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span),
"freq" => Value::int(cpu.frequency() as i64, span),
"load_average" => Value::string(load_avg, span),
};
if long {
// sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes.
// Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats.
let rounded_usage = (f64::from(cpu.cpu_usage()) * 10.0).round() / 10.0;
record.push("cpu_usage", rounded_usage.into_value(span));
}
Value::record(record, span)
})
.collect();

View File

@ -4,9 +4,8 @@ use lscolors::Style;
use nu_engine::{command_prelude::*, env_to_string};
use nu_protocol::Config;
use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions};
use nu_utils::get_ls_colors;
use nu_utils::{get_ls_colors, terminal_size};
use std::path::Path;
use terminal_size::{Height, Width};
#[derive(Clone)]
pub struct Griddle;
@ -192,7 +191,7 @@ fn create_grid_output(
let cols = if let Some(col) = width_param {
col as u16
} else if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() {
} else if let Ok((w, _h)) = terminal_size() {
w
} else {
80u16

View File

@ -2,7 +2,14 @@
// overall reduce the redundant calls to StyleComputer etc.
// the goal is to configure it once...
use lscolors::{LsColors, Style};
use std::{
collections::VecDeque,
io::{IsTerminal, Read},
path::PathBuf,
str::FromStr,
time::Instant,
};
use nu_color_config::{color_from_hex, StyleComputer, TextStyle};
use nu_engine::{command_prelude::*, env_to_string};
use nu_path::form::Absolute;
@ -15,17 +22,12 @@ use nu_table::{
NuTable, StringResult, TableOpts, TableOutput,
};
use nu_utils::get_ls_colors;
use std::{
collections::VecDeque,
io::{IsTerminal, Read},
path::PathBuf,
str::FromStr,
time::Instant,
};
use terminal_size::{Height, Width};
use lscolors::{LsColors, Style};
use url::Url;
type ShellResult<T> = Result<T, ShellError>;
type NuPathBuf = nu_path::PathBuf<Absolute>;
const STREAM_PAGE_SIZE: usize = 1000;
const DEFAULT_TABLE_WIDTH: usize = 80;
@ -119,6 +121,7 @@ impl Command for Table {
let val = Value::list(supported_table_modes(), Span::test_data());
return Ok(val.into_pipeline_data());
}
let input = CmdInput::parse(engine_state, stack, call, input)?;
// reset vt processing, aka ansi because illbehaved externals can break it
@ -384,7 +387,7 @@ struct CmdInput<'a> {
call: &'a Call<'a>,
data: PipelineData,
cfg: TableConfig,
cwd: nu_path::PathBuf<Absolute>,
cwd: Option<NuPathBuf>,
}
impl<'a> CmdInput<'a> {
@ -394,8 +397,8 @@ impl<'a> CmdInput<'a> {
call: &'a Call<'a>,
data: PipelineData,
) -> ShellResult<Self> {
let cwd = engine_state.cwd(Some(stack))?;
let cfg = parse_table_config(call, engine_state, stack)?;
let cwd = get_cwd(engine_state, stack)?;
Ok(Self {
engine_state,
@ -746,6 +749,13 @@ fn make_clickable_link(
) -> String {
// uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
#[cfg(any(
unix,
windows,
target_os = "redox",
target_os = "wasi",
target_os = "hermit"
))]
if show_clickable_links {
format!(
"\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\",
@ -761,6 +771,18 @@ fn make_clickable_link(
None => full_path,
}
}
#[cfg(not(any(
unix,
windows,
target_os = "redox",
target_os = "wasi",
target_os = "hermit"
)))]
match link_name {
Some(link_name) => link_name.to_string(),
None => full_path,
}
}
struct PagingTableCreator {
@ -980,14 +1002,18 @@ fn render_path_name(
path: &str,
config: &Config,
ls_colors: &LsColors,
cwd: nu_path::PathBuf<Absolute>,
cwd: Option<NuPathBuf>,
span: Span,
) -> Option<Value> {
if !config.ls.use_ls_colors {
return None;
}
let fullpath = cwd.join(path);
let fullpath = match cwd {
Some(cwd) => PathBuf::from(cwd.join(path)),
None => PathBuf::from(path),
};
let stripped_path = nu_utils::strip_ansi_unlikely(path);
let metadata = std::fs::symlink_metadata(fullpath);
let has_metadata = metadata.is_ok();
@ -1124,10 +1150,20 @@ fn create_table_opts<'a>(
TableOpts::new(cfg, comp, signals, span, width, theme, offset, index)
}
fn get_table_width(width: Option<i64>) -> usize {
if let Some(col) = width {
fn get_cwd(engine_state: &EngineState, stack: &mut Stack) -> ShellResult<Option<NuPathBuf>> {
#[cfg(feature = "os")]
let cwd = engine_state.cwd(Some(stack)).map(Some)?;
#[cfg(not(feature = "os"))]
let cwd = None;
Ok(cwd)
}
fn get_table_width(width_param: Option<i64>) -> usize {
if let Some(col) = width_param {
col as usize
} else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() {
} else if let Ok((w, _h)) = crossterm::terminal::size() {
w as usize
} else {
DEFAULT_TABLE_WIDTH

View File

@ -1,88 +0,0 @@
use nu_test_support::nu;
#[test]
fn append_assign_int() {
let actual = nu!(r#"
mut a = [1 2];
$a ++= [3 4];
$a == [1 2 3 4]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_string() {
let actual = nu!(r#"
mut a = [a b];
$a ++= [c d];
$a == [a b c d]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_any() {
let actual = nu!(r#"
mut a = [1 2 a];
$a ++= [b 3];
$a == [1 2 a b 3]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_both_empty() {
let actual = nu!(r#"
mut a = [];
$a ++= [];
$a == []
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_type_mismatch() {
let actual = nu!(r#"
mut a = [1 2];
$a ++= [a];
$a == [1 2 "a"]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_single_element() {
let actual = nu!(r#"
mut a = ["list" "and"];
$a ++= "a single element";
$a == ["list" "and" "a single element"]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_to_single_element() {
let actual = nu!(r#"
mut a = "string";
$a ++= ["and" "the" "list"];
$a == ["string" "and" "the" "list"]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn append_assign_single_to_single() {
let actual = nu!(r#"
mut a = 1;
$a ++= "and a single element";
"#);
assert!(actual.err.contains("nu::parser::unsupported_operation"));
}

View File

@ -0,0 +1,76 @@
use nu_test_support::nu;
#[test]
fn concat_assign_list_int() {
let actual = nu!(r#"
mut a = [1 2];
$a ++= [3 4];
$a == [1 2 3 4]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn concat_assign_list_string() {
let actual = nu!(r#"
mut a = [a b];
$a ++= [c d];
$a == [a b c d]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn concat_assign_any() {
let actual = nu!(r#"
mut a = [1 2 a];
$a ++= [b 3];
$a == [1 2 a b 3]
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn concat_assign_both_empty() {
let actual = nu!(r#"
mut a = [];
$a ++= [];
$a == []
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn concat_assign_string() {
let actual = nu!(r#"
mut a = 'hello';
$a ++= ' world';
$a == 'hello world'
"#);
assert_eq!(actual.out, "true")
}
#[test]
fn concat_assign_type_mismatch() {
let actual = nu!(r#"
mut a = [];
$a ++= 'str'
"#);
assert!(actual.err.contains("nu::parser::unsupported_operation"));
}
#[test]
fn concat_assign_runtime_type_mismatch() {
let actual = nu!(r#"
mut a = [];
$a ++= if true { 'str' }
"#);
assert!(actual.err.contains("nu::shell::type_mismatch"));
}

View File

@ -1 +1 @@
mod append_assign;
mod concat;

View File

@ -0,0 +1,58 @@
use nu_test_support::{nu, pipeline};
#[test]
fn chunk_by_on_empty_input_returns_empty_list() {
let actual = nu!("[] | chunk-by {|it| $it} | to nuon");
assert!(actual.err.is_empty());
assert_eq!(actual.out, "[]");
}
#[test]
fn chunk_by_strings_works() {
let sample = r#"
[a a a b b b c c c a a a]
"#;
let actual = nu!(pipeline(&format!(
r#"
{sample}
| chunk-by {{|it| $it}}
| to nuon
"#
)));
assert_eq!(actual.out, "[[a, a, a], [b, b, b], [c, c, c], [a, a, a]]");
}
#[test]
fn chunk_by_field_works() {
let sample = r#"[
{
name: bob,
age: 20,
cool: false
},
{
name: jane,
age: 30,
cool: false
},
{
name: marie,
age: 19,
cool: true
},
{
name: carl,
age: 36,
cool: true
} ]"#;
let actual = nu!(pipeline(&format!(
r#"{sample}
| chunk-by {{|it| $it.cool}}
| length"#
)));
assert_eq!(actual.out, "2");
}

View File

@ -2,7 +2,7 @@ use nu_test_support::nu;
#[test]
fn timeit_show_stdout() {
let actual = nu!("let t = timeit nu --testbin cococo abcdefg");
let actual = nu!("let t = timeit { nu --testbin cococo abcdefg }");
assert_eq!(actual.out, "abcdefg");
}

View File

@ -44,7 +44,7 @@ fn do_with_semicolon_break_on_failed_external() {
fn ignore_shell_errors_works_for_external_with_semicolon() {
let actual = nu!(r#"do -s { open asdfasdf.txt }; "text""#);
assert_eq!(actual.err, "");
assert!(actual.err.contains("Deprecated option"));
assert_eq!(actual.out, "text");
}
@ -52,7 +52,7 @@ fn ignore_shell_errors_works_for_external_with_semicolon() {
fn ignore_program_errors_works_for_external_with_semicolon() {
let actual = nu!(r#"do -p { nu -n -c 'exit 1' }; "text""#);
assert_eq!(actual.err, "");
assert!(actual.err.contains("Deprecated option"));
assert_eq!(actual.out, "text");
}
@ -80,6 +80,7 @@ fn run_closure_with_it_using() {
#[test]
fn waits_for_external() {
let actual = nu!(r#"do -p { nu -c 'sleep 1sec; print before; exit 1'}; print after"#);
assert!(actual.err.is_empty());
assert!(actual.err.contains("Deprecated option"));
assert_eq!(actual.out, "beforeafter");
}

Some files were not shown because too many files have changed in this diff Show More