mirror of
https://github.com/nushell/nushell
synced 2024-12-27 05:23:11 +00:00
Adding plist support (#13545)
# Description Provides the ability convert from and to plist format. <img width="1250" alt="Screenshot 2024-08-05 at 10 21 26" src="https://github.com/user-attachments/assets/970f3366-eb70-4d74-a396-649374556f66"> <img width="730" alt="Screenshot 2024-08-05 at 10 22 38" src="https://github.com/user-attachments/assets/6ec317d0-686e-47c6-bf35-8ab6e5d802db"> # User-Facing Changes - Introduction of `from plist` command - Introduction of `to plist`command --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
This commit is contained in:
parent
9172b22985
commit
2f44801414
12 changed files with 418 additions and 32 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -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",
|
||||
]
|
||||
|
||||
|
|
|
@ -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" }
|
|
@ -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<Value, Lab
|
|||
fn test_examples() -> 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)
|
||||
}
|
||||
|
|
|
@ -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<String>)>, 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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
240
crates/nu_plugin_formats/src/from/plist.rs
Normal file
240
crates/nu_plugin_formats/src/from/plist.rs
Normal file
|
@ -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<Example> {
|
||||
vec![Example {
|
||||
example: r#"'<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>a</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
</plist>' | 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<NuValue, LabeledError> {
|
||||
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<String>, span: Span) -> LabeledError {
|
||||
LabeledError::new("Could not load plist").with_label(msg, span)
|
||||
}
|
||||
|
||||
fn convert_plist_value(plist_val: &PlistValue, span: Span) -> Result<NuValue, LabeledError> {
|
||||
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<NuValue, LabeledError> {
|
||||
let cols: Vec<String> = dict.keys().cloned().collect();
|
||||
let vals: Result<Vec<NuValue>, 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<Vec<NuValue>, LabeledError> {
|
||||
plist_array
|
||||
.iter()
|
||||
.map(|v| convert_plist_value(v, span))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn convert_date(plist_date: &PlistDate) -> DateTime<FixedOffset> {
|
||||
// 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<Utc> = 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)
|
||||
}
|
||||
}
|
|
@ -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<String>)>, 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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {})
|
||||
}
|
||||
|
|
1
crates/nu_plugin_formats/src/to/mod.rs
Normal file
1
crates/nu_plugin_formats/src/to/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub(crate) mod plist;
|
113
crates/nu_plugin_formats/src/to/plist.rs
Normal file
113
crates/nu_plugin_formats/src/to/plist.rs
Normal file
|
@ -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<Example> {
|
||||
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<NuValue, LabeledError> {
|
||||
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<PlistValue, LabeledError> {
|
||||
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::<Integer>::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::<Result<_, _>>()?,
|
||||
)),
|
||||
NuValue::Date { val, .. } => Ok(PlistValue::Date(SystemTime::from(val.to_owned()).into())),
|
||||
NuValue::Filesize { val, .. } => Ok(PlistValue::Integer(Into::<Integer>::into(*val))),
|
||||
_ => Err(build_label_error(
|
||||
format!("{:?} is not convertible", nu_val),
|
||||
span,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_nu_dict(record: &Record) -> Result<PlistValue, LabeledError> {
|
||||
Ok(PlistValue::Dictionary(
|
||||
record
|
||||
.iter()
|
||||
.map(|(k, v)| convert_nu_value(v).map(|v| (k.to_owned(), v)))
|
||||
.collect::<Result<_, _>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
#[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)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue