Move Value to helpers, separate span call (#10121)

# Description

As part of the refactor to split spans off of Value, this moves to using
helper functions to create values, and using `.span()` instead of
matching span out of Value directly.

Hoping to get a few more helping hands to finish this, as there are a
lot of commands to update :)

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

# Tests + Formatting
<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->

---------

Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
Co-authored-by: WindSoilder <windsoilder@outlook.com>
This commit is contained in:
JT
2023-09-04 02:27:29 +12:00
committed by GitHub
parent af79eb2943
commit 6cdfee3573
372 changed files with 5811 additions and 7448 deletions

View File

@ -86,53 +86,35 @@ impl Command for Fill {
description:
"Fill a string on the left side to a width of 15 with the character '─'",
example: "'nushell' | fill -a l -c '─' -w 15",
result: Some(Value::String {
val: "nushell────────".into(),
span: Span::test_data(),
}),
result: Some(Value::string("nushell────────", Span::test_data())),
},
Example {
description:
"Fill a string on the right side to a width of 15 with the character '─'",
example: "'nushell' | fill -a r -c '─' -w 15",
result: Some(Value::String {
val: "────────nushell".into(),
span: Span::test_data(),
}),
result: Some(Value::string("────────nushell", Span::test_data())),
},
Example {
description: "Fill a string on both sides to a width of 15 with the character '─'",
example: "'nushell' | fill -a m -c '─' -w 15",
result: Some(Value::String {
val: "────nushell────".into(),
span: Span::test_data(),
}),
result: Some(Value::string("────nushell────", Span::test_data())),
},
Example {
description:
"Fill a number on the left side to a width of 5 with the character '0'",
example: "1 | fill --alignment right --character '0' --width 5",
result: Some(Value::String {
val: "00001".into(),
span: Span::test_data(),
}),
result: Some(Value::string("00001", Span::test_data())),
},
Example {
description: "Fill a number on both sides to a width of 5 with the character '0'",
example: "1.1 | fill --alignment center --character '0' --width 5",
result: Some(Value::String {
val: "01.10".into(),
span: Span::test_data(),
}),
result: Some(Value::string("01.10", Span::test_data())),
},
Example {
description:
"Fill a filesize on the left side to a width of 5 with the character '0'",
example: "1kib | fill --alignment middle --character '0' --width 10",
result: Some(Value::String {
val: "0001024000".into(),
span: Span::test_data(),
}),
result: Some(Value::string("0001024000", Span::test_data())),
},
]
}
@ -198,15 +180,15 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Value::String { val, .. } => fill_string(val, args, span),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "int, filesize, float, string".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
}),
},
span,
},
),
}
}
@ -214,18 +196,18 @@ fn fill_float(num: f64, args: &Arguments, span: Span) -> Value {
let s = num.to_string();
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
Value::string(out_str, span)
}
fn fill_int(num: i64, args: &Arguments, span: Span) -> Value {
let s = num.to_string();
let out_str = pad(&s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
Value::string(out_str, span)
}
fn fill_string(s: &str, args: &Arguments, span: Span) -> Value {
let out_str = pad(s, args.width, &args.character, args.alignment, false);
Value::String { val: out_str, span }
Value::string(out_str, span)
}
fn pad(s: &str, width: usize, pad_char: &str, alignment: FillAlignment, truncate: bool) -> String {

View File

@ -72,29 +72,29 @@ impl Command for SubCommand {
Example {
description: "convert string to a nushell binary primitive",
example: "'This is a string that is exactly 52 characters long.' | into binary",
result: Some(Value::Binary {
val: "This is a string that is exactly 52 characters long."
result: Some(Value::binary(
"This is a string that is exactly 52 characters long."
.to_string()
.as_bytes()
.to_vec(),
span: Span::test_data(),
}),
Span::test_data(),
)),
},
Example {
description: "convert a number to a nushell binary primitive",
example: "1 | into binary",
result: Some(Value::Binary {
val: i64::from(1).to_ne_bytes().to_vec(),
span: Span::test_data(),
}),
result: Some(Value::binary(
i64::from(1).to_ne_bytes().to_vec(),
Span::test_data(),
)),
},
Example {
description: "convert a boolean to a nushell binary primitive",
example: "true | into binary",
result: Some(Value::Binary {
val: i64::from(1).to_ne_bytes().to_vec(),
span: Span::test_data(),
}),
result: Some(Value::binary(
i64::from(1).to_ne_bytes().to_vec(),
Span::test_data(),
)),
},
Example {
description: "convert a filesize to a nushell binary primitive",
@ -109,19 +109,16 @@ impl Command for SubCommand {
Example {
description: "convert a decimal to a nushell binary primitive",
example: "1.234 | into binary",
result: Some(Value::Binary {
val: 1.234f64.to_ne_bytes().to_vec(),
span: Span::test_data(),
}),
result: Some(Value::binary(
1.234f64.to_ne_bytes().to_vec(),
Span::test_data(),
)),
},
Example {
description:
"convert an integer to a nushell binary primitive with compact enabled",
example: "10 | into binary --compact",
result: Some(Value::Binary {
val: vec![10],
span: Span::test_data(),
}),
result: Some(Value::binary(vec![10], Span::test_data())),
},
]
}
@ -138,22 +135,16 @@ fn into_binary(
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::Binary {
val: vec![],
span: head,
PipelineData::ExternalStream { stdout: None, .. } => {
Ok(Value::binary(vec![], head).into_pipeline_data())
}
.into_pipeline_data()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_bytes()?;
Ok(Value::Binary {
val: output.item,
span: head,
}
.into_pipeline_data())
Ok(Value::binary(output.item, head).into_pipeline_data())
}
_ => {
let args = Arguments {
@ -168,50 +159,32 @@ fn into_binary(
pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
let value = match input {
Value::Binary { .. } => input.clone(),
Value::Int { val, .. } => Value::Binary {
val: val.to_ne_bytes().to_vec(),
span,
},
Value::Float { val, .. } => Value::Binary {
val: val.to_ne_bytes().to_vec(),
span,
},
Value::Filesize { val, .. } => Value::Binary {
val: val.to_ne_bytes().to_vec(),
span,
},
Value::String { val, .. } => Value::Binary {
val: val.as_bytes().to_vec(),
span,
},
Value::Bool { val, .. } => Value::Binary {
val: i64::from(*val).to_ne_bytes().to_vec(),
span,
},
Value::Duration { val, .. } => Value::Binary {
val: val.to_ne_bytes().to_vec(),
span,
},
Value::Date { val, .. } => Value::Binary {
val: val.format("%c").to_string().as_bytes().to_vec(),
span,
},
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::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),
Value::Date { val, .. } => {
Value::binary(val.format("%c").to_string().as_bytes().to_vec(), span)
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "integer, float, filesize, string, date, duration, binary or bool"
.into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
}),
},
span,
},
),
};
if _args.compact {
if let Value::Binary { val, span } = value {
let val_span = value.span();
if let Value::Binary { val, .. } = value {
let val = if cfg!(target_endian = "little") {
match val.iter().rposition(|&x| x != 0) {
Some(idx) => &val[..idx + 1],
@ -224,10 +197,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value {
}
};
Value::Binary {
val: val.to_vec(),
span,
}
Value::binary(val.to_vec(), val_span)
} else {
value
}

View File

@ -58,8 +58,8 @@ impl Command for SubCommand {
Example {
description: "Convert value to boolean in table",
example: "[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value",
result: Some(Value::List {
vals: vec![
result: Some(Value::list(
vec![
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::bool(false, span)],
@ -82,7 +82,7 @@ impl Command for SubCommand {
}),
],
span,
}),
)),
},
Example {
description: "Convert bool to boolean",
@ -149,32 +149,23 @@ fn string_to_boolean(s: &str, span: Span) -> Result<bool, ShellError> {
fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
match input {
Value::Bool { .. } => input.clone(),
Value::Int { val, .. } => Value::Bool {
val: *val != 0,
span,
},
Value::Float { val, .. } => Value::Bool {
val: val.abs() >= f64::EPSILON,
span,
},
Value::Int { val, .. } => Value::bool(*val != 0, span),
Value::Float { val, .. } => Value::bool(val.abs() >= f64::EPSILON, span),
Value::String { val, .. } => match string_to_boolean(val, span) {
Ok(val) => Value::Bool { val, span },
Err(error) => Value::Error {
error: Box::new(error),
span,
},
Ok(val) => Value::bool(val, span),
Err(error) => Value::error(error, span),
},
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "bool, integer, float or string".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
}),
},
span,
},
),
}
}

View File

@ -34,16 +34,16 @@ impl Command for Into {
call: &Call,
_input: PipelineData,
) -> Result<PipelineData, ShellError> {
Ok(Value::String {
val: get_full_help(
Ok(Value::string(
get_full_help(
&Into.signature(),
&[],
engine_state,
stack,
self.is_parser_keyword(),
),
span: call.head,
}
call.head,
)
.into_pipeline_data())
}
}

View File

@ -154,10 +154,10 @@ impl Command for SubCommand {
fn examples(&self) -> Vec<Example> {
let example_result_1 = |nanos: i64| {
Some(Value::Date {
val: Utc.timestamp_nanos(nanos).into(),
span: Span::test_data(),
})
Some(Value::date(
Utc.timestamp_nanos(nanos).into(),
Span::test_data(),
))
};
vec![
Example {
@ -195,35 +195,35 @@ impl Command for SubCommand {
Example {
description: "Convert list of timestamps to datetimes",
example: r#"["2023-03-30 10:10:07 -05:00", "2023-05-05 13:43:49 -05:00", "2023-06-05 01:37:42 -05:00"] | into datetime"#,
result: Some(Value::List {
vals: vec![
Value::Date {
val: DateTime::parse_from_str(
result: Some(Value::list(
vec![
Value::date(
DateTime::parse_from_str(
"2023-03-30 10:10:07 -05:00",
"%Y-%m-%d %H:%M:%S %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
Value::Date {
val: DateTime::parse_from_str(
Span::test_data(),
),
Value::date(
DateTime::parse_from_str(
"2023-05-05 13:43:49 -05:00",
"%Y-%m-%d %H:%M:%S %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
Value::Date {
val: DateTime::parse_from_str(
Span::test_data(),
),
Value::date(
DateTime::parse_from_str(
"2023-06-05 01:37:42 -05:00",
"%Y-%m-%d %H:%M:%S %z",
)
.expect("date calculation should not fail in test"),
span: Span::test_data(),
},
Span::test_data(),
),
],
span: Span::test_data(),
}),
Span::test_data(),
)),
},
]
}
@ -240,12 +240,7 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
if matches!(input, Value::String { .. }) && dateformat.is_none() {
if let Ok(input_val) = input.as_spanned_string() {
match parse_date_from_string(&input_val.item, input_val.span) {
Ok(date) => {
return Value::Date {
val: date,
span: input_val.span,
}
}
Ok(date) => return Value::date(date, input_val.span),
Err(err) => err,
};
}
@ -259,15 +254,15 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return input.clone(),
other => {
return Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
}),
span: head,
};
},
head,
);
}
};
@ -277,107 +272,87 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
// note all these `.timestamp_nanos()` could overflow if we didn't check range in `<date> | into int`.
// default to UTC
None => Value::Date {
val: Utc.timestamp_nanos(ts).into(),
span: head,
},
None => Value::date(Utc.timestamp_nanos(ts).into(), head),
Some(Spanned { item, span }) => match item {
Zone::Utc => {
let dt = Utc.timestamp_nanos(ts);
Value::Date {
val: dt.into(),
span: *span,
}
Value::date(dt.into(), *span)
}
Zone::Local => {
let dt = Local.timestamp_nanos(ts);
Value::Date {
val: dt.into(),
span: *span,
}
Value::date(dt.into(), *span)
}
Zone::East(i) => match FixedOffset::east_opt((*i as i32) * HOUR) {
Some(eastoffset) => {
let dt = eastoffset.timestamp_nanos(ts);
Value::Date {
val: dt,
span: *span,
}
Value::date(dt, *span)
}
None => Value::Error {
error: Box::new(ShellError::DatetimeParseError(
input.debug_value(),
*span,
)),
span: *span,
},
None => Value::error(
ShellError::DatetimeParseError(input.debug_value(), *span),
*span,
),
},
Zone::West(i) => match FixedOffset::west_opt((*i as i32) * HOUR) {
Some(westoffset) => {
let dt = westoffset.timestamp_nanos(ts);
Value::Date {
val: dt,
span: *span,
}
Value::date(dt, *span)
}
None => Value::Error {
error: Box::new(ShellError::DatetimeParseError(
input.debug_value(),
*span,
)),
span: *span,
},
None => Value::error(
ShellError::DatetimeParseError(input.debug_value(), *span),
*span,
),
},
Zone::Error => Value::Error {
Zone::Error => Value::error(
// This is an argument error, not an input error
error: Box::new(ShellError::TypeMismatch {
ShellError::TypeMismatch {
err_message: "Invalid timezone or offset".to_string(),
span: *span,
}),
span: *span,
},
},
*span,
),
},
};
};
}
// If input is not a timestamp, try parsing it as a string
let span = input.span();
match input {
Value::String { val, span } => {
Value::String { val, .. } => {
match dateformat {
Some(dt) => match DateTime::parse_from_str(val, &dt.0) {
Ok(d) => Value::Date { val: d, span: head },
Ok(d) => Value::date ( d, head ),
Err(reason) => {
Value::Error {
error: Box::new(ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) }),
span: head,
}
Value::error (
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt.0), from_type: reason.to_string(), span: head, help: Some("you can use `into datetime` without a format string to enable flexible parsing".to_string()) },
head,
)
}
},
// Tries to automatically parse the date
// (i.e. without a format string)
// and assumes the system's local timezone if none is specified
None => match parse_date_from_string(val, *span) {
Ok(date) => Value::Date {
val: date,
span: *span,
},
None => match parse_date_from_string(val, span) {
Ok(date) => Value::date (
date,
span,
),
Err(err) => err,
},
}
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
}),
span: head,
},
},
head,
),
}
}
@ -404,11 +379,10 @@ mod tests {
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z")
.unwrap(),
span: Span::test_data(),
};
let expected = Value::date(
DateTime::parse_from_str("16.11.1984 8:00 am +0000", "%d.%m.%Y %H:%M %P %z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
@ -421,11 +395,10 @@ mod tests {
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z")
.unwrap(),
span: Span::test_data(),
};
let expected = Value::date(
DateTime::parse_from_str("2020-08-04T16:39:18+00:00", "%Y-%m-%dT%H:%M:%S%z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
@ -442,11 +415,10 @@ mod tests {
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(),
span: Span::test_data(),
};
let expected = Value::date(
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
@ -464,11 +436,10 @@ mod tests {
cell_paths: None,
};
let actual = action(&date_int, &args, Span::test_data());
let expected = Value::Date {
val: DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z")
.unwrap(),
span: Span::test_data(),
};
let expected = Value::date(
DateTime::parse_from_str("2021-02-27 21:55:40 +08:00", "%Y-%m-%d %H:%M:%S %z").unwrap(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
@ -486,10 +457,10 @@ mod tests {
cell_paths: None,
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: Local.timestamp_opt(1614434140, 0).unwrap().into(),
span: Span::test_data(),
};
let expected = Value::date(
Local.timestamp_opt(1614434140, 0).unwrap().into(),
Span::test_data(),
);
assert_eq!(actual, expected)
}
@ -504,10 +475,10 @@ mod tests {
};
let actual = action(&date_str, &args, Span::test_data());
let expected = Value::Date {
val: Utc.timestamp_opt(1614434140, 0).unwrap().into(),
span: Span::test_data(),
};
let expected = Value::date(
Utc.timestamp_opt(1614434140, 0).unwrap().into(),
Span::test_data(),
);
assert_eq!(actual, expected)
}

View File

@ -62,13 +62,13 @@ impl Command for SubCommand {
Example {
description: "Convert string to decimal in table",
example: "[[num]; ['5.01']] | into decimal num",
result: Some(Value::List {
vals: vec![Value::test_record(Record {
result: Some(Value::list(
vec![Value::test_record(Record {
cols: vec!["num".to_string()],
vals: vec![Value::test_float(5.01)],
})],
span: Span::test_data(),
}),
Span::test_data(),
)),
},
Example {
description: "Convert string to decimal",
@ -93,43 +93,44 @@ impl Command for SubCommand {
}
fn action(input: &Value, _args: &CellPathOnlyArgs, head: Span) -> Value {
let span = input.span();
match input {
Value::Float { .. } => input.clone(),
Value::String { val: s, span } => {
Value::String { val: s, .. } => {
let other = s.trim();
match other.parse::<f64>() {
Ok(x) => Value::float(x, head),
Err(reason) => Value::Error {
error: Box::new(ShellError::CantConvert {
Err(reason) => Value::error(
ShellError::CantConvert {
to_type: "float".to_string(),
from_type: reason.to_string(),
span: *span,
span,
help: None,
}),
span: *span,
},
},
span,
),
}
}
Value::Int { val: v, span } => Value::float(*v as f64, *span),
Value::Bool { val: b, span } => Value::Float {
val: match b {
Value::Int { val: v, .. } => Value::float(*v as f64, span),
Value::Bool { val: b, .. } => Value::float(
match b {
true => 1.0,
false => 0.0,
},
span: *span,
},
span,
),
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string, integer or bool".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
}),
span: head,
},
},
head,
),
}
}

View File

@ -62,71 +62,50 @@ impl Command for SubCommand {
Example {
description: "Convert duration string to duration value",
example: "'7min' | into duration",
result: Some(Value::Duration {
val: 7 * 60 * NS_PER_SEC,
span,
}),
result: Some(Value::duration(7 * 60 * NS_PER_SEC, span)),
},
Example {
description: "Convert compound duration string to duration value",
example: "'1day 2hr 3min 4sec' | into duration",
result: Some(Value::Duration {
val: (((((/* 1 * */24) + 2) * 60) + 3) * 60 + 4) * NS_PER_SEC,
result: Some(Value::duration(
(((((/* 1 * */24) + 2) * 60) + 3) * 60 + 4) * NS_PER_SEC,
span,
}),
)),
},
Example {
description: "Convert table of duration strings to table of duration values",
example:
"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value",
result: Some(Value::List {
vals: vec![
result: Some(Value::list(
vec![
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: NS_PER_SEC,
span,
}],
vals: vec![Value::duration(NS_PER_SEC, span)],
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 2 * 60 * NS_PER_SEC,
span,
}],
vals: vec![Value::duration(2 * 60 * NS_PER_SEC, span)],
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 3 * 60 * 60 * NS_PER_SEC,
span,
}],
vals: vec![Value::duration(3 * 60 * 60 * NS_PER_SEC, span)],
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 4 * 24 * 60 * 60 * NS_PER_SEC,
span,
}],
vals: vec![Value::duration(4 * 24 * 60 * 60 * NS_PER_SEC, span)],
}),
Value::test_record(Record {
cols: vec!["value".to_string()],
vals: vec![Value::Duration {
val: 5 * 7 * 24 * 60 * 60 * NS_PER_SEC,
span,
}],
vals: vec![Value::duration(5 * 7 * 24 * 60 * 60 * NS_PER_SEC, span)],
}),
],
span,
}),
)),
},
Example {
description: "Convert duration to duration",
example: "420sec | into duration",
result: Some(Value::Duration {
val: 7 * 60 * NS_PER_SEC,
span,
}),
result: Some(Value::duration(7 * 60 * NS_PER_SEC, span)),
},
]
}
@ -154,10 +133,7 @@ fn into_duration(
let r =
ret.update_cell_path(&path.members, Box::new(move |old| action(old, span)));
if let Err(error) = r {
return Value::Error {
error: Box::new(error),
span,
};
return Value::error(error, span);
}
}
@ -227,29 +203,24 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
}
fn action(input: &Value, span: Span) -> Value {
let value_span = input.span();
match input {
Value::Duration { .. } => input.clone(),
Value::String {
val,
span: value_span,
} => match compound_to_duration(val, *value_span) {
Ok(val) => Value::Duration { val, span },
Err(error) => Value::Error {
error: Box::new(error),
span,
},
Value::String { val, .. } => match compound_to_duration(val, value_span) {
Ok(val) => Value::duration(val, span),
Err(error) => Value::error(error, span),
},
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or duration".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
}),
},
span,
},
),
}
}

View File

@ -79,69 +79,45 @@ impl Command for SubCommand {
Example {
description: "Convert string to filesize in table",
example: r#"[[device size]; ["/dev/sda1" "200"] ["/dev/loop0" "50"]] | into filesize size"#,
result: Some(Value::List {
vals: vec![
result: Some(Value::list(
vec![
Value::test_record(Record {
cols: vec!["device".to_string(), "size".to_string()],
vals: vec![
Value::String {
val: "/dev/sda1".to_string(),
span: Span::test_data(),
},
Value::Filesize {
val: 200,
span: Span::test_data(),
},
Value::string("/dev/sda1".to_string(), Span::test_data()),
Value::filesize(200, Span::test_data()),
],
}),
Value::test_record(Record {
cols: vec!["device".to_string(), "size".to_string()],
vals: vec![
Value::String {
val: "/dev/loop0".to_string(),
span: Span::test_data(),
},
Value::Filesize {
val: 50,
span: Span::test_data(),
},
Value::string("/dev/loop0".to_string(), Span::test_data()),
Value::filesize(50, Span::test_data()),
],
}),
],
span: Span::test_data(),
}),
Span::test_data(),
)),
},
Example {
description: "Convert string to filesize",
example: "'2' | into filesize",
result: Some(Value::Filesize {
val: 2,
span: Span::test_data(),
}),
result: Some(Value::filesize(2, Span::test_data())),
},
Example {
description: "Convert decimal to filesize",
example: "8.3 | into filesize",
result: Some(Value::Filesize {
val: 8,
span: Span::test_data(),
}),
result: Some(Value::filesize(8, Span::test_data())),
},
Example {
description: "Convert int to filesize",
example: "5 | into filesize",
result: Some(Value::Filesize {
val: 5,
span: Span::test_data(),
}),
result: Some(Value::filesize(5, Span::test_data())),
},
Example {
description: "Convert file size to filesize",
example: "4KB | into filesize",
result: Some(Value::Filesize {
val: 4000,
span: Span::test_data(),
}),
result: Some(Value::filesize(4000, Span::test_data())),
},
]
}
@ -151,37 +127,22 @@ pub fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value {
let value_span = input.span();
match input {
Value::Filesize { .. } => input.clone(),
Value::Int { val, .. } => Value::Filesize {
val: *val,
span: value_span,
},
Value::Float { val, .. } => Value::Filesize {
val: *val as i64,
span: value_span,
},
Value::Int { val, .. } => Value::filesize(*val, value_span),
Value::Float { val, .. } => Value::filesize(*val as i64, value_span),
Value::String { val, .. } => match int_from_string(val, value_span) {
Ok(val) => Value::Filesize {
val,
span: value_span,
},
Err(error) => Value::Error {
error: Box::new(error),
span: value_span,
},
Ok(val) => Value::filesize(val, value_span),
Err(error) => Value::error(error, value_span),
},
Value::Nothing { .. } => Value::Filesize {
val: 0,
span: value_span,
},
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
Value::Nothing { .. } => Value::filesize(0, value_span),
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: value_span,
}),
},
span,
},
),
}
}
fn int_from_string(a_string: &str, span: Span) -> Result<i64, ShellError> {

View File

@ -107,33 +107,44 @@ impl Command for SubCommand {
let radix = call.get_flag::<Value>(engine_state, stack, "radix")?;
let radix: u32 = match radix {
Some(Value::Int { val, span }) => {
if !(2..=36).contains(&val) {
return Err(ShellError::TypeMismatch {
err_message: "Radix must lie in the range [2, 36]".to_string(),
span,
});
Some(val) => {
let span = val.span();
match val {
Value::Int { val, .. } => {
if !(2..=36).contains(&val) {
return Err(ShellError::TypeMismatch {
err_message: "Radix must lie in the range [2, 36]".to_string(),
span,
});
}
val as u32
}
_ => 10,
}
val as u32
}
Some(_) => 10,
None => 10,
};
let endian = call.get_flag::<Value>(engine_state, stack, "endian")?;
let little_endian = match endian {
Some(Value::String { val, span }) => match val.as_str() {
"native" => cfg!(target_endian = "little"),
"little" => true,
"big" => false,
_ => {
return Err(ShellError::TypeMismatch {
err_message: "Endian must be one of native, little, big".to_string(),
span,
})
Some(val) => {
let span = val.span();
match val {
Value::String { val, .. } => match val.as_str() {
"native" => cfg!(target_endian = "little"),
"little" => true,
"big" => false,
_ => {
return Err(ShellError::TypeMismatch {
err_message: "Endian must be one of native, little, big"
.to_string(),
span,
})
}
},
_ => false,
}
},
Some(_) => false,
}
None => cfg!(target_endian = "little"),
};
@ -175,10 +186,10 @@ impl Command for SubCommand {
Example {
description: "Convert bool to integer",
example: "[false, true] | into int",
result: Some(Value::List {
vals: vec![Value::test_int(0), Value::test_int(1)],
span: Span::test_data(),
}),
result: Some(Value::list(
vec![Value::test_int(0), Value::test_int(1)],
Span::test_data(),
)),
},
Example {
description: "Convert date to integer (Unix nanosecond timestamp)",
@ -217,6 +228,7 @@ impl Command for SubCommand {
fn action(input: &Value, args: &Arguments, span: Span) -> Value {
let radix = args.radix;
let little_endian = args.little_endian;
let val_span = input.span();
match input {
Value::Int { val: _, .. } => {
if radix == 10 {
@ -225,47 +237,35 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
convert_int(input, span, radix)
}
}
Value::Filesize { val, .. } => Value::Int { val: *val, span },
Value::Float { val, .. } => Value::Int {
val: {
Value::Filesize { val, .. } => Value::int(*val, span),
Value::Float { val, .. } => Value::int(
{
if radix == 10 {
*val as i64
} else {
match convert_int(
&Value::Int {
val: *val as i64,
span,
},
span,
radix,
)
.as_i64()
{
match convert_int(&Value::int(*val as i64, span), span, radix).as_i64() {
Ok(v) => v,
_ => {
return Value::Error {
error: Box::new(ShellError::CantConvert {
return Value::error(
ShellError::CantConvert {
to_type: "float".to_string(),
from_type: "integer".to_string(),
span,
help: None,
}),
},
span,
}
)
}
}
}
},
span,
},
),
Value::String { val, .. } => {
if radix == 10 {
match int_from_string(val, span) {
Ok(val) => Value::Int { val, span },
Err(error) => Value::Error {
error: Box::new(error),
span,
},
Ok(val) => Value::int(val, span),
Err(error) => Value::error(error, span),
}
} else {
convert_int(input, span, radix)
@ -273,15 +273,12 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
}
Value::Bool { val, .. } => {
if *val {
Value::Int { val: 1, span }
Value::int(1, span)
} else {
Value::Int { val: 0, span }
Value::int(0, span)
}
}
Value::Date {
val,
span: val_span,
} => {
Value::Date { val, .. } => {
if val
< &FixedOffset::east_opt(0)
.expect("constant")
@ -293,23 +290,20 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
.with_ymd_and_hms(2262, 4, 11, 23, 47, 16)
.unwrap()
{
Value::Error {
error: Box::new(ShellError::IncorrectValue {
Value::error (
ShellError::IncorrectValue {
msg: "DateTime out of range for timestamp: 1677-09-21T00:12:43Z to 2262-04-11T23:47:16".to_string(),
val_span: *val_span,
val_span,
call_span: span,
}),
},
span,
}
)
} else {
Value::Int {
val: val.timestamp_nanos(),
span,
}
Value::int(val.timestamp_nanos(), span)
}
}
Value::Duration { val, .. } => Value::Int { val: *val, span },
Value::Binary { val, span } => {
Value::Duration { val, .. } => Value::int(*val, span),
Value::Binary { val, .. } => {
use byteorder::{BigEndian, ByteOrder, LittleEndian};
let mut val = val.to_vec();
@ -320,28 +314,28 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
}
val.resize(8, 0);
Value::int(LittleEndian::read_i64(&val), *span)
Value::int(LittleEndian::read_i64(&val), val_span)
} else {
while val.len() < 8 {
val.insert(0, 0);
}
val.resize(8, 0);
Value::int(BigEndian::read_i64(&val), *span)
Value::int(BigEndian::read_i64(&val), val_span)
}
}
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => input.clone(),
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "integer, float, filesize, date, string, binary, duration or bool"
.into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
}),
},
span,
},
),
}
}
@ -357,27 +351,22 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
{
match int_from_string(val, head) {
Ok(x) => return Value::int(x, head),
Err(e) => {
return Value::Error {
error: Box::new(e),
span: head,
}
}
Err(e) => return Value::error(e, head),
}
} else if val.starts_with("00") {
// It's a padded string
match i64::from_str_radix(val, radix) {
Ok(n) => return Value::int(n, head),
Err(e) => {
return Value::Error {
error: Box::new(ShellError::CantConvert {
return Value::error(
ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "int".to_string(),
span: head,
help: Some(e.to_string()),
}),
span: head,
}
},
head,
)
}
}
}
@ -386,28 +375,28 @@ fn convert_int(input: &Value, head: Span, radix: u32) -> Value {
// Propagate errors by explicitly matching them before the final case.
Value::Error { .. } => return input.clone(),
other => {
return Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string and integer".into(),
wrong_type: other.get_type().to_string(),
dst_span: head,
src_span: other.span(),
}),
span: head,
};
},
head,
);
}
};
match i64::from_str_radix(i.trim(), radix) {
Ok(n) => Value::int(n, head),
Err(_reason) => Value::Error {
error: Box::new(ShellError::CantConvert {
Err(_reason) => Value::error(
ShellError::CantConvert {
to_type: "string".to_string(),
from_type: "int".to_string(),
span: head,
help: None,
}),
span: head,
},
},
head,
),
}
}

View File

@ -62,9 +62,9 @@ impl Command for SubCommand {
result: Some(Value::test_record(Record {
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
vals: vec![
Value::Int { val: 1, span },
Value::Int { val: 2, span },
Value::Int { val: 3, span },
Value::int(1, span),
Value::int(2, span),
Value::int(3, span),
],
})),
},
@ -74,9 +74,9 @@ impl Command for SubCommand {
result: Some(Value::test_record(Record {
cols: vec!["0".to_string(), "1".to_string(), "2".to_string()],
vals: vec![
Value::Int { val: 0, span },
Value::Int { val: 1, span },
Value::Int { val: 2, span },
Value::int(0, span),
Value::int(1, span),
Value::int(2, span),
],
})),
},
@ -92,14 +92,11 @@ impl Command for SubCommand {
"sign".into(),
],
vals: vec![
Value::Int { val: 71, span },
Value::Int { val: 3, span },
Value::Int { val: 4, span },
Value::Int { val: 5, span },
Value::String {
val: "-".into(),
span,
},
Value::int(71, span),
Value::int(3, span),
Value::int(4, span),
Value::int(5, span),
Value::string("-", span),
],
})),
},
@ -108,7 +105,7 @@ impl Command for SubCommand {
example: "{a: 1, b: 2} | into record",
result: Some(Value::test_record(Record {
cols: vec!["a".to_string(), "b".to_string()],
vals: vec![Value::Int { val: 1, span }, Value::Int { val: 2, span }],
vals: vec![Value::int(1, span), Value::int(2, span)],
})),
},
Example {
@ -125,16 +122,13 @@ impl Command for SubCommand {
"timezone".into(),
],
vals: vec![
Value::Int { val: 2020, span },
Value::Int { val: 4, span },
Value::Int { val: 12, span },
Value::Int { val: 22, span },
Value::Int { val: 10, span },
Value::Int { val: 57, span },
Value::String {
val: "+02:00".to_string(),
span,
},
Value::int(2020, span),
Value::int(4, span),
Value::int(12, span),
Value::int(22, span),
Value::int(10, span),
Value::int(57, span),
Value::string("+02:00".to_string(), span),
],
})),
},
@ -149,10 +143,11 @@ fn into_record(
) -> Result<PipelineData, ShellError> {
let input = input.into_value(call.head);
let input_type = input.get_type();
let span = input.span();
let res = match input {
Value::Date { val, span } => parse_date_into_record(val, span),
Value::Duration { val, span } => parse_duration_into_record(val, span),
Value::List { mut vals, span } => match input_type {
Value::Date { val, .. } => parse_date_into_record(val, span),
Value::Duration { val, .. } => parse_duration_into_record(val, span),
Value::List { mut vals, .. } => match input_type {
Type::Table(..) if vals.len() == 1 => vals.pop().expect("already checked 1 item"),
_ => Value::record(
vals.into_iter()
@ -162,24 +157,24 @@ fn into_record(
span,
),
},
Value::Range { val, span } => Value::record(
Value::Range { val, .. } => Value::record(
val.into_range_iter(engine_state.ctrlc.clone())?
.enumerate()
.map(|(idx, val)| (format!("{idx}"), val))
.collect(),
span,
),
Value::Record { val, span } => Value::Record { val, span },
Value::Record { val, .. } => Value::record(val, span),
Value::Error { .. } => input,
other => Value::Error {
error: Box::new(ShellError::OnlySupportsThisInputType {
other => Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string".into(),
wrong_type: other.get_type().to_string(),
dst_span: call.head,
src_span: other.span(),
}),
span: call.head,
},
},
call.head,
),
};
Ok(res.into_pipeline_data())
}

View File

@ -182,22 +182,16 @@ fn string_helper(
};
match input {
PipelineData::ExternalStream { stdout: None, .. } => Ok(Value::String {
val: String::new(),
span: head,
PipelineData::ExternalStream { stdout: None, .. } => {
Ok(Value::string(String::new(), head).into_pipeline_data())
}
.into_pipeline_data()),
PipelineData::ExternalStream {
stdout: Some(stream),
..
} => {
// TODO: in the future, we may want this to stream out, converting each to bytes
let output = stream.into_string()?;
Ok(Value::String {
val: output.item,
span: head,
}
.into_pipeline_data())
Ok(Value::string(output.item, head).into_pipeline_data())
}
_ => operate(action, args, input, head, engine_state.ctrlc.clone()),
}
@ -211,80 +205,53 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value {
Value::Int { val, .. } => {
let decimal_value = digits.unwrap_or(0) as usize;
let res = format_int(*val, false, decimal_value);
Value::String { val: res, span }
Value::string(res, span)
}
Value::Float { val, .. } => {
if decimals {
let decimal_value = digits.unwrap_or(2) as usize;
Value::String {
val: format!("{val:.decimal_value$}"),
span,
}
Value::string(format!("{val:.decimal_value$}"), span)
} else {
Value::String {
val: val.to_string(),
span,
}
Value::string(val.to_string(), span)
}
}
Value::Bool { val, .. } => Value::String {
val: val.to_string(),
span,
},
Value::Date { val, .. } => Value::String {
val: val.format("%c").to_string(),
span,
},
Value::String { val, .. } => Value::String {
val: val.to_string(),
span,
},
Value::Bool { val, .. } => Value::string(val.to_string(), span),
Value::Date { val, .. } => Value::string(val.format("%c").to_string(), span),
Value::String { val, .. } => Value::string(val.to_string(), span),
Value::Filesize { val: _, .. } => Value::String {
val: input.into_string(", ", config),
span,
},
Value::Duration { val: _, .. } => Value::String {
val: input.into_string("", config),
span,
},
Value::Filesize { val: _, .. } => Value::string(input.into_string(", ", config), span),
Value::Duration { val: _, .. } => Value::string(input.into_string("", config), span),
Value::Error { error, .. } => Value::String {
val: into_code(error).unwrap_or_default(),
span,
},
Value::Nothing { .. } => Value::String {
val: "".to_string(),
span,
},
Value::Record { .. } => Value::Error {
Value::Error { error, .. } => Value::string(into_code(error).unwrap_or_default(), span),
Value::Nothing { .. } => Value::string("".to_string(), span),
Value::Record { .. } => Value::error(
// Watch out for CantConvert's argument order
error: Box::new(ShellError::CantConvert {
ShellError::CantConvert {
to_type: "string".into(),
from_type: "record".into(),
span,
help: Some("try using the `to nuon` command".into()),
}),
},
span,
},
Value::Binary { .. } => Value::Error {
error: Box::new(ShellError::CantConvert {
),
Value::Binary { .. } => Value::error(
ShellError::CantConvert {
to_type: "string".into(),
from_type: "binary".into(),
span,
help: Some("try using the `decode` command".into()),
}),
},
span,
},
x => Value::Error {
error: Box::new(ShellError::CantConvert {
),
x => Value::error(
ShellError::CantConvert {
to_type: String::from("string"),
from_type: x.get_type().to_string(),
span,
help: None,
}),
},
span,
},
),
}
}