mirror of
https://github.com/nushell/nushell.git
synced 2025-05-05 23:42:56 +00:00
feat: duration from record (#15600)
Closes #15543 # Description 1. Simplify code in ``datetime.rs`` based on a suggestion in my last PR on "datetime from record" 1. Make ``into duration`` work with durations inside a record, provided as a cell path 1. Make ``into duration`` work with durations as record # User-Facing Changes ```nushell # Happy paths ~> {d: '1hr'} | into duration d ╭───┬─────╮ │ d │ 1hr │ ╰───┴─────╯ ~> {week: 10, day: 2, sign: '+'} | into duration 10wk 2day # Error paths and invalid usage ~> {week: 10, day: 2, sign: 'x'} | into duration Error: nu:🐚:incorrect_value × Incorrect value. ╭─[entry #4:1:26] 1 │ {week: 10, day: 2, sign: 'x'} | into duration · ─┬─ ──────┬────── · │ ╰── encountered here · ╰── Invalid sign. Allowed signs are +, - ╰──── ~> {week: 10, day: -2, sign: '+'} | into duration Error: nu:🐚:incorrect_value × Incorrect value. ╭─[entry #5:1:17] 1 │ {week: 10, day: -2, sign: '+'} | into duration · ─┬ ──────┬────── · │ ╰── encountered here · ╰── number should be positive ╰──── ~> {week: 10, day: '2', sign: '+'} | into duration Error: nu:🐚:only_supports_this_input_type × Input type not supported. ╭─[entry #6:1:17] 1 │ {week: 10, day: '2', sign: '+'} | into duration · ─┬─ ──────┬────── · │ ╰── only int input data is supported · ╰── input type: string ╰──── ~> {week: 10, unknown: 1} | into duration Error: nu:🐚:unsupported_input × Unsupported input ╭─[entry #7:1:1] 1 │ {week: 10, unknown: 1} | into duration · ───────────┬────────── ──────┬────── · │ ╰── Column 'unknown' is not valid for a structured duration. Allowed columns are: week, day, hour, minute, second, millisecond, microsecond, nanosecond, sign · ╰── value originates from here ╰──── ~> {week: 10, day: 2, sign: '+'} | into duration --unit sec Error: nu:🐚:incompatible_parameters × Incompatible parameters. ╭─[entry #2:1:33] 1 │ {week: 10, day: 2, sign: '+'} | into duration --unit sec · ──────┬────── ─────┬──── · │ ╰── the units should be included in the record · ╰── got a record as input ╰──── ``` # Tests + Formatting - Add examples and integration tests for ``into duration`` - Add one test for ``into duration`` # After Submitting If this is merged in time, I'll update my PR on the "datetime handling highlights" for the release notes.
This commit is contained in:
parent
1503ee09ba
commit
5c59611083
@ -317,7 +317,8 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return merge_record(record, head, input.span());
|
let span = input.span();
|
||||||
|
return merge_record(record, head, span).unwrap_or_else(|err| Value::error(err, span));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let's try dtparse first
|
// Let's try dtparse first
|
||||||
@ -458,15 +459,10 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(reason) => {
|
Err(reason) => {
|
||||||
match parse_with_format(val, &dt_format.item.0, head) {
|
parse_with_format(val, &dt_format.item.0, head).unwrap_or_else(|_| Value::error (
|
||||||
Ok(parsed) => parsed,
|
|
||||||
Err(_) => {
|
|
||||||
Value::error (
|
|
||||||
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt_format.item.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()) },
|
ShellError::CantConvert { to_type: format!("could not parse as datetime using format '{}'", dt_format.item.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,
|
head,
|
||||||
)
|
))
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -501,21 +497,20 @@ fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
|
||||||
if let Some(invalid_col) = record
|
if let Some(invalid_col) = record
|
||||||
.columns()
|
.columns()
|
||||||
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
|
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
|
||||||
{
|
{
|
||||||
let allowed_cols = ALLOWED_COLUMNS.join(", ");
|
let allowed_cols = ALLOWED_COLUMNS.join(", ");
|
||||||
return Value::error(ShellError::UnsupportedInput {
|
return Err(ShellError::UnsupportedInput {
|
||||||
msg: format!(
|
msg: format!(
|
||||||
"Column '{invalid_col}' is not valid for a structured datetime. Allowed columns are: {allowed_cols}"
|
"Column '{invalid_col}' is not valid for a structured datetime. Allowed columns are: {allowed_cols}"
|
||||||
),
|
),
|
||||||
input: "value originates from here".into(),
|
input: "value originates from here".into(),
|
||||||
msg_span: head,
|
msg_span: head,
|
||||||
input_span: span
|
input_span: span
|
||||||
},
|
}
|
||||||
span,
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -541,15 +536,12 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
match val {
|
match val {
|
||||||
Value::Int { val, .. } => *val as i32,
|
Value::Int { val, .. } => *val as i32,
|
||||||
other => {
|
other => {
|
||||||
return Value::error(
|
return Err(ShellError::OnlySupportsThisInputType {
|
||||||
ShellError::OnlySupportsThisInputType {
|
exp_input_type: "int".to_string(),
|
||||||
exp_input_type: "int".to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
wrong_type: other.get_type().to_string(),
|
dst_span: head,
|
||||||
dst_span: head,
|
src_span: other.span(),
|
||||||
src_span: other.span(),
|
});
|
||||||
},
|
|
||||||
span,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -558,12 +550,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let month = match record.get("month") {
|
let month = match record.get("month") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("month", col_val, &head, &span) {
|
parse_value_from_record_as_u32("month", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now.month(),
|
RecordColumnDefault::Now => now.month(),
|
||||||
@ -573,12 +560,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let day = match record.get("day") {
|
let day = match record.get("day") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("day", col_val, &head, &span) {
|
parse_value_from_record_as_u32("day", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now.day(),
|
RecordColumnDefault::Now => now.day(),
|
||||||
@ -588,12 +570,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let hour = match record.get("hour") {
|
let hour = match record.get("hour") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("hour", col_val, &head, &span) {
|
parse_value_from_record_as_u32("hour", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now.hour(),
|
RecordColumnDefault::Now => now.hour(),
|
||||||
@ -603,12 +580,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let minute = match record.get("minute") {
|
let minute = match record.get("minute") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("minute", col_val, &head, &span) {
|
parse_value_from_record_as_u32("minute", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now.minute(),
|
RecordColumnDefault::Now => now.minute(),
|
||||||
@ -618,12 +590,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let second = match record.get("second") {
|
let second = match record.get("second") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("second", col_val, &head, &span) {
|
parse_value_from_record_as_u32("second", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now.second(),
|
RecordColumnDefault::Now => now.second(),
|
||||||
@ -633,12 +600,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let millisecond = match record.get("millisecond") {
|
let millisecond = match record.get("millisecond") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("millisecond", col_val, &head, &span) {
|
parse_value_from_record_as_u32("millisecond", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now_millisecond,
|
RecordColumnDefault::Now => now_millisecond,
|
||||||
@ -648,12 +610,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let microsecond = match record.get("microsecond") {
|
let microsecond = match record.get("microsecond") {
|
||||||
Some(col_val) => {
|
Some(col_val) => {
|
||||||
record_column_default = RecordColumnDefault::Zero;
|
record_column_default = RecordColumnDefault::Zero;
|
||||||
match parse_value_from_record_as_u32("microsecond", col_val, &head, &span) {
|
parse_value_from_record_as_u32("microsecond", col_val, &head, &span)?
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now_microsecond,
|
RecordColumnDefault::Now => now_microsecond,
|
||||||
@ -662,14 +619,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let nanosecond = match record.get("nanosecond") {
|
let nanosecond = match record.get("nanosecond") {
|
||||||
Some(col_val) => {
|
Some(col_val) => parse_value_from_record_as_u32("nanosecond", col_val, &head, &span)?,
|
||||||
match parse_value_from_record_as_u32("nanosecond", col_val, &head, &span) {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => match record_column_default {
|
None => match record_column_default {
|
||||||
RecordColumnDefault::Now => now_nanosecond,
|
RecordColumnDefault::Now => now_nanosecond,
|
||||||
RecordColumnDefault::Zero => 0,
|
RecordColumnDefault::Zero => 0,
|
||||||
@ -677,12 +627,7 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let offset: FixedOffset = match record.get("timezone") {
|
let offset: FixedOffset = match record.get("timezone") {
|
||||||
Some(timezone) => match parse_timezone_from_record(timezone, &head, &timezone.span()) {
|
Some(timezone) => parse_timezone_from_record(timezone, &head, &timezone.span())?,
|
||||||
Ok(value) => value,
|
|
||||||
Err(err) => {
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => now.offset().to_owned(),
|
None => now.offset().to_owned(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -691,29 +636,21 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let date = match NaiveDate::from_ymd_opt(year, month, day) {
|
let date = match NaiveDate::from_ymd_opt(year, month, day) {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => {
|
None => {
|
||||||
return Value::error(
|
return Err(ShellError::IncorrectValue {
|
||||||
ShellError::IncorrectValue {
|
msg: "one of more values are incorrect and do not represent valid date".to_string(),
|
||||||
msg: "one of more values are incorrect and do not represent valid date"
|
val_span: head,
|
||||||
.to_string(),
|
call_span: span,
|
||||||
val_span: head,
|
})
|
||||||
call_span: span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let time = match NaiveTime::from_hms_nano_opt(hour, minute, second, total_nanoseconds) {
|
let time = match NaiveTime::from_hms_nano_opt(hour, minute, second, total_nanoseconds) {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
return Value::error(
|
return Err(ShellError::IncorrectValue {
|
||||||
ShellError::IncorrectValue {
|
msg: "one of more values are incorrect and do not represent valid time".to_string(),
|
||||||
msg: "one of more values are incorrect and do not represent valid time"
|
val_span: head,
|
||||||
.to_string(),
|
call_span: span,
|
||||||
val_span: head,
|
})
|
||||||
call_span: span,
|
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let date_time = NaiveDateTime::new(date, time);
|
let date_time = NaiveDateTime::new(date, time);
|
||||||
@ -721,17 +658,14 @@ fn merge_record(record: &Record, head: Span, span: Span) -> Value {
|
|||||||
let date_time_fixed = match offset.from_local_datetime(&date_time).single() {
|
let date_time_fixed = match offset.from_local_datetime(&date_time).single() {
|
||||||
Some(d) => d,
|
Some(d) => d,
|
||||||
None => {
|
None => {
|
||||||
return Value::error(
|
return Err(ShellError::IncorrectValue {
|
||||||
ShellError::IncorrectValue {
|
msg: "Ambiguous or invalid timezone conversion".to_string(),
|
||||||
msg: "Ambiguous or invalid timezone conversion".to_string(),
|
val_span: head,
|
||||||
val_span: head,
|
call_span: span,
|
||||||
call_span: span,
|
})
|
||||||
},
|
|
||||||
span,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Value::date(date_time_fixed, span)
|
Ok(Value::date(date_time_fixed, span))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_value_from_record_as_u32(
|
fn parse_value_from_record_as_u32(
|
||||||
@ -739,31 +673,25 @@ fn parse_value_from_record_as_u32(
|
|||||||
col_val: &Value,
|
col_val: &Value,
|
||||||
head: &Span,
|
head: &Span,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<u32, Value> {
|
) -> Result<u32, ShellError> {
|
||||||
let value: u32 = match col_val {
|
let value: u32 = match col_val {
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
if *val < 0 || *val > u32::MAX as i64 {
|
if *val < 0 || *val > u32::MAX as i64 {
|
||||||
return Err(Value::error(
|
return Err(ShellError::IncorrectValue {
|
||||||
ShellError::IncorrectValue {
|
msg: format!("incorrect value for {}", col),
|
||||||
msg: format!("incorrect value for {}", col),
|
val_span: *head,
|
||||||
val_span: *head,
|
call_span: *span,
|
||||||
call_span: *span,
|
});
|
||||||
},
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
*val as u32
|
*val as u32
|
||||||
}
|
}
|
||||||
other => {
|
other => {
|
||||||
return Err(Value::error(
|
return Err(ShellError::OnlySupportsThisInputType {
|
||||||
ShellError::OnlySupportsThisInputType {
|
exp_input_type: "int".to_string(),
|
||||||
exp_input_type: "int".to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
wrong_type: other.get_type().to_string(),
|
dst_span: *head,
|
||||||
dst_span: *head,
|
src_span: other.span(),
|
||||||
src_span: other.span(),
|
});
|
||||||
},
|
|
||||||
*span,
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(value)
|
Ok(value)
|
||||||
@ -773,33 +701,27 @@ fn parse_timezone_from_record(
|
|||||||
timezone: &Value,
|
timezone: &Value,
|
||||||
head: &Span,
|
head: &Span,
|
||||||
span: &Span,
|
span: &Span,
|
||||||
) -> Result<FixedOffset, Value> {
|
) -> Result<FixedOffset, ShellError> {
|
||||||
match timezone {
|
match timezone {
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
let offset: FixedOffset = match val.parse() {
|
let offset: FixedOffset = match val.parse() {
|
||||||
Ok(offset) => offset,
|
Ok(offset) => offset,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
return Err(Value::error(
|
return Err(ShellError::IncorrectValue {
|
||||||
ShellError::IncorrectValue {
|
msg: "invalid timezone".to_string(),
|
||||||
msg: "invalid timezone".to_string(),
|
val_span: *span,
|
||||||
val_span: *span,
|
call_span: *head,
|
||||||
call_span: *head,
|
})
|
||||||
},
|
|
||||||
*span,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(offset)
|
Ok(offset)
|
||||||
}
|
}
|
||||||
other => Err(Value::error(
|
other => Err(ShellError::OnlySupportsThisInputType {
|
||||||
ShellError::OnlySupportsThisInputType {
|
exp_input_type: "string".to_string(),
|
||||||
exp_input_type: "string".to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
wrong_type: other.get_type().to_string(),
|
dst_span: *head,
|
||||||
dst_span: *head,
|
src_span: other.span(),
|
||||||
src_span: other.span(),
|
}),
|
||||||
},
|
|
||||||
*span,
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,41 @@
|
|||||||
|
use nu_cmd_base::input_handler::{operate, CmdArgument};
|
||||||
use nu_engine::command_prelude::*;
|
use nu_engine::command_prelude::*;
|
||||||
use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
|
use nu_parser::{parse_unit_value, DURATION_UNIT_GROUPS};
|
||||||
use nu_protocol::{ast::Expr, Unit};
|
use nu_protocol::{ast::Expr, Unit};
|
||||||
|
|
||||||
|
const NS_PER_US: i64 = 1_000;
|
||||||
|
const NS_PER_MS: i64 = 1_000_000;
|
||||||
const NS_PER_SEC: i64 = 1_000_000_000;
|
const NS_PER_SEC: i64 = 1_000_000_000;
|
||||||
|
const NS_PER_MINUTE: i64 = 60 * NS_PER_SEC;
|
||||||
|
const NS_PER_HOUR: i64 = 60 * NS_PER_MINUTE;
|
||||||
|
const NS_PER_DAY: i64 = 24 * NS_PER_HOUR;
|
||||||
|
const NS_PER_WEEK: i64 = 7 * NS_PER_DAY;
|
||||||
|
|
||||||
|
const ALLOWED_COLUMNS: [&str; 9] = [
|
||||||
|
"week",
|
||||||
|
"day",
|
||||||
|
"hour",
|
||||||
|
"minute",
|
||||||
|
"second",
|
||||||
|
"millisecond",
|
||||||
|
"microsecond",
|
||||||
|
"nanosecond",
|
||||||
|
"sign",
|
||||||
|
];
|
||||||
|
const ALLOWED_SIGNS: [&str; 2] = ["+", "-"];
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct Arguments {
|
||||||
|
unit: Option<Spanned<String>>,
|
||||||
|
cell_paths: Option<Vec<CellPath>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CmdArgument for Arguments {
|
||||||
|
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
|
||||||
|
self.cell_paths.take()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct IntoDuration;
|
pub struct IntoDuration;
|
||||||
|
|
||||||
@ -18,11 +51,14 @@ impl Command for IntoDuration {
|
|||||||
(Type::Float, Type::Duration),
|
(Type::Float, Type::Duration),
|
||||||
(Type::String, Type::Duration),
|
(Type::String, Type::Duration),
|
||||||
(Type::Duration, Type::Duration),
|
(Type::Duration, Type::Duration),
|
||||||
|
// FIXME: https://github.com/nushell/nushell/issues/15485
|
||||||
|
// 'record -> any' was added as a temporary workaround to avoid type inference issues. The Any arm needs to be appear first.
|
||||||
|
(Type::record(), Type::Any),
|
||||||
|
(Type::record(), Type::record()),
|
||||||
|
(Type::record(), Type::Duration),
|
||||||
(Type::table(), Type::table()),
|
(Type::table(), Type::table()),
|
||||||
//todo: record<hour,minute,sign> | into duration -> Duration
|
|
||||||
//(Type::record(), Type::record()),
|
|
||||||
])
|
])
|
||||||
//.allow_variants_without_examples(true)
|
.allow_variants_without_examples(true)
|
||||||
.named(
|
.named(
|
||||||
"unit",
|
"unit",
|
||||||
SyntaxShape::String,
|
SyntaxShape::String,
|
||||||
@ -56,7 +92,35 @@ impl Command for IntoDuration {
|
|||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
) -> Result<PipelineData, ShellError> {
|
) -> Result<PipelineData, ShellError> {
|
||||||
into_duration(engine_state, stack, call, input)
|
let cell_paths = call.rest(engine_state, stack, 0)?;
|
||||||
|
let cell_paths = (!cell_paths.is_empty()).then_some(cell_paths);
|
||||||
|
|
||||||
|
let span = match input.span() {
|
||||||
|
Some(t) => t,
|
||||||
|
None => call.head,
|
||||||
|
};
|
||||||
|
let unit = match call.get_flag::<Spanned<String>>(engine_state, stack, "unit")? {
|
||||||
|
Some(spanned_unit) => {
|
||||||
|
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
|
||||||
|
.contains(&spanned_unit.item.as_str())
|
||||||
|
{
|
||||||
|
Some(spanned_unit)
|
||||||
|
} else {
|
||||||
|
return Err(ShellError::CantConvertToDuration {
|
||||||
|
details: spanned_unit.item,
|
||||||
|
dst_span: span,
|
||||||
|
src_span: span,
|
||||||
|
help: Some(
|
||||||
|
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk"
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
let args = Arguments { unit, cell_paths };
|
||||||
|
operate(action, args, input, call.head, engine_state.signals())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn examples(&self) -> Vec<Example> {
|
fn examples(&self) -> Vec<Example> {
|
||||||
@ -115,67 +179,18 @@ impl Command for IntoDuration {
|
|||||||
example: "1.234 | into duration --unit sec",
|
example: "1.234 | into duration --unit sec",
|
||||||
result: Some(Value::test_duration(1_234 * 1_000_000)),
|
result: Some(Value::test_duration(1_234 * 1_000_000)),
|
||||||
},
|
},
|
||||||
|
Example {
|
||||||
|
description: "Convert a record to a duration",
|
||||||
|
example: "{day: 10, hour: 2, minute: 6, second: 50, sign: '+'} | into duration",
|
||||||
|
result: Some(Value::duration(
|
||||||
|
10 * NS_PER_DAY + 2 * NS_PER_HOUR + 6 * NS_PER_MINUTE + 50 * NS_PER_SEC,
|
||||||
|
Span::test_data(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn into_duration(
|
|
||||||
engine_state: &EngineState,
|
|
||||||
stack: &mut Stack,
|
|
||||||
call: &Call,
|
|
||||||
input: PipelineData,
|
|
||||||
) -> Result<PipelineData, ShellError> {
|
|
||||||
let span = match input.span() {
|
|
||||||
Some(t) => t,
|
|
||||||
None => call.head,
|
|
||||||
};
|
|
||||||
let column_paths: Vec<CellPath> = call.rest(engine_state, stack, 0)?;
|
|
||||||
|
|
||||||
let unit = match call.get_flag::<String>(engine_state, stack, "unit")? {
|
|
||||||
Some(sep) => {
|
|
||||||
if ["ns", "us", "µs", "ms", "sec", "min", "hr", "day", "wk"]
|
|
||||||
.iter()
|
|
||||||
.any(|d| d == &sep)
|
|
||||||
{
|
|
||||||
sep
|
|
||||||
} else {
|
|
||||||
return Err(ShellError::CantConvertToDuration {
|
|
||||||
details: sep,
|
|
||||||
dst_span: span,
|
|
||||||
src_span: span,
|
|
||||||
help: Some(
|
|
||||||
"supported units are ns, us/µs, ms, sec, min, hr, day, and wk".to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => "ns".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
input.map(
|
|
||||||
move |v| {
|
|
||||||
if column_paths.is_empty() {
|
|
||||||
action(&v, &unit.clone(), span)
|
|
||||||
} else {
|
|
||||||
let unitclone = &unit.clone();
|
|
||||||
let mut ret = v;
|
|
||||||
for path in &column_paths {
|
|
||||||
let r = ret.update_cell_path(
|
|
||||||
&path.members,
|
|
||||||
Box::new(move |old| action(old, unitclone, span)),
|
|
||||||
);
|
|
||||||
if let Err(error) = r {
|
|
||||||
return Value::error(error, span);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret
|
|
||||||
}
|
|
||||||
},
|
|
||||||
engine_state.signals(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
|
fn split_whitespace_indices(s: &str, span: Span) -> impl Iterator<Item = (&str, Span)> {
|
||||||
s.split_whitespace().map(move |sub| {
|
s.split_whitespace().map(move |sub| {
|
||||||
// Gets the offset of the `sub` substring inside the string `s`.
|
// Gets the offset of the `sub` substring inside the string `s`.
|
||||||
@ -238,27 +253,51 @@ fn string_to_duration(s: &str, span: Span) -> Result<i64, ShellError> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn action(input: &Value, unit: &str, span: Span) -> Value {
|
fn action(input: &Value, args: &Arguments, head: Span) -> Value {
|
||||||
let value_span = input.span();
|
let value_span = input.span();
|
||||||
|
let unit_option = &args.unit;
|
||||||
|
|
||||||
|
if let Value::Record { .. } | Value::Duration { .. } = input {
|
||||||
|
if let Some(unit) = unit_option {
|
||||||
|
return Value::error(
|
||||||
|
ShellError::IncompatibleParameters {
|
||||||
|
left_message: "got a record as input".into(),
|
||||||
|
left_span: head,
|
||||||
|
right_message: "the units should be included in the record".into(),
|
||||||
|
right_span: unit.span,
|
||||||
|
},
|
||||||
|
head,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let unit: &str = match unit_option {
|
||||||
|
Some(unit) => &unit.item,
|
||||||
|
None => "ns",
|
||||||
|
};
|
||||||
|
|
||||||
match input {
|
match input {
|
||||||
Value::Duration { .. } => input.clone(),
|
Value::Duration { .. } => input.clone(),
|
||||||
|
Value::Record { val, .. } => {
|
||||||
|
merge_record(val, head, value_span).unwrap_or_else(|err| Value::error(err, value_span))
|
||||||
|
}
|
||||||
Value::String { val, .. } => {
|
Value::String { val, .. } => {
|
||||||
if let Ok(num) = val.parse::<f64>() {
|
if let Ok(num) = val.parse::<f64>() {
|
||||||
let ns = unit_to_ns_factor(unit);
|
let ns = unit_to_ns_factor(unit);
|
||||||
return Value::duration((num * (ns as f64)) as i64, span);
|
return Value::duration((num * (ns as f64)) as i64, head);
|
||||||
}
|
}
|
||||||
match compound_to_duration(val, value_span) {
|
match compound_to_duration(val, value_span) {
|
||||||
Ok(val) => Value::duration(val, span),
|
Ok(val) => Value::duration(val, head),
|
||||||
Err(error) => Value::error(error, span),
|
Err(error) => Value::error(error, head),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Value::Float { val, .. } => {
|
Value::Float { val, .. } => {
|
||||||
let ns = unit_to_ns_factor(unit);
|
let ns = unit_to_ns_factor(unit);
|
||||||
Value::duration((*val * (ns as f64)) as i64, span)
|
Value::duration((*val * (ns as f64)) as i64, head)
|
||||||
}
|
}
|
||||||
Value::Int { val, .. } => {
|
Value::Int { val, .. } => {
|
||||||
let ns = unit_to_ns_factor(unit);
|
let ns = unit_to_ns_factor(unit);
|
||||||
Value::duration(*val * ns, span)
|
Value::duration(*val * ns, head)
|
||||||
}
|
}
|
||||||
// Propagate errors by explicitly matching them before the final case.
|
// Propagate errors by explicitly matching them before the final case.
|
||||||
Value::Error { .. } => input.clone(),
|
Value::Error { .. } => input.clone(),
|
||||||
@ -266,24 +305,130 @@ fn action(input: &Value, unit: &str, span: Span) -> Value {
|
|||||||
ShellError::OnlySupportsThisInputType {
|
ShellError::OnlySupportsThisInputType {
|
||||||
exp_input_type: "string or duration".into(),
|
exp_input_type: "string or duration".into(),
|
||||||
wrong_type: other.get_type().to_string(),
|
wrong_type: other.get_type().to_string(),
|
||||||
dst_span: span,
|
dst_span: head,
|
||||||
src_span: other.span(),
|
src_span: other.span(),
|
||||||
},
|
},
|
||||||
span,
|
head,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn merge_record(record: &Record, head: Span, span: Span) -> Result<Value, ShellError> {
|
||||||
|
if let Some(invalid_col) = record
|
||||||
|
.columns()
|
||||||
|
.find(|key| !ALLOWED_COLUMNS.contains(&key.as_str()))
|
||||||
|
{
|
||||||
|
let allowed_cols = ALLOWED_COLUMNS.join(", ");
|
||||||
|
return Err(ShellError::UnsupportedInput {
|
||||||
|
msg: format!(
|
||||||
|
"Column '{invalid_col}' is not valid for a structured duration. Allowed columns are: {allowed_cols}"
|
||||||
|
),
|
||||||
|
input: "value originates from here".into(),
|
||||||
|
msg_span: head,
|
||||||
|
input_span: span
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut duration: i64 = 0;
|
||||||
|
|
||||||
|
if let Some(col_val) = record.get("week") {
|
||||||
|
let week = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += week * NS_PER_WEEK;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("day") {
|
||||||
|
let day = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += day * NS_PER_DAY;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("hour") {
|
||||||
|
let hour = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += hour * NS_PER_HOUR;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("minute") {
|
||||||
|
let minute = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += minute * NS_PER_MINUTE;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("second") {
|
||||||
|
let second = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += second * NS_PER_SEC;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("millisecond") {
|
||||||
|
let millisecond = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += millisecond * NS_PER_MS;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("microsecond") {
|
||||||
|
let microsecond = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += microsecond * NS_PER_US;
|
||||||
|
};
|
||||||
|
if let Some(col_val) = record.get("nanosecond") {
|
||||||
|
let nanosecond = parse_number_from_record(col_val, &head)?;
|
||||||
|
duration += nanosecond;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(sign) = record.get("sign") {
|
||||||
|
match sign {
|
||||||
|
Value::String { val, .. } => {
|
||||||
|
if !ALLOWED_SIGNS.contains(&val.as_str()) {
|
||||||
|
let allowed_signs = ALLOWED_SIGNS.join(", ");
|
||||||
|
return Err(ShellError::IncorrectValue {
|
||||||
|
msg: format!("Invalid sign. Allowed signs are {}", allowed_signs)
|
||||||
|
.to_string(),
|
||||||
|
val_span: sign.span(),
|
||||||
|
call_span: head,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if val == "-" {
|
||||||
|
duration = -duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::OnlySupportsThisInputType {
|
||||||
|
exp_input_type: "int".to_string(),
|
||||||
|
wrong_type: other.get_type().to_string(),
|
||||||
|
dst_span: head,
|
||||||
|
src_span: other.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Value::duration(duration, span))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_number_from_record(col_val: &Value, head: &Span) -> Result<i64, ShellError> {
|
||||||
|
let value = match col_val {
|
||||||
|
Value::Int { val, .. } => {
|
||||||
|
if *val < 0 {
|
||||||
|
return Err(ShellError::IncorrectValue {
|
||||||
|
msg: "number should be positive".to_string(),
|
||||||
|
val_span: col_val.span(),
|
||||||
|
call_span: *head,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*val
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Err(ShellError::OnlySupportsThisInputType {
|
||||||
|
exp_input_type: "int".to_string(),
|
||||||
|
wrong_type: other.get_type().to_string(),
|
||||||
|
dst_span: *head,
|
||||||
|
src_span: other.span(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
|
||||||
fn unit_to_ns_factor(unit: &str) -> i64 {
|
fn unit_to_ns_factor(unit: &str) -> i64 {
|
||||||
match unit {
|
match unit {
|
||||||
"ns" => 1,
|
"ns" => 1,
|
||||||
"us" | "µs" => 1_000,
|
"us" | "µs" => NS_PER_US,
|
||||||
"ms" => 1_000_000,
|
"ms" => NS_PER_MS,
|
||||||
"sec" => NS_PER_SEC,
|
"sec" => NS_PER_SEC,
|
||||||
"min" => NS_PER_SEC * 60,
|
"min" => NS_PER_MINUTE,
|
||||||
"hr" => NS_PER_SEC * 60 * 60,
|
"hr" => NS_PER_HOUR,
|
||||||
"day" => NS_PER_SEC * 60 * 60 * 24,
|
"day" => NS_PER_DAY,
|
||||||
"wk" => NS_PER_SEC * 60 * 60 * 24 * 7,
|
"wk" => NS_PER_WEEK,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,24 +449,27 @@ mod test {
|
|||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("3ns", 3)]
|
#[case("3ns", 3)]
|
||||||
#[case("4us", 4*1000)]
|
#[case("4us", 4 * NS_PER_US)]
|
||||||
#[case("4\u{00B5}s", 4*1000)] // micro sign
|
#[case("4\u{00B5}s", 4 * NS_PER_US)] // micro sign
|
||||||
#[case("4\u{03BC}s", 4*1000)] // mu symbol
|
#[case("4\u{03BC}s", 4 * NS_PER_US)] // mu symbol
|
||||||
#[case("5ms", 5 * 1000 * 1000)]
|
#[case("5ms", 5 * NS_PER_MS)]
|
||||||
#[case("1sec", NS_PER_SEC)]
|
#[case("1sec", NS_PER_SEC)]
|
||||||
#[case("7min", 7 * 60 * NS_PER_SEC)]
|
#[case("7min", 7 * NS_PER_MINUTE)]
|
||||||
#[case("42hr", 42 * 60 * 60 * NS_PER_SEC)]
|
#[case("42hr", 42 * NS_PER_HOUR)]
|
||||||
#[case("123day", 123 * 24 * 60 * 60 * NS_PER_SEC)]
|
#[case("123day", 123 * NS_PER_DAY)]
|
||||||
#[case("3wk", 3 * 7 * 24 * 60 * 60 * NS_PER_SEC)]
|
#[case("3wk", 3 * NS_PER_WEEK)]
|
||||||
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
|
#[case("86hr 26ns", 86 * 3600 * NS_PER_SEC + 26)] // compound duration string
|
||||||
#[case("14ns 3hr 17sec", 14 + 3 * 3600 * NS_PER_SEC + 17 * NS_PER_SEC)] // compound string with units in random order
|
#[case("14ns 3hr 17sec", 14 + 3 * NS_PER_HOUR + 17 * NS_PER_SEC)] // compound string with units in random order
|
||||||
|
|
||||||
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
|
fn turns_string_to_duration(#[case] phrase: &str, #[case] expected_duration_val: i64) {
|
||||||
let actual = action(
|
let args = Arguments {
|
||||||
&Value::test_string(phrase),
|
unit: Some(Spanned {
|
||||||
"ns",
|
item: "ns".to_string(),
|
||||||
Span::new(0, phrase.len()),
|
span: Span::test_data(),
|
||||||
);
|
}),
|
||||||
|
cell_paths: None,
|
||||||
|
};
|
||||||
|
let actual = action(&Value::test_string(phrase), &args, Span::test_data());
|
||||||
match actual {
|
match actual {
|
||||||
Value::Duration {
|
Value::Duration {
|
||||||
val: observed_val, ..
|
val: observed_val, ..
|
||||||
|
@ -2,6 +2,13 @@ use nu_test_support::nu;
|
|||||||
|
|
||||||
// Tests happy paths
|
// Tests happy paths
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_datetime_from_record_cell_path() {
|
||||||
|
let actual = nu!(r#"{d: '2021'} | into datetime d"#);
|
||||||
|
|
||||||
|
assert!(actual.out.contains("years ago"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn into_datetime_from_record() {
|
fn into_datetime_from_record() {
|
||||||
let actual = nu!(
|
let actual = nu!(
|
||||||
|
@ -8,3 +8,103 @@ fn into_duration_float() {
|
|||||||
|
|
||||||
assert_eq!("1min 4sec 200ms", actual.out);
|
assert_eq!("1min 4sec 200ms", actual.out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_cell_path() {
|
||||||
|
let actual = nu!(r#"{d: '1hr'} | into duration d"#);
|
||||||
|
let expected = nu!(r#"{d: 1hr}"#);
|
||||||
|
|
||||||
|
assert_eq!(expected.out, actual.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"{week: 10, day: 1, hour: 2, minute: 3, second: 4, millisecond: 5, microsecond: 6, nanosecond: 7, sign: '+'} | into duration | into record"#
|
||||||
|
);
|
||||||
|
let expected = nu!(
|
||||||
|
r#"{week: 10, day: 1, hour: 2, minute: 3, second: 4, millisecond: 5, microsecond: 6, nanosecond: 7, sign: '+'}"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(expected.out, actual.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_negative() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"{week: 10, day: 1, hour: 2, minute: 3, second: 4, millisecond: 5, microsecond: 6, nanosecond: 7, sign: '-'} | into duration | into record"#
|
||||||
|
);
|
||||||
|
let expected = nu!(
|
||||||
|
r#"{week: 10, day: 1, hour: 2, minute: 3, second: 4, millisecond: 5, microsecond: 6, nanosecond: 7, sign: '-'}"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(expected.out, actual.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_defaults() {
|
||||||
|
let actual = nu!(r#"{} | into duration | into int"#);
|
||||||
|
|
||||||
|
assert_eq!("0".to_string(), actual.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_round_trip() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"('10wk 1day 2hr 3min 4sec 5ms 6µs 7ns' | into duration | into record | into duration | into string) == '10wk 1day 2hr 3min 4sec 5ms 6µs 7ns'"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(actual.out.contains("true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_table_column() {
|
||||||
|
let actual =
|
||||||
|
nu!(r#"[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value"#);
|
||||||
|
let expected = nu!(r#"[[value]; [1sec] [2min] [3hr] [4day] [5wk]]"#);
|
||||||
|
|
||||||
|
assert_eq!(actual.out, expected.out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests error paths
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_fails_with_wrong_type() {
|
||||||
|
let actual = nu!(r#"{week: '10'} | into duration"#);
|
||||||
|
|
||||||
|
assert!(actual
|
||||||
|
.err
|
||||||
|
.contains("nu::shell::only_supports_this_input_type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_fails_with_invalid_date_time_values() {
|
||||||
|
let actual = nu!(r#"{week: -10} | into duration"#);
|
||||||
|
|
||||||
|
assert!(actual.err.contains("nu::shell::incorrect_value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_fails_with_invalid_sign() {
|
||||||
|
let actual = nu!(r#"{week: 10, sign: 'x'} | into duration"#);
|
||||||
|
|
||||||
|
assert!(actual.err.contains("nu::shell::incorrect_value"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests invalid usage
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_fails_with_unknown_key() {
|
||||||
|
let actual = nu!(r#"{week: 10, unknown: 1} | into duration"#);
|
||||||
|
|
||||||
|
assert!(actual.err.contains("nu::shell::unsupported_input"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn into_duration_from_record_incompatible_with_unit_flag() {
|
||||||
|
let actual = nu!(
|
||||||
|
r#"{week: 10, day: 1, hour: 2, minute: 3, second: 4, sign: '-'} | into duration --unit sec"#
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(actual.err.contains("nu::shell::incompatible_parameters"));
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user