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:
Jack Wright
2024-08-05 14:07:15 -07:00
committed by GitHub
parent 9172b22985
commit 2f44801414
12 changed files with 418 additions and 32 deletions

View File

@ -0,0 +1 @@
pub(crate) mod plist;

View 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)
}
}