From 2f4480141428f70611e1c0f149929e4ce5b4a8f2 Mon Sep 17 00:00:00 2001
From: Jack Wright <56345+ayax79@users.noreply.github.com>
Date: Mon, 5 Aug 2024 14:07:15 -0700
Subject: [PATCH] Adding plist support (#13545)
# Description
Provides the ability convert from and to plist format.
# User-Facing Changes
- Introduction of `from plist` command
- Introduction of `to plist`command
---------
Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
---
Cargo.lock | 30 ++-
crates/nu_plugin_formats/Cargo.toml | 4 +-
crates/nu_plugin_formats/src/from/eml.rs | 8 +-
crates/nu_plugin_formats/src/from/ics.rs | 8 +-
crates/nu_plugin_formats/src/from/ini.rs | 8 +-
crates/nu_plugin_formats/src/from/mod.rs | 9 +-
crates/nu_plugin_formats/src/from/plist.rs | 240 +++++++++++++++++++++
crates/nu_plugin_formats/src/from/vcf.rs | 8 +-
crates/nu_plugin_formats/src/lib.rs | 17 +-
crates/nu_plugin_formats/src/main.rs | 4 +-
crates/nu_plugin_formats/src/to/mod.rs | 1 +
crates/nu_plugin_formats/src/to/plist.rs | 113 ++++++++++
12 files changed, 418 insertions(+), 32 deletions(-)
create mode 100644 crates/nu_plugin_formats/src/from/plist.rs
create mode 100644 crates/nu_plugin_formats/src/to/mod.rs
create mode 100644 crates/nu_plugin_formats/src/to/plist.rs
diff --git a/Cargo.lock b/Cargo.lock
index 89e5127356..902210ffbc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -605,7 +605,7 @@ dependencies = [
"encoding_rs",
"log",
"once_cell",
- "quick-xml",
+ "quick-xml 0.31.0",
"serde",
"zip",
]
@@ -3094,7 +3094,7 @@ dependencies = [
"pretty_assertions",
"print-positions",
"procfs",
- "quick-xml",
+ "quick-xml 0.31.0",
"quickcheck",
"quickcheck_macros",
"rand",
@@ -3476,12 +3476,14 @@ dependencies = [
name = "nu_plugin_formats"
version = "0.96.2"
dependencies = [
+ "chrono",
"eml-parser",
"ical",
"indexmap",
"nu-plugin",
"nu-plugin-test-support",
"nu-protocol",
+ "plist",
"rust-ini",
]
@@ -4148,6 +4150,19 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "plist"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016"
+dependencies = [
+ "base64 0.22.1",
+ "indexmap",
+ "quick-xml 0.32.0",
+ "serde",
+ "time",
+]
+
[[package]]
name = "polars"
version = "0.41.2"
@@ -4816,6 +4831,15 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "quick-xml"
+version = "0.32.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "quickcheck"
version = "1.0.3"
@@ -6872,7 +6896,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283"
dependencies = [
"proc-macro2",
- "quick-xml",
+ "quick-xml 0.31.0",
"quote",
]
diff --git a/crates/nu_plugin_formats/Cargo.toml b/crates/nu_plugin_formats/Cargo.toml
index dbf1b43657..f6ad84758f 100644
--- a/crates/nu_plugin_formats/Cargo.toml
+++ b/crates/nu_plugin_formats/Cargo.toml
@@ -16,6 +16,8 @@ indexmap = { workspace = true }
eml-parser = "0.1"
ical = "0.11"
rust-ini = "0.21.0"
+plist = "1.7"
+chrono = "0.4"
[dev-dependencies]
-nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" }
\ No newline at end of file
+nu-plugin-test-support = { path = "../nu-plugin-test-support", version = "0.96.2" }
diff --git a/crates/nu_plugin_formats/src/from/eml.rs b/crates/nu_plugin_formats/src/from/eml.rs
index 2630e3b1c2..2b4c33a814 100644
--- a/crates/nu_plugin_formats/src/from/eml.rs
+++ b/crates/nu_plugin_formats/src/from/eml.rs
@@ -1,4 +1,4 @@
-use crate::FromCmds;
+use crate::FormatCmdsPlugin;
use eml_parser::eml::*;
use eml_parser::EmlParser;
use indexmap::IndexMap;
@@ -12,7 +12,7 @@ const DEFAULT_BODY_PREVIEW: usize = 50;
pub struct FromEml;
impl SimplePluginCommand for FromEml {
- type Plugin = FromCmds;
+ type Plugin = FormatCmdsPlugin;
fn name(&self) -> &str {
"from eml"
@@ -40,7 +40,7 @@ impl SimplePluginCommand for FromEml {
fn run(
&self,
- _plugin: &FromCmds,
+ _plugin: &FormatCmdsPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
@@ -176,5 +176,5 @@ fn from_eml(input: &Value, body_preview: usize, head: Span) -> Result Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
- PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromEml)
+ PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromEml)
}
diff --git a/crates/nu_plugin_formats/src/from/ics.rs b/crates/nu_plugin_formats/src/from/ics.rs
index 099b3431fe..bcd7311f8a 100644
--- a/crates/nu_plugin_formats/src/from/ics.rs
+++ b/crates/nu_plugin_formats/src/from/ics.rs
@@ -1,4 +1,4 @@
-use crate::FromCmds;
+use crate::FormatCmdsPlugin;
use ical::{parser::ical::component::*, property::Property};
use indexmap::IndexMap;
@@ -11,7 +11,7 @@ use std::io::BufReader;
pub struct FromIcs;
impl SimplePluginCommand for FromIcs {
- type Plugin = FromCmds;
+ type Plugin = FormatCmdsPlugin;
fn name(&self) -> &str {
"from ics"
@@ -33,7 +33,7 @@ impl SimplePluginCommand for FromIcs {
fn run(
&self,
- _plugin: &FromCmds,
+ _plugin: &FormatCmdsPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
@@ -274,5 +274,5 @@ fn params_to_value(params: Vec<(String, Vec)>, span: Span) -> Value {
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
- PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromIcs)
+ PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromIcs)
}
diff --git a/crates/nu_plugin_formats/src/from/ini.rs b/crates/nu_plugin_formats/src/from/ini.rs
index cf37ffc3d7..bb44ce1398 100644
--- a/crates/nu_plugin_formats/src/from/ini.rs
+++ b/crates/nu_plugin_formats/src/from/ini.rs
@@ -1,4 +1,4 @@
-use crate::FromCmds;
+use crate::FormatCmdsPlugin;
use nu_plugin::{EngineInterface, EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
@@ -8,7 +8,7 @@ use nu_protocol::{
pub struct FromIni;
impl SimplePluginCommand for FromIni {
- type Plugin = FromCmds;
+ type Plugin = FormatCmdsPlugin;
fn name(&self) -> &str {
"from ini"
@@ -30,7 +30,7 @@ impl SimplePluginCommand for FromIni {
fn run(
&self,
- _plugin: &FromCmds,
+ _plugin: &FormatCmdsPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
@@ -101,5 +101,5 @@ b=2' | from ini",
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
- PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromIni)
+ PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromIni)
}
diff --git a/crates/nu_plugin_formats/src/from/mod.rs b/crates/nu_plugin_formats/src/from/mod.rs
index 4b4976f391..232dde7d6d 100644
--- a/crates/nu_plugin_formats/src/from/mod.rs
+++ b/crates/nu_plugin_formats/src/from/mod.rs
@@ -1,4 +1,5 @@
-pub mod eml;
-pub mod ics;
-pub mod ini;
-pub mod vcf;
+pub(crate) mod eml;
+pub(crate) mod ics;
+pub(crate) mod ini;
+pub(crate) mod plist;
+pub(crate) mod vcf;
diff --git a/crates/nu_plugin_formats/src/from/plist.rs b/crates/nu_plugin_formats/src/from/plist.rs
new file mode 100644
index 0000000000..9894879955
--- /dev/null
+++ b/crates/nu_plugin_formats/src/from/plist.rs
@@ -0,0 +1,240 @@
+use std::time::SystemTime;
+
+use chrono::{DateTime, FixedOffset, Offset, Utc};
+use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
+use nu_protocol::{
+ record, Category, Example, LabeledError, Record, Signature, Span, Value as NuValue,
+};
+use plist::{Date as PlistDate, Dictionary, Value as PlistValue};
+
+use crate::FormatCmdsPlugin;
+
+pub struct FromPlist;
+
+impl SimplePluginCommand for FromPlist {
+ type Plugin = FormatCmdsPlugin;
+
+ fn name(&self) -> &str {
+ "from plist"
+ }
+
+ fn usage(&self) -> &str {
+ "Convert plist to Nushell values"
+ }
+
+ fn examples(&self) -> Vec {
+ vec![Example {
+ example: r#"'
+
+
+
+ a
+ 3
+
+' | from plist"#,
+ description: "Convert a table into a plist file",
+ result: Some(NuValue::test_record(record!( "a" => NuValue::test_int(3)))),
+ }]
+ }
+
+ fn signature(&self) -> Signature {
+ Signature::build(PluginCommand::name(self)).category(Category::Formats)
+ }
+
+ fn run(
+ &self,
+ _plugin: &FormatCmdsPlugin,
+ _engine: &EngineInterface,
+ call: &EvaluatedCall,
+ input: &NuValue,
+ ) -> Result {
+ match input {
+ NuValue::String { val, .. } => {
+ let plist = plist::from_bytes(val.as_bytes())
+ .map_err(|e| build_label_error(format!("{}", e), input.span()))?;
+ let converted = convert_plist_value(&plist, call.head)?;
+ Ok(converted)
+ }
+ NuValue::Binary { val, .. } => {
+ let plist = plist::from_bytes(val)
+ .map_err(|e| build_label_error(format!("{}", e), input.span()))?;
+ let converted = convert_plist_value(&plist, call.head)?;
+ Ok(converted)
+ }
+ _ => Err(build_label_error(
+ format!("Invalid input, must be string not: {:?}", input),
+ call.head,
+ )),
+ }
+ }
+}
+
+fn build_label_error(msg: impl Into, span: Span) -> LabeledError {
+ LabeledError::new("Could not load plist").with_label(msg, span)
+}
+
+fn convert_plist_value(plist_val: &PlistValue, span: Span) -> Result {
+ match plist_val {
+ PlistValue::String(s) => Ok(NuValue::string(s.to_owned(), span)),
+ PlistValue::Boolean(b) => Ok(NuValue::bool(*b, span)),
+ PlistValue::Real(r) => Ok(NuValue::float(*r, span)),
+ PlistValue::Date(d) => Ok(NuValue::date(convert_date(d), span)),
+ PlistValue::Integer(i) => {
+ let signed = i
+ .as_signed()
+ .ok_or_else(|| build_label_error(format!("Cannot convert {i} to i64"), span))?;
+ Ok(NuValue::int(signed, span))
+ }
+ PlistValue::Uid(uid) => Ok(NuValue::float(uid.get() as f64, span)),
+ PlistValue::Data(data) => Ok(NuValue::binary(data.to_owned(), span)),
+ PlistValue::Array(arr) => Ok(NuValue::list(convert_array(arr, span)?, span)),
+ PlistValue::Dictionary(dict) => Ok(convert_dict(dict, span)?),
+ _ => Ok(NuValue::nothing(span)),
+ }
+}
+
+fn convert_dict(dict: &Dictionary, span: Span) -> Result {
+ let cols: Vec = dict.keys().cloned().collect();
+ let vals: Result, LabeledError> = dict
+ .values()
+ .map(|v| convert_plist_value(v, span))
+ .collect();
+ Ok(NuValue::record(
+ Record::from_raw_cols_vals(cols, vals?, span, span)?,
+ span,
+ ))
+}
+
+fn convert_array(plist_array: &[PlistValue], span: Span) -> Result, LabeledError> {
+ plist_array
+ .iter()
+ .map(|v| convert_plist_value(v, span))
+ .collect()
+}
+
+pub fn convert_date(plist_date: &PlistDate) -> DateTime {
+ // In the docs the plist date object is listed as a utc timestamp, so this
+ // conversion should be fine
+ let plist_sys_time: SystemTime = plist_date.to_owned().into();
+ let utc_date: DateTime = plist_sys_time.into();
+ let utc_offset = utc_date.offset().fix();
+ utc_date.with_timezone(&utc_offset)
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+ use chrono::Datelike;
+ use plist::Uid;
+ use std::time::SystemTime;
+
+ use nu_plugin_test_support::PluginTest;
+ use nu_protocol::ShellError;
+
+ #[test]
+ fn test_convert_string() {
+ let plist_val = PlistValue::String("hello".to_owned());
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(
+ result,
+ Ok(NuValue::string("hello".to_owned(), Span::test_data()))
+ );
+ }
+
+ #[test]
+ fn test_convert_boolean() {
+ let plist_val = PlistValue::Boolean(true);
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(result, Ok(NuValue::bool(true, Span::test_data())));
+ }
+
+ #[test]
+ fn test_convert_real() {
+ let plist_val = PlistValue::Real(3.14);
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(result, Ok(NuValue::float(3.14, Span::test_data())));
+ }
+
+ #[test]
+ fn test_convert_integer() {
+ let plist_val = PlistValue::Integer(42.into());
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(result, Ok(NuValue::int(42, Span::test_data())));
+ }
+
+ #[test]
+ fn test_convert_uid() {
+ let v = 12345678_u64;
+ let uid = Uid::new(v);
+ let plist_val = PlistValue::Uid(uid);
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(result, Ok(NuValue::float(v as f64, Span::test_data())));
+ }
+
+ #[test]
+ fn test_convert_data() {
+ let data = vec![0x41, 0x42, 0x43];
+ let plist_val = PlistValue::Data(data.clone());
+ let result = convert_plist_value(&plist_val, Span::test_data());
+ assert_eq!(result, Ok(NuValue::binary(data, Span::test_data())));
+ }
+
+ #[test]
+ fn test_convert_date() {
+ let epoch = SystemTime::UNIX_EPOCH;
+ let plist_date = epoch.into();
+
+ let datetime = convert_date(&plist_date);
+ assert_eq!(1970, datetime.year());
+ assert_eq!(1, datetime.month());
+ assert_eq!(1, datetime.day());
+ }
+
+ #[test]
+ fn test_convert_dict() {
+ let mut dict = Dictionary::new();
+ dict.insert("a".to_string(), PlistValue::String("c".to_string()));
+ dict.insert("b".to_string(), PlistValue::String("d".to_string()));
+ let nu_dict = convert_dict(&dict, Span::test_data()).unwrap();
+ assert_eq!(
+ nu_dict,
+ NuValue::record(
+ Record::from_raw_cols_vals(
+ vec!["a".to_string(), "b".to_string()],
+ vec![
+ NuValue::string("c".to_string(), Span::test_data()),
+ NuValue::string("d".to_string(), Span::test_data())
+ ],
+ Span::test_data(),
+ Span::test_data(),
+ )
+ .expect("failed to create record"),
+ Span::test_data(),
+ )
+ );
+ }
+
+ #[test]
+ fn test_convert_array() {
+ let mut arr = Vec::new();
+ arr.push(PlistValue::String("a".to_string()));
+ arr.push(PlistValue::String("b".to_string()));
+ let nu_arr = convert_array(&arr, Span::test_data()).unwrap();
+ assert_eq!(
+ nu_arr,
+ vec![
+ NuValue::string("a".to_string(), Span::test_data()),
+ NuValue::string("b".to_string(), Span::test_data())
+ ]
+ );
+ }
+
+ #[test]
+ fn test_examples() -> Result<(), ShellError> {
+ let plugin = FormatCmdsPlugin {};
+ let cmd = FromPlist {};
+
+ let mut plugin_test = PluginTest::new("polars", plugin.into())?;
+ plugin_test.test_command_examples(&cmd)
+ }
+}
diff --git a/crates/nu_plugin_formats/src/from/vcf.rs b/crates/nu_plugin_formats/src/from/vcf.rs
index 4de20154d7..a751a774d3 100644
--- a/crates/nu_plugin_formats/src/from/vcf.rs
+++ b/crates/nu_plugin_formats/src/from/vcf.rs
@@ -1,4 +1,4 @@
-use crate::FromCmds;
+use crate::FormatCmdsPlugin;
use ical::{parser::vcard::component::*, property::Property};
use indexmap::IndexMap;
@@ -10,7 +10,7 @@ use nu_protocol::{
pub struct FromVcf;
impl SimplePluginCommand for FromVcf {
- type Plugin = FromCmds;
+ type Plugin = FormatCmdsPlugin;
fn name(&self) -> &str {
"from vcf"
@@ -32,7 +32,7 @@ impl SimplePluginCommand for FromVcf {
fn run(
&self,
- _plugin: &FromCmds,
+ _plugin: &FormatCmdsPlugin,
_engine: &EngineInterface,
call: &EvaluatedCall,
input: &Value,
@@ -164,5 +164,5 @@ fn params_to_value(params: Vec<(String, Vec)>, span: Span) -> Value {
fn test_examples() -> Result<(), nu_protocol::ShellError> {
use nu_plugin_test_support::PluginTest;
- PluginTest::new("formats", crate::FromCmds.into())?.test_command_examples(&FromVcf)
+ PluginTest::new("formats", crate::FormatCmdsPlugin.into())?.test_command_examples(&FromVcf)
}
diff --git a/crates/nu_plugin_formats/src/lib.rs b/crates/nu_plugin_formats/src/lib.rs
index 2ae24a4971..ce95fcf8c9 100644
--- a/crates/nu_plugin_formats/src/lib.rs
+++ b/crates/nu_plugin_formats/src/lib.rs
@@ -1,15 +1,18 @@
mod from;
+mod to;
use nu_plugin::{Plugin, PluginCommand};
-pub use from::eml::FromEml;
-pub use from::ics::FromIcs;
-pub use from::ini::FromIni;
-pub use from::vcf::FromVcf;
+use from::eml::FromEml;
+use from::ics::FromIcs;
+use from::ini::FromIni;
+use from::plist::FromPlist;
+use from::vcf::FromVcf;
+use to::plist::IntoPlist;
-pub struct FromCmds;
+pub struct FormatCmdsPlugin;
-impl Plugin for FromCmds {
+impl Plugin for FormatCmdsPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
@@ -20,6 +23,8 @@ impl Plugin for FromCmds {
Box::new(FromIcs),
Box::new(FromIni),
Box::new(FromVcf),
+ Box::new(FromPlist),
+ Box::new(IntoPlist),
]
}
}
diff --git a/crates/nu_plugin_formats/src/main.rs b/crates/nu_plugin_formats/src/main.rs
index e6c7179781..f36ba3364a 100644
--- a/crates/nu_plugin_formats/src/main.rs
+++ b/crates/nu_plugin_formats/src/main.rs
@@ -1,6 +1,6 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
-use nu_plugin_formats::FromCmds;
+use nu_plugin_formats::FormatCmdsPlugin;
fn main() {
- serve_plugin(&FromCmds, MsgPackSerializer {})
+ serve_plugin(&FormatCmdsPlugin, MsgPackSerializer {})
}
diff --git a/crates/nu_plugin_formats/src/to/mod.rs b/crates/nu_plugin_formats/src/to/mod.rs
new file mode 100644
index 0000000000..2f804de5d6
--- /dev/null
+++ b/crates/nu_plugin_formats/src/to/mod.rs
@@ -0,0 +1 @@
+pub(crate) mod plist;
diff --git a/crates/nu_plugin_formats/src/to/plist.rs b/crates/nu_plugin_formats/src/to/plist.rs
new file mode 100644
index 0000000000..94368c184a
--- /dev/null
+++ b/crates/nu_plugin_formats/src/to/plist.rs
@@ -0,0 +1,113 @@
+use std::time::SystemTime;
+
+use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
+use nu_protocol::{Category, Example, LabeledError, Record, Signature, Span, Value as NuValue};
+use plist::{Integer, Value as PlistValue};
+
+use crate::FormatCmdsPlugin;
+
+pub(crate) struct IntoPlist;
+
+impl SimplePluginCommand for IntoPlist {
+ type Plugin = FormatCmdsPlugin;
+
+ fn name(&self) -> &str {
+ "to plist"
+ }
+
+ fn usage(&self) -> &str {
+ "Convert Nu values into plist"
+ }
+
+ fn examples(&self) -> Vec {
+ vec![Example {
+ example: "{ a: 3 } | to plist",
+ description: "Convert a table into a plist file",
+ result: None,
+ }]
+ }
+
+ fn signature(&self) -> Signature {
+ Signature::build(PluginCommand::name(self))
+ .switch("binary", "Output plist in binary format", Some('b'))
+ .category(Category::Formats)
+ }
+
+ fn run(
+ &self,
+ _plugin: &FormatCmdsPlugin,
+ _engine: &EngineInterface,
+ call: &EvaluatedCall,
+ input: &NuValue,
+ ) -> Result {
+ let plist_val = convert_nu_value(input)?;
+ let mut out = Vec::new();
+ if call.has_flag("binary")? {
+ plist::to_writer_binary(&mut out, &plist_val)
+ .map_err(|e| build_label_error(format!("{}", e), input.span()))?;
+ Ok(NuValue::binary(out, input.span()))
+ } else {
+ plist::to_writer_xml(&mut out, &plist_val)
+ .map_err(|e| build_label_error(format!("{}", e), input.span()))?;
+ Ok(NuValue::string(
+ String::from_utf8(out)
+ .map_err(|e| build_label_error(format!("{}", e), input.span()))?,
+ input.span(),
+ ))
+ }
+ }
+}
+
+fn build_label_error(msg: String, span: Span) -> LabeledError {
+ LabeledError::new("Cannot convert plist").with_label(msg, span)
+}
+
+fn convert_nu_value(nu_val: &NuValue) -> Result {
+ let span = Span::test_data();
+ match nu_val {
+ NuValue::String { val, .. } => Ok(PlistValue::String(val.to_owned())),
+ NuValue::Bool { val, .. } => Ok(PlistValue::Boolean(*val)),
+ NuValue::Float { val, .. } => Ok(PlistValue::Real(*val)),
+ NuValue::Int { val, .. } => Ok(PlistValue::Integer(Into::::into(*val))),
+ NuValue::Binary { val, .. } => Ok(PlistValue::Data(val.to_owned())),
+ NuValue::Record { val, .. } => convert_nu_dict(val),
+ NuValue::List { vals, .. } => Ok(PlistValue::Array(
+ vals.iter()
+ .map(convert_nu_value)
+ .collect::>()?,
+ )),
+ NuValue::Date { val, .. } => Ok(PlistValue::Date(SystemTime::from(val.to_owned()).into())),
+ NuValue::Filesize { val, .. } => Ok(PlistValue::Integer(Into::::into(*val))),
+ _ => Err(build_label_error(
+ format!("{:?} is not convertible", nu_val),
+ span,
+ )),
+ }
+}
+
+fn convert_nu_dict(record: &Record) -> Result {
+ Ok(PlistValue::Dictionary(
+ record
+ .iter()
+ .map(|(k, v)| convert_nu_value(v).map(|v| (k.to_owned(), v)))
+ .collect::>()?,
+ ))
+}
+
+#[cfg(test)]
+mod test {
+
+ use nu_plugin_test_support::PluginTest;
+ use nu_protocol::ShellError;
+
+ use super::*;
+
+ #[test]
+ fn test_examples() -> Result<(), ShellError> {
+ let plugin = FormatCmdsPlugin {};
+ let cmd = IntoPlist {};
+
+ let mut plugin_test = PluginTest::new("polars", plugin.into())?;
+ plugin_test.test_command_examples(&cmd)
+ }
+}