mirror of
https://github.com/nushell/nushell
synced 2024-12-26 13:03:07 +00:00
Add from-eml
command (#1656)
* from-eml initial ver * Adding tests for `from-eml` * Add eml to prepares_and_decorates_filesystem_source_files * Sort the file order Co-authored-by: Jonathan Turner <jonathandturner@users.noreply.github.com>
This commit is contained in:
parent
e7767ab7b3
commit
ad8ab5b04d
8 changed files with 313 additions and 0 deletions
|
@ -18,6 +18,7 @@ nu-parser = { version = "0.13.0", path = "../nu-parser" }
|
||||||
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
|
nu-value-ext = { version = "0.13.0", path = "../nu-value-ext" }
|
||||||
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
|
nu-test-support = { version = "0.13.0", path = "../nu-test-support" }
|
||||||
|
|
||||||
|
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
app_dirs = "1.2.1"
|
app_dirs = "1.2.1"
|
||||||
async-stream = "0.2"
|
async-stream = "0.2"
|
||||||
|
@ -35,6 +36,7 @@ ctrlc = "3.1.4"
|
||||||
derive-new = "0.5.8"
|
derive-new = "0.5.8"
|
||||||
dirs = "2.0.2"
|
dirs = "2.0.2"
|
||||||
dunce = "1.0.0"
|
dunce = "1.0.0"
|
||||||
|
eml-parser = "0.1.0"
|
||||||
filesize = "0.2.0"
|
filesize = "0.2.0"
|
||||||
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
futures = { version = "0.3", features = ["compat", "io-compat"] }
|
||||||
futures-util = "0.3.4"
|
futures-util = "0.3.4"
|
||||||
|
|
|
@ -328,6 +328,7 @@ pub fn create_default_context(
|
||||||
whole_stream_command(ToYAML),
|
whole_stream_command(ToYAML),
|
||||||
// File format input
|
// File format input
|
||||||
whole_stream_command(FromCSV),
|
whole_stream_command(FromCSV),
|
||||||
|
whole_stream_command(FromEML),
|
||||||
whole_stream_command(FromTSV),
|
whole_stream_command(FromTSV),
|
||||||
whole_stream_command(FromSSV),
|
whole_stream_command(FromSSV),
|
||||||
whole_stream_command(FromINI),
|
whole_stream_command(FromINI),
|
||||||
|
|
|
@ -32,6 +32,7 @@ pub(crate) mod first;
|
||||||
pub(crate) mod format;
|
pub(crate) mod format;
|
||||||
pub(crate) mod from_bson;
|
pub(crate) mod from_bson;
|
||||||
pub(crate) mod from_csv;
|
pub(crate) mod from_csv;
|
||||||
|
pub(crate) mod from_eml;
|
||||||
pub(crate) mod from_ics;
|
pub(crate) mod from_ics;
|
||||||
pub(crate) mod from_ini;
|
pub(crate) mod from_ini;
|
||||||
pub(crate) mod from_json;
|
pub(crate) mod from_json;
|
||||||
|
@ -145,6 +146,7 @@ pub(crate) use first::First;
|
||||||
pub(crate) use format::Format;
|
pub(crate) use format::Format;
|
||||||
pub(crate) use from_bson::FromBSON;
|
pub(crate) use from_bson::FromBSON;
|
||||||
pub(crate) use from_csv::FromCSV;
|
pub(crate) use from_csv::FromCSV;
|
||||||
|
pub(crate) use from_eml::FromEML;
|
||||||
pub(crate) use from_ics::FromIcs;
|
pub(crate) use from_ics::FromIcs;
|
||||||
pub(crate) use from_ini::FromINI;
|
pub(crate) use from_ini::FromINI;
|
||||||
pub(crate) use from_json::FromJSON;
|
pub(crate) use from_json::FromJSON;
|
||||||
|
|
122
crates/nu-cli/src/commands/from_eml.rs
Normal file
122
crates/nu-cli/src/commands/from_eml.rs
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use ::eml_parser::eml::*;
|
||||||
|
use ::eml_parser::EmlParser;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, TaggedDictBuilder, UntaggedValue};
|
||||||
|
use nu_source::Tagged;
|
||||||
|
|
||||||
|
pub struct FromEML;
|
||||||
|
|
||||||
|
const DEFAULT_BODY_PREVIEW: usize = 50;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone)]
|
||||||
|
pub struct FromEMLArgs {
|
||||||
|
#[serde(rename(deserialize = "preview-body"))]
|
||||||
|
preview_body: Option<Tagged<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WholeStreamCommand for FromEML {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"from-eml"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("from-eml").named(
|
||||||
|
"preview-body",
|
||||||
|
SyntaxShape::Int,
|
||||||
|
"How many bytes of the body to preview",
|
||||||
|
Some('b'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Parse text as .eml and create table."
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
args.process(registry, from_eml)?.run()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emailaddress_to_value(tag: &Tag, email_address: &EmailAddress) -> TaggedDictBuilder {
|
||||||
|
let mut dict = TaggedDictBuilder::with_capacity(tag, 2);
|
||||||
|
let (n, a) = match email_address {
|
||||||
|
EmailAddress::AddressOnly { address } => {
|
||||||
|
(UntaggedValue::nothing(), UntaggedValue::string(address))
|
||||||
|
}
|
||||||
|
EmailAddress::NameAndEmailAddress { name, address } => {
|
||||||
|
(UntaggedValue::string(name), UntaggedValue::string(address))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
dict.insert_untagged("Name", n);
|
||||||
|
dict.insert_untagged("Address", a);
|
||||||
|
|
||||||
|
dict
|
||||||
|
}
|
||||||
|
|
||||||
|
fn headerfieldvalue_to_value(tag: &Tag, value: &HeaderFieldValue) -> UntaggedValue {
|
||||||
|
use HeaderFieldValue::*;
|
||||||
|
|
||||||
|
match value {
|
||||||
|
SingleEmailAddress(address) => emailaddress_to_value(tag, address).into_untagged_value(),
|
||||||
|
MultipleEmailAddresses(addresses) => UntaggedValue::Table(
|
||||||
|
addresses
|
||||||
|
.iter()
|
||||||
|
.map(|a| emailaddress_to_value(tag, a).into_value())
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
Unstructured(s) => UntaggedValue::string(s),
|
||||||
|
Empty => UntaggedValue::nothing(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_eml(
|
||||||
|
eml_args: FromEMLArgs,
|
||||||
|
runnable_context: RunnableContext,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let input = runnable_context.input;
|
||||||
|
let tag = runnable_context.name;
|
||||||
|
|
||||||
|
let stream = async_stream! {
|
||||||
|
let value = input.collect_string(tag.clone()).await?;
|
||||||
|
|
||||||
|
let body_preview = eml_args.preview_body.map(|b| b.item).unwrap_or(DEFAULT_BODY_PREVIEW);
|
||||||
|
|
||||||
|
let eml = EmlParser::from_string(value.item)
|
||||||
|
.with_body_preview(body_preview)
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ShellError::labeled_error("Could not parse .eml file", "could not parse .eml file", &tag))?;
|
||||||
|
|
||||||
|
let mut dict = TaggedDictBuilder::new(&tag);
|
||||||
|
|
||||||
|
if let Some(subj) = eml.subject {
|
||||||
|
dict.insert_untagged("Subject", UntaggedValue::string(subj));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(from) = eml.from {
|
||||||
|
dict.insert_untagged("From", headerfieldvalue_to_value(&tag, &from));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(to) = eml.to {
|
||||||
|
dict.insert_untagged("To", headerfieldvalue_to_value(&tag, &to));
|
||||||
|
}
|
||||||
|
|
||||||
|
for HeaderField{ name, value } in eml.headers.iter() {
|
||||||
|
dict.insert_untagged(name, headerfieldvalue_to_value(&tag, &value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(body) = eml.body {
|
||||||
|
dict.insert_untagged("Body", UntaggedValue::string(body));
|
||||||
|
}
|
||||||
|
|
||||||
|
yield ReturnSuccess::value(dict.into_value());
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(stream.to_output_stream())
|
||||||
|
}
|
|
@ -301,6 +301,10 @@ mod tests {
|
||||||
loc: fixtures().join("sample.db"),
|
loc: fixtures().join("sample.db"),
|
||||||
at: 0
|
at: 0
|
||||||
},
|
},
|
||||||
|
Res {
|
||||||
|
loc: fixtures().join("sample.eml"),
|
||||||
|
at: 0
|
||||||
|
},
|
||||||
Res {
|
Res {
|
||||||
loc: fixtures().join("sample.ini"),
|
loc: fixtures().join("sample.ini"),
|
||||||
at: 0
|
at: 0
|
||||||
|
|
84
crates/nu-cli/tests/format_conversions/eml.rs
Normal file
84
crates/nu-cli/tests/format_conversions/eml.rs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
use nu_test_support::{nu, pipeline};
|
||||||
|
|
||||||
|
const TEST_CWD: &str = "tests/fixtures/formats";
|
||||||
|
|
||||||
|
// The To field in this email is just "username@domain.com", which gets parsed out as the Address. The Name is empty.
|
||||||
|
#[test]
|
||||||
|
fn from_eml_get_to_field() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: TEST_CWD,
|
||||||
|
pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.eml
|
||||||
|
| get To
|
||||||
|
| get Address
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "username@domain.com");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: TEST_CWD,
|
||||||
|
pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.eml
|
||||||
|
| get To
|
||||||
|
| get Name
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Reply-To field in this email is "aw-confirm@ebay.com" <aw-confirm@ebay.com>, meaning both the Name and Address values are identical.
|
||||||
|
#[test]
|
||||||
|
fn from_eml_get_replyto_field() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: TEST_CWD,
|
||||||
|
pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.eml
|
||||||
|
| get Reply-To
|
||||||
|
| get Address
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "aw-confirm@ebay.com");
|
||||||
|
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: TEST_CWD,
|
||||||
|
pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.eml
|
||||||
|
| get Reply-To
|
||||||
|
| get Name
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "aw-confirm@ebay.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Reply-To field in this email is "aw-confirm@ebay.com" <aw-confirm@ebay.com>, meaning both the Name and Address values are identical.
|
||||||
|
#[test]
|
||||||
|
fn from_eml_get_subject_field() {
|
||||||
|
let actual = nu!(
|
||||||
|
cwd: TEST_CWD,
|
||||||
|
pipeline(
|
||||||
|
r#"
|
||||||
|
open sample.eml
|
||||||
|
| get Subject
|
||||||
|
| echo $it
|
||||||
|
"#
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(actual, "Billing Issues");
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod bson;
|
mod bson;
|
||||||
mod csv;
|
mod csv;
|
||||||
|
mod eml;
|
||||||
mod html;
|
mod html;
|
||||||
mod ics;
|
mod ics;
|
||||||
mod json;
|
mod json;
|
||||||
|
|
97
tests/fixtures/formats/sample.eml
vendored
Normal file
97
tests/fixtures/formats/sample.eml
vendored
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
Return-Path: <aw-confirm@ebay.com>
|
||||||
|
X-Original-To: username@domain.com
|
||||||
|
Delivered-To: username@domain.com
|
||||||
|
Received: from 81.18.87.130 (unknown [81.18.87.190])
|
||||||
|
by spanky.domain.com (Postfix) with SMTP id CCC115378FC
|
||||||
|
for <username@domain.com>; Sat, 27 Nov 2004 15:33:24 -0500 (EST)
|
||||||
|
Received: from 20.84.152.113 by 65.23.81.142; Sat, 27 Nov 2004 15:24:27 -0500
|
||||||
|
Message-ID: <LCCOVYFMRBWQCHUDWYCOYUW@hotmail.com>
|
||||||
|
From: "aw-confirm@ebay.com" <aw-confirm@ebay.com>
|
||||||
|
Reply-To: "aw-confirm@ebay.com" <aw-confirm@ebay.com>
|
||||||
|
To: username@domain.com
|
||||||
|
Subject: Billing Issues
|
||||||
|
Date: Sun, 28 Nov 2004 00:30:27 +0400
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: multipart/alternative;
|
||||||
|
boundary="--591699981497957"
|
||||||
|
X-Priority: 3
|
||||||
|
X-CS-IP: 224.248.218.116
|
||||||
|
Status: O
|
||||||
|
X-Status:
|
||||||
|
X-Keywords:
|
||||||
|
X-UID: 1
|
||||||
|
|
||||||
|
----591699981497957
|
||||||
|
Content-Type: text/html;
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<p>
|
||||||
|
<A target=3D"_blank"
|
||||||
|
href=3D"http://click3.ebay.com/99019653.50692.0.20283"
|
||||||
|
><IMG
|
||||||
|
src=3D"http://emailpics3.ebay.com/627422250/images/logo-18.gif"
|
||||||
|
border=3D0></A></p>
|
||||||
|
<p><br>
|
||||||
|
<FONT SIZE=3D2 PTSIZE=3D10 FAMILY=3D"SANSSERIF"
|
||||||
|
FACE=3D"Arial" LANG=3D"0"><FONT SIZE=3D2 PTSIZE=3D10
|
||||||
|
FAMILY=3D"SANSSERIF" FACE=3D"Arial" LANG=3D"0">Dear valued
|
||||||
|
eBay member:<BR>
|
||||||
|
<BR>
|
||||||
|
We recently have determined that different computers
|
||||||
|
have logged onto your eBay account, and multiple
|
||||||
|
password failures were present before the logons. We
|
||||||
|
now need you to re-confirm your account information to
|
||||||
|
us. If this is not completed by <strong>November 30,
|
||||||
|
2004</strong>, we will be forced to suspend your
|
||||||
|
account indefinitely, as it may have been used for
|
||||||
|
fraudulent purposes. We thank you for your cooperation
|
||||||
|
in this manner.</FONT>
|
||||||
|
<p>To confirm your eBay records click here: <br>
|
||||||
|
<a
|
||||||
|
HREF=3Dhttp://140.130.108.11/account/aw-confirm/ebayDLLupdate/index.html
|
||||||
|
target=3D"_self">http://cgi1.ebay.com/aw-cgi/ebayISAPI.dll?UPdate</a></p>
|
||||||
|
<p><font size=3D"2" face=3D"Arial, Helvetica,
|
||||||
|
sans-serif"><font color=3D"#000000">We appreciate your
|
||||||
|
support and understanding, as we work together to keep
|
||||||
|
eBay a safe place to trade.</font></font><font
|
||||||
|
color=3D"#000000" size=3D"2" face=3D"Arial, Helvetica,
|
||||||
|
sans-serif"><BR>
|
||||||
|
Thank you for your patience in this matter.</font><br>
|
||||||
|
<br>
|
||||||
|
</p>
|
||||||
|
<p><font color=3D"#000000" size=3D"2" face=3D"Arial,
|
||||||
|
Helvetica, sans-serif">Trust and Safety
|
||||||
|
Department</font><br>
|
||||||
|
<font color=3D"#000000" size=3D"2" face=3D"Arial,
|
||||||
|
Helvetica, sans-serif">eBay Inc.</font></p>
|
||||||
|
<p><font size=3D"2" face=3D"Arial, Helvetica,
|
||||||
|
sans-serif"><font color=3D"#999999" size=3D"1">Please do
|
||||||
|
not reply to this e-mail as this is only a
|
||||||
|
notification. Mail sent to this address cannot be
|
||||||
|
answered. </font></font></p>
|
||||||
|
<p><font color=3D"#000000" size=3D"1" face=3D"Arial,
|
||||||
|
Helvetica, sans-serif"><font color=3D"black"><span
|
||||||
|
style=3D"color: black;">Copyright 1995-2004 <a
|
||||||
|
target=3D"_blank"
|
||||||
|
href=3D"http://pages.ebay.com/community/aboutebay/index.html">eBay
|
||||||
|
Inc.</a> All Rights Reserved. Designated trademarks
|
||||||
|
and brands are the property of their respective
|
||||||
|
owners. Use of this Web site constitutes acceptance of
|
||||||
|
the eBay <a target=3D"_blank"
|
||||||
|
href=3D"http://pages.ebay.com/help/community/png-user.html">User
|
||||||
|
Agreement</a> and <a target=3D"_blank"
|
||||||
|
href=3D"http://pages.ebay.com/help/community/png-priv.html">Privacy
|
||||||
|
Policy</a>.</span> </font></font><font color=3D"#000000"
|
||||||
|
size=3D"1" face=3D"Arial,
|
||||||
|
Helvetica, sans-serif">Designated trademarks and
|
||||||
|
brands are the property of their respective owners.
|
||||||
|
eBay and the eBay logo are trademarks of eBay Inc.
|
||||||
|
eBay is located at 2145 Hamilton Avenue, San Jose, CA
|
||||||
|
95125. </font><br>
|
||||||
|
</p>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----591699981497957--
|
Loading…
Reference in a new issue