mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 23:07:46 +02:00
Create Record
type (#10103)
# Description This PR creates a new `Record` type to reduce duplicate code and possibly bugs as well. (This is an edited version of #9648.) - `Record` implements `FromIterator` and `IntoIterator` and so can be iterated over or collected into. For example, this helps with conversions to and from (hash)maps. (Also, no more `cols.iter().zip(vals)`!) - `Record` has a `push(col, val)` function to help insure that the number of columns is equal to the number of values. I caught a few potential bugs thanks to this (e.g. in the `ls` command). - Finally, this PR also adds a `record!` macro that helps simplify record creation. It is used like so: ```rust record! { "key1" => some_value, "key2" => Value::string("text", span), "key3" => Value::int(optional_int.unwrap_or(0), span), "key4" => Value::bool(config.setting, span), } ``` Since macros hinder formatting, etc., the right hand side values should be relatively short and sweet like the examples above. Where possible, prefer `record!` or `.collect()` on an iterator instead of multiple `Record::push`s, since the first two automatically set the record capacity and do less work overall. # User-Facing Changes Besides the changes in `nu-protocol` the only other breaking changes are to `nu-table::{ExpandedTable::build_map, JustTable::kv_table}`.
This commit is contained in:
@ -2,8 +2,8 @@ use chrono_tz::TZ_VARIANTS;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoInterruptiblePipelineData, PipelineData, ShellError, Signature, Span,
|
||||
Type, Value,
|
||||
record, Category, Example, IntoInterruptiblePipelineData, PipelineData, Record, ShellError,
|
||||
Signature, Span, Type, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -40,12 +40,10 @@ impl Command for SubCommand {
|
||||
Ok(TZ_VARIANTS
|
||||
.iter()
|
||||
.map(move |x| {
|
||||
let cols = vec!["timezone".into()];
|
||||
let vals = vec![Value::String {
|
||||
val: x.name().to_string(),
|
||||
Value::record(
|
||||
record! { "timezone" => Value::string(x.name(), span) },
|
||||
span,
|
||||
}];
|
||||
Value::Record { cols, vals, span }
|
||||
)
|
||||
})
|
||||
.into_pipeline_data(engine_state.ctrlc.clone()))
|
||||
}
|
||||
@ -55,11 +53,10 @@ impl Command for SubCommand {
|
||||
example: "date list-timezone | where timezone =~ Shanghai",
|
||||
description: "Show timezone(s) that contains 'Shanghai'",
|
||||
result: Some(Value::List {
|
||||
vals: vec![Value::Record {
|
||||
vals: vec![Value::test_record(Record {
|
||||
cols: vec!["timezone".into()],
|
||||
vals: vec![Value::test_string("Asia/Shanghai")],
|
||||
span: Span::test_data(),
|
||||
}],
|
||||
})],
|
||||
span: Span::test_data(),
|
||||
}),
|
||||
}]
|
||||
|
@ -3,10 +3,9 @@ use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
|
||||
Signature, Span, Value,
|
||||
record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
|
||||
ShellError::PipelineEmpty, Signature, Span, Type, Value,
|
||||
};
|
||||
use nu_protocol::{ShellError, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -78,7 +77,7 @@ impl Command for SubCommand {
|
||||
span,
|
||||
},
|
||||
];
|
||||
Some(Value::Record { cols, vals, span })
|
||||
Some(Value::test_record(Record { cols, vals }))
|
||||
};
|
||||
|
||||
let example_result_2 = || {
|
||||
@ -106,7 +105,7 @@ impl Command for SubCommand {
|
||||
span,
|
||||
},
|
||||
];
|
||||
Some(Value::Record { cols, vals, span })
|
||||
Some(Value::test_record(Record { cols, vals }))
|
||||
};
|
||||
|
||||
vec![
|
||||
@ -134,37 +133,20 @@ impl Command for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value {
|
||||
let cols = vec![
|
||||
"year".into(),
|
||||
"month".into(),
|
||||
"day".into(),
|
||||
"hour".into(),
|
||||
"minute".into(),
|
||||
"second".into(),
|
||||
"nanosecond".into(),
|
||||
"timezone".into(),
|
||||
];
|
||||
match date {
|
||||
Ok(x) => {
|
||||
let vals = vec![
|
||||
Value::int(x.year() as i64, head),
|
||||
Value::int(x.month() as i64, head),
|
||||
Value::int(x.day() as i64, head),
|
||||
Value::int(x.hour() as i64, head),
|
||||
Value::int(x.minute() as i64, head),
|
||||
Value::int(x.second() as i64, head),
|
||||
Value::int(x.nanosecond() as i64, head),
|
||||
Value::string(x.offset().to_string(), head),
|
||||
];
|
||||
Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(e) => e,
|
||||
}
|
||||
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
|
||||
Value::record(
|
||||
record! {
|
||||
"year" => Value::int(date.year() as i64, head),
|
||||
"month" => Value::int(date.month() as i64, head),
|
||||
"day" => Value::int(date.day() as i64, head),
|
||||
"hour" => Value::int(date.hour() as i64, head),
|
||||
"minute" => Value::int(date.minute() as i64, head),
|
||||
"second" => Value::int(date.second() as i64, head),
|
||||
"nanosecond" => Value::int(date.nanosecond() as i64, head),
|
||||
"timezone" => Value::string(date.offset().to_string(), head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
}
|
||||
|
||||
fn helper(val: Value, head: Span) -> Value {
|
||||
@ -172,16 +154,16 @@ fn helper(val: Value, head: Span) -> Value {
|
||||
Value::String {
|
||||
val,
|
||||
span: val_span,
|
||||
} => {
|
||||
let date = parse_date_from_string(&val, val_span);
|
||||
parse_date_into_table(date, head)
|
||||
}
|
||||
} => match parse_date_from_string(&val, val_span) {
|
||||
Ok(date) => parse_date_into_table(date, head),
|
||||
Err(e) => e,
|
||||
},
|
||||
Value::Nothing { span: _ } => {
|
||||
let now = Local::now();
|
||||
let n = now.with_timezone(now.offset());
|
||||
parse_date_into_table(Ok(n), head)
|
||||
parse_date_into_table(n, head)
|
||||
}
|
||||
Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head),
|
||||
Value::Date { val, span: _ } => parse_date_into_table(val, head),
|
||||
_ => Value::Error {
|
||||
error: Box::new(DatetimeParseError(val.debug_value(), head)),
|
||||
},
|
||||
|
@ -3,10 +3,9 @@ use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike};
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, PipelineData, ShellError::DatetimeParseError, ShellError::PipelineEmpty,
|
||||
Signature, Span, Value,
|
||||
record, Category, Example, PipelineData, Record, ShellError, ShellError::DatetimeParseError,
|
||||
ShellError::PipelineEmpty, Signature, Span, Type, Value,
|
||||
};
|
||||
use nu_protocol::{ShellError, Type};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SubCommand;
|
||||
@ -76,7 +75,7 @@ impl Command for SubCommand {
|
||||
},
|
||||
];
|
||||
Some(Value::List {
|
||||
vals: vec![Value::Record { cols, vals, span }],
|
||||
vals: vec![Value::test_record(Record { cols, vals })],
|
||||
span,
|
||||
})
|
||||
};
|
||||
@ -107,7 +106,7 @@ impl Command for SubCommand {
|
||||
},
|
||||
];
|
||||
Some(Value::List {
|
||||
vals: vec![Value::Record { cols, vals, span }],
|
||||
vals: vec![Value::test_record(Record { cols, vals })],
|
||||
span,
|
||||
})
|
||||
};
|
||||
@ -137,40 +136,19 @@ impl Command for SubCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_date_into_table(date: Result<DateTime<FixedOffset>, Value>, head: Span) -> Value {
|
||||
let cols = vec![
|
||||
"year".into(),
|
||||
"month".into(),
|
||||
"day".into(),
|
||||
"hour".into(),
|
||||
"minute".into(),
|
||||
"second".into(),
|
||||
"nanosecond".into(),
|
||||
"timezone".into(),
|
||||
];
|
||||
match date {
|
||||
Ok(x) => {
|
||||
let vals = vec![
|
||||
Value::int(x.year() as i64, head),
|
||||
Value::int(x.month() as i64, head),
|
||||
Value::int(x.day() as i64, head),
|
||||
Value::int(x.hour() as i64, head),
|
||||
Value::int(x.minute() as i64, head),
|
||||
Value::int(x.second() as i64, head),
|
||||
Value::int(x.nanosecond() as i64, head),
|
||||
Value::string(x.offset().to_string(), head),
|
||||
];
|
||||
Value::List {
|
||||
vals: vec![Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: head,
|
||||
}],
|
||||
span: head,
|
||||
}
|
||||
}
|
||||
Err(e) => e,
|
||||
}
|
||||
fn parse_date_into_table(date: DateTime<FixedOffset>, head: Span) -> Value {
|
||||
let record = record! {
|
||||
"year" => Value::int(date.year() as i64, head),
|
||||
"month" => Value::int(date.month() as i64, head),
|
||||
"day" => Value::int(date.day() as i64, head),
|
||||
"hour" => Value::int(date.hour() as i64, head),
|
||||
"minute" => Value::int(date.minute() as i64, head),
|
||||
"second" => Value::int(date.second() as i64, head),
|
||||
"nanosecond" => Value::int(date.nanosecond() as i64, head),
|
||||
"timezone" => Value::string(date.offset().to_string(), head),
|
||||
};
|
||||
|
||||
Value::list(vec![Value::record(record, head)], head)
|
||||
}
|
||||
|
||||
fn helper(val: Value, head: Span) -> Value {
|
||||
@ -178,16 +156,16 @@ fn helper(val: Value, head: Span) -> Value {
|
||||
Value::String {
|
||||
val,
|
||||
span: val_span,
|
||||
} => {
|
||||
let date = parse_date_from_string(&val, val_span);
|
||||
parse_date_into_table(date, head)
|
||||
}
|
||||
} => match parse_date_from_string(&val, val_span) {
|
||||
Ok(date) => parse_date_into_table(date, head),
|
||||
Err(e) => e,
|
||||
},
|
||||
Value::Nothing { span: _ } => {
|
||||
let now = Local::now();
|
||||
let n = now.with_timezone(now.offset());
|
||||
parse_date_into_table(Ok(n), head)
|
||||
parse_date_into_table(n, head)
|
||||
}
|
||||
Value::Date { val, span: _ } => parse_date_into_table(Ok(val), head),
|
||||
Value::Date { val, span: _ } => parse_date_into_table(val, head),
|
||||
_ => Value::Error {
|
||||
error: Box::new(DatetimeParseError(val.debug_value(), head)),
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
use chrono::{DateTime, FixedOffset, Local, LocalResult, TimeZone};
|
||||
use nu_protocol::{ShellError, Span, Value};
|
||||
use nu_protocol::{record, ShellError, Span, Value};
|
||||
|
||||
pub(crate) fn parse_date_from_string(
|
||||
input: &str,
|
||||
@ -31,11 +31,6 @@ pub(crate) fn parse_date_from_string(
|
||||
/// * `head` - use the call's head
|
||||
/// * `show_parse_only_formats` - whether parse-only format specifiers (that can't be outputted) should be shown. Should only be used for `into datetime`, not `format date`
|
||||
pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool) -> Value {
|
||||
let column_names = vec![
|
||||
"Specification".into(),
|
||||
"Example".into(),
|
||||
"Description".into(),
|
||||
];
|
||||
let now = Local::now();
|
||||
|
||||
struct FormatSpecification<'a> {
|
||||
@ -258,14 +253,15 @@ pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool)
|
||||
|
||||
let mut records = specifications
|
||||
.iter()
|
||||
.map(|s| Value::Record {
|
||||
cols: column_names.clone(),
|
||||
vals: vec![
|
||||
Value::string(s.spec, head),
|
||||
Value::string(now.format(s.spec).to_string(), head),
|
||||
Value::string(s.description, head),
|
||||
],
|
||||
span: head,
|
||||
.map(|s| {
|
||||
Value::record(
|
||||
record! {
|
||||
"Specification" => Value::string(s.spec, head),
|
||||
"Example" => Value::string(now.format(s.spec).to_string(), head),
|
||||
"Description" => Value::string(s.description, head),
|
||||
},
|
||||
head,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<Value>>();
|
||||
|
||||
@ -279,21 +275,16 @@ pub(crate) fn generate_strftime_list(head: Span, show_parse_only_formats: bool)
|
||||
.unwrap_or("")
|
||||
.to_string();
|
||||
|
||||
records.push(Value::Record {
|
||||
cols: column_names,
|
||||
vals: vec![
|
||||
Value::string("%#z", head),
|
||||
Value::String {
|
||||
val: example,
|
||||
span: head,
|
||||
},
|
||||
Value::string(
|
||||
"Parsing only: Same as %z but allows minutes to be missing or present.",
|
||||
head,
|
||||
),
|
||||
],
|
||||
span: head,
|
||||
});
|
||||
let description = "Parsing only: Same as %z but allows minutes to be missing or present.";
|
||||
|
||||
records.push(Value::record(
|
||||
record! {
|
||||
"Specification" => Value::string("%#z", head),
|
||||
"Example" => Value::string(example, head),
|
||||
"Description" => Value::string(description, head),
|
||||
},
|
||||
head,
|
||||
));
|
||||
}
|
||||
|
||||
Value::List {
|
||||
|
Reference in New Issue
Block a user