From 06f64abb189df20c582e7caf8b7391b67d78d444 Mon Sep 17 00:00:00 2001 From: trivernis Date: Mon, 3 Oct 2022 15:20:52 +0200 Subject: [PATCH] Add rusty-value serialization support for nu values --- Cargo.toml | 1 + src/into_value.rs | 196 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 4 + src/state_builder/mod.rs | 5 +- src/utils.rs | 86 +---------------- tests/test_value.rs | 50 ++++++++++ 6 files changed, 253 insertions(+), 89 deletions(-) create mode 100644 src/into_value.rs create mode 100644 tests/test_value.rs diff --git a/Cargo.toml b/Cargo.toml index a0d9270..03f8e2f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ nu-engine = "0.69.1" nu-parser = "0.69.1" nu-protocol = "0.69.1" paste = "1.0.9" +rusty-value = { version = "0.2.0", features = ["derive"] } thiserror = "1.0.37" diff --git a/src/into_value.rs b/src/into_value.rs new file mode 100644 index 0000000..61f2e9e --- /dev/null +++ b/src/into_value.rs @@ -0,0 +1,196 @@ +use nu_protocol::{Span, Value}; +use rusty_value::{Fields, HashableValue, RustyValue}; + +use crate::utils::SpanEmpty; + +pub struct RawValue(Value); + +/// Converts the given type into a value +/// This trait is implemented for all types that +/// Implement the RustyValue trait +pub trait IntoValue { + fn into_value(self) -> Value; +} + +impl IntoValue for RawValue { + fn into_value(self) -> Value { + self.0 + } +} + +/// Helper trait to avoid conflicts +pub(crate) trait RustyIntoValue { + fn into_value(self) -> Value; +} + +pub(crate) trait HashableIntoString { + fn into_string(self) -> String; +} + +impl HashableIntoString for HashableValue { + fn into_string(self) -> String { + match self { + HashableValue::Primitive(p) => p.to_string(), + HashableValue::List(l) => l + .into_iter() + .map(|v| v.into_string()) + .collect::>() + .join(","), + } + } +} + +impl RustyIntoValue for Vec { + fn into_value(self) -> Value { + Value::List { + vals: self, + span: Span::empty(), + } + } +} + +impl RustyIntoValue for rusty_value::Value { + fn into_value(self) -> Value { + match self { + rusty_value::Value::Primitive(p) => p.into_value(), + rusty_value::Value::Struct(s) => { + if let Fields::Unit = &s.fields { + Value::String { + val: s.name, + span: Span::empty(), + } + } else { + s.fields.into_value() + } + } + rusty_value::Value::Enum(e) => { + if let Fields::Unit = &e.fields { + Value::String { + val: e.variant, + span: Span::empty(), + } + } else { + e.fields.into_value() + } + } + rusty_value::Value::Map(map) => { + let mut cols = Vec::new(); + let mut vals = Vec::new(); + + for (key, val) in map { + cols.push(key.into_string()); + vals.push(val.into_value()); + } + Value::Record { + cols, + vals, + span: Span::empty(), + } + } + rusty_value::Value::List(l) => { + let vals = l.into_iter().map(|e| e.into_value()).collect(); + + Value::List { + vals, + span: Span::empty(), + } + } + } + } +} + +impl RustyIntoValue for rusty_value::Primitive { + fn into_value(self) -> Value { + match self { + rusty_value::Primitive::Integer(i) => i.into_value(), + rusty_value::Primitive::Float(f) => f.into_value(), + rusty_value::Primitive::String(val) => Value::String { + val, + span: Span::empty(), + }, + rusty_value::Primitive::Char(val) => Value::String { + val: val.to_string(), + span: Span::empty(), + }, + rusty_value::Primitive::Bool(val) => Value::Bool { + val, + span: Span::empty(), + }, + } + } +} + +impl RustyIntoValue for rusty_value::Fields { + fn into_value(self) -> Value { + match self { + rusty_value::Fields::Named(named) => { + let mut cols = Vec::with_capacity(named.len()); + let mut vals = Vec::with_capacity(named.len()); + + for (k, v) in named { + cols.push(k); + vals.push(v.into_value()); + } + Value::Record { + cols, + vals, + span: Span::empty(), + } + } + rusty_value::Fields::Unnamed(unnamed) => { + let vals = unnamed.into_iter().map(|v| v.into_value()).collect(); + + Value::List { + vals, + span: Span::empty(), + } + } + rusty_value::Fields::Unit => Value::Nothing { + span: Span::empty(), + }, + } + } +} + +impl RustyIntoValue for rusty_value::Integer { + fn into_value(self) -> Value { + let val = match self { + rusty_value::Integer::USize(i) => i as i64, + rusty_value::Integer::ISize(i) => i as i64, + rusty_value::Integer::U8(i) => i as i64, + rusty_value::Integer::I8(i) => i as i64, + rusty_value::Integer::U16(i) => i as i64, + rusty_value::Integer::I16(i) => i as i64, + rusty_value::Integer::U32(i) => i as i64, + rusty_value::Integer::I32(i) => i as i64, + rusty_value::Integer::U64(i) => i as i64, + rusty_value::Integer::I64(i) => i, + rusty_value::Integer::U128(i) => i as i64, + rusty_value::Integer::I128(i) => i as i64, + }; + Value::Int { + val, + span: Span::empty(), + } + } +} + +impl RustyIntoValue for rusty_value::Float { + #[inline] + fn into_value(self) -> Value { + let val = match self { + rusty_value::Float::F32(f) => f as f64, + rusty_value::Float::F64(f) => f, + }; + Value::Float { + val, + span: Span::empty(), + } + } +} + +impl IntoValue for R { + fn into_value(self) -> Value { + self.into_rusty_value().into_value() + } +} diff --git a/src/lib.rs b/src/lib.rs index d715116..45e5d5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ pub(crate) mod error; +pub(crate) mod into_value; pub mod state_builder; pub(crate) mod utils; +pub use into_value::*; +pub use rusty_value; + pub type Error = error::CrateError; diff --git a/src/state_builder/mod.rs b/src/state_builder/mod.rs index 61eeb00..91d4994 100644 --- a/src/state_builder/mod.rs +++ b/src/state_builder/mod.rs @@ -9,10 +9,7 @@ mod bindings; mod command_group_config; pub use command_group_config::CommandGroupConfig; -use crate::{ - error::CrateResult, - utils::{IntoValue, SpanEmpty}, -}; +use crate::{error::CrateResult, into_value::IntoValue, utils::SpanEmpty}; /// Builder to create a new nu engine state pub struct StateBuilder { diff --git a/src/utils.rs b/src/utils.rs index 1dc9c9c..364866e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,4 @@ -use std::collections::HashMap; - -use nu_protocol::{Span, Value}; +use nu_protocol::Span; pub trait SpanEmpty { fn empty() -> Self; @@ -11,85 +9,3 @@ impl SpanEmpty for Span { Span::new(0, 0) } } - -pub trait IntoValue { - fn into_value(self) -> Value; -} - -impl IntoValue for Value { - fn into_value(self) -> Value { - self - } -} - -impl IntoValue for String { - fn into_value(self) -> Value { - Value::String { - val: self, - span: Span::empty(), - } - } -} - -impl IntoValue for i64 { - fn into_value(self) -> Value { - Value::Int { - val: self, - span: Span::empty(), - } - } -} - -impl IntoValue for bool { - fn into_value(self) -> Value { - Value::Bool { - val: self, - span: Span::empty(), - } - } -} - -impl IntoValue for f64 { - fn into_value(self) -> Value { - Value::Float { - val: self, - span: Span::empty(), - } - } -} - -impl IntoValue for char { - fn into_value(self) -> Value { - Value::String { - val: self.to_string(), - span: Span::empty(), - } - } -} - -impl<'a> IntoValue for &'a str { - fn into_value(self) -> Value { - self.to_string().into_value() - } -} - -impl IntoValue for HashMap { - fn into_value(self) -> Value { - let (cols, vals) = self.into_iter().unzip(); - - Value::Record { - cols, - vals, - span: Span::empty(), - } - } -} - -impl IntoValue for Vec { - fn into_value(self) -> Value { - Value::List { - vals: self, - span: Span::empty(), - } - } -} diff --git a/tests/test_value.rs b/tests/test_value.rs new file mode 100644 index 0000000..8d6664d --- /dev/null +++ b/tests/test_value.rs @@ -0,0 +1,50 @@ +use embed_nu::IntoValue; +use nu_protocol::Value; +use rusty_value::RustyValue; +use std::mem; + +#[derive(RustyValue, Debug, Clone)] +pub struct TestStruct { + foo: String, + bar: Vec, + baz: TestEnum, +} + +#[derive(RustyValue, Debug, Clone)] +pub enum TestEnum { + Empty, + Unnamed(String), + Named { foo: usize, bar: Box }, +} + +impl TestStruct { + pub fn new_test() -> Self { + Self { + foo: "Hello".to_string(), + bar: vec!["One".to_string(), "Two".to_string()], + baz: TestEnum::Named { + foo: 12, + bar: Box::new(TestStruct { + foo: "World".to_string(), + bar: vec![], + baz: TestEnum::Empty, + }), + }, + } + } +} + +#[test] +fn it_creates_values_from_structs() { + let test_val = TestStruct::new_test(); + dbg!(mem::size_of::()); + dbg!(&test_val); + let rusty_val = test_val.clone().into_rusty_value(); + dbg!(mem::size_of::()); + dbg!(&rusty_val); + let val = test_val.into_value(); + dbg!(mem::size_of::()); + dbg!(&val); + + assert!(val.as_record().is_ok()) +}