mirror of
https://github.com/nushell/nushell.git
synced 2025-05-17 00:10:53 +02:00
# 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}`.
187 lines
5.9 KiB
Rust
187 lines
5.9 KiB
Rust
use eml_parser::eml::*;
|
|
use eml_parser::EmlParser;
|
|
use indexmap::map::IndexMap;
|
|
use nu_plugin::{EvaluatedCall, LabeledError};
|
|
use nu_protocol::{record, PluginExample, Record, ShellError, Span, Value};
|
|
|
|
const DEFAULT_BODY_PREVIEW: usize = 50;
|
|
pub const CMD_NAME: &str = "from eml";
|
|
|
|
pub fn from_eml_call(call: &EvaluatedCall, input: &Value) -> Result<Value, LabeledError> {
|
|
let preview_body: usize = call
|
|
.get_flag::<i64>("preview-body")?
|
|
.map(|l| if l < 0 { 0 } else { l as usize })
|
|
.unwrap_or(DEFAULT_BODY_PREVIEW);
|
|
from_eml(input, preview_body, call.head)
|
|
}
|
|
|
|
pub fn examples() -> Vec<PluginExample> {
|
|
vec![
|
|
PluginExample {
|
|
description: "Convert eml structured data into record".into(),
|
|
example: "'From: test@email.com
|
|
Subject: Welcome
|
|
To: someone@somewhere.com
|
|
Test' | from eml"
|
|
.into(),
|
|
result: Some(Value::test_record(Record {
|
|
cols: vec![
|
|
"Subject".to_string(),
|
|
"From".to_string(),
|
|
"To".to_string(),
|
|
"Body".to_string(),
|
|
],
|
|
vals: vec![
|
|
Value::test_string("Welcome"),
|
|
Value::test_record(Record {
|
|
cols: vec!["Name".to_string(), "Address".to_string()],
|
|
vals: vec![
|
|
Value::nothing(Span::test_data()),
|
|
Value::test_string("test@email.com"),
|
|
],
|
|
}),
|
|
Value::test_record(Record {
|
|
cols: vec!["Name".to_string(), "Address".to_string()],
|
|
vals: vec![
|
|
Value::nothing(Span::test_data()),
|
|
Value::test_string("someone@somewhere.com"),
|
|
],
|
|
}),
|
|
Value::test_string("Test"),
|
|
],
|
|
})),
|
|
},
|
|
PluginExample {
|
|
description: "Convert eml structured data into record".into(),
|
|
example: "'From: test@email.com
|
|
Subject: Welcome
|
|
To: someone@somewhere.com
|
|
Test' | from eml -b 1"
|
|
.into(),
|
|
result: Some(Value::test_record(Record {
|
|
cols: vec![
|
|
"Subject".to_string(),
|
|
"From".to_string(),
|
|
"To".to_string(),
|
|
"Body".to_string(),
|
|
],
|
|
vals: vec![
|
|
Value::test_string("Welcome"),
|
|
Value::test_record(Record {
|
|
cols: vec!["Name".to_string(), "Address".to_string()],
|
|
vals: vec![
|
|
Value::nothing(Span::test_data()),
|
|
Value::test_string("test@email.com"),
|
|
],
|
|
}),
|
|
Value::test_record(Record {
|
|
cols: vec!["Name".to_string(), "Address".to_string()],
|
|
vals: vec![
|
|
Value::nothing(Span::test_data()),
|
|
Value::test_string("someone@somewhere.com"),
|
|
],
|
|
}),
|
|
Value::test_string("T"),
|
|
],
|
|
})),
|
|
},
|
|
]
|
|
}
|
|
|
|
fn emailaddress_to_value(span: Span, email_address: &EmailAddress) -> Value {
|
|
let (n, a) = match email_address {
|
|
EmailAddress::AddressOnly { address } => (
|
|
Value::nothing(span),
|
|
Value::String {
|
|
val: address.to_string(),
|
|
span,
|
|
},
|
|
),
|
|
EmailAddress::NameAndEmailAddress { name, address } => (
|
|
Value::String {
|
|
val: name.to_string(),
|
|
span,
|
|
},
|
|
Value::String {
|
|
val: address.to_string(),
|
|
span,
|
|
},
|
|
),
|
|
};
|
|
|
|
Value::record(
|
|
record! {
|
|
"Name" => n,
|
|
"Address" => a,
|
|
},
|
|
span,
|
|
)
|
|
}
|
|
|
|
fn headerfieldvalue_to_value(head: Span, value: &HeaderFieldValue) -> Value {
|
|
use HeaderFieldValue::*;
|
|
|
|
match value {
|
|
SingleEmailAddress(address) => emailaddress_to_value(head, address),
|
|
MultipleEmailAddresses(addresses) => Value::List {
|
|
vals: addresses
|
|
.iter()
|
|
.map(|a| emailaddress_to_value(head, a))
|
|
.collect(),
|
|
span: head,
|
|
},
|
|
Unstructured(s) => Value::string(s, head),
|
|
Empty => Value::nothing(head),
|
|
}
|
|
}
|
|
|
|
fn from_eml(input: &Value, body_preview: usize, head: Span) -> Result<Value, LabeledError> {
|
|
let value = input.as_string()?;
|
|
|
|
let eml = EmlParser::from_string(value)
|
|
.with_body_preview(body_preview)
|
|
.parse()
|
|
.map_err(|_| ShellError::CantConvert {
|
|
to_type: "structured eml data".into(),
|
|
from_type: "string".into(),
|
|
span: head,
|
|
help: None,
|
|
})?;
|
|
|
|
let mut collected = IndexMap::new();
|
|
|
|
if let Some(subj) = eml.subject {
|
|
collected.insert(
|
|
"Subject".to_string(),
|
|
Value::String {
|
|
val: subj,
|
|
span: head,
|
|
},
|
|
);
|
|
}
|
|
|
|
if let Some(from) = eml.from {
|
|
collected.insert("From".to_string(), headerfieldvalue_to_value(head, &from));
|
|
}
|
|
|
|
if let Some(to) = eml.to {
|
|
collected.insert("To".to_string(), headerfieldvalue_to_value(head, &to));
|
|
}
|
|
|
|
for HeaderField { name, value } in &eml.headers {
|
|
collected.insert(name.to_string(), headerfieldvalue_to_value(head, value));
|
|
}
|
|
|
|
if let Some(body) = eml.body {
|
|
collected.insert(
|
|
"Body".to_string(),
|
|
Value::String {
|
|
val: body,
|
|
span: head,
|
|
},
|
|
);
|
|
}
|
|
|
|
Ok(Value::record(collected.into_iter().collect(), head))
|
|
}
|