From 303fa9ed3ea496bc3d33f3850e0ed809a8b07e71 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Thu, 30 Dec 2021 17:05:00 -0800 Subject: [PATCH 1/4] [WIP] Add YAML Formatting Support Yaml formatting includes: - Emitting numbers in the various bases supported by Yaml. - Emitting strings as block scalars. - Emitting comments along with struct fields to improve human readability. These emitter enhancments are facilitated by a derivable YamlFormat trait which allow the schema (struct) author to declare the desired formatting and comments on a field-by-field basis. Signed-off-by: Chris Frantz --- Cargo.toml | 6 +- src/error.rs | 8 + src/lib.rs | 1 + src/ser.rs | 377 ++++++++++++++++++++++++-------- src/value/mod.rs | 7 +- src/yamlformat.rs | 109 +++++++++ tests/test_format.rs | 136 ++++++++++++ yamlformat_derive/Cargo.toml | 18 ++ yamlformat_derive/src/ast.rs | 124 +++++++++++ yamlformat_derive/src/attr.rs | 103 +++++++++ yamlformat_derive/src/expand.rs | 116 ++++++++++ yamlformat_derive/src/lib.rs | 16 ++ 12 files changed, 925 insertions(+), 96 deletions(-) create mode 100644 src/yamlformat.rs create mode 100644 tests/test_format.rs create mode 100644 yamlformat_derive/Cargo.toml create mode 100644 yamlformat_derive/src/ast.rs create mode 100644 yamlformat_derive/src/attr.rs create mode 100644 yamlformat_derive/src/expand.rs create mode 100644 yamlformat_derive/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 01772bbd..019c58d1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,11 @@ keywords = ["yaml", "serde"] ryu = "1.0" indexmap = "1.5" serde = "1.0.69" -yaml-rust = "0.4.5" +#yaml-rust = "0.4.5" +yaml-rust = {path = "../yaml-rust"} +yamlformat_derive = {path = "yamlformat_derive"} +inventory = "0.2" +once_cell = "1.9" [dev-dependencies] anyhow = "1.0" diff --git a/src/error.rs b/src/error.rs index 72e719e5..ba0e43dc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -25,6 +25,7 @@ pub enum ErrorImpl { Io(io::Error), Utf8(str::Utf8Error), FromUtf8(string::FromUtf8Error), + Format(String), EndOfStream, MoreThanOneDocument, @@ -130,6 +131,10 @@ pub(crate) fn string_utf8(err: string::FromUtf8Error) -> Error { Error(Box::new(ErrorImpl::FromUtf8(err))) } +pub(crate) fn format(err: String) -> Error { + Error(Box::new(ErrorImpl::Format(err))) +} + pub(crate) fn recursion_limit_exceeded() -> Error { Error(Box::new(ErrorImpl::RecursionLimitExceeded)) } @@ -205,6 +210,7 @@ impl ErrorImpl { ErrorImpl::Io(err) => error::Error::description(err), ErrorImpl::Utf8(err) => error::Error::description(err), ErrorImpl::FromUtf8(err) => error::Error::description(err), + ErrorImpl::Format(err) => err.as_str(), ErrorImpl::EndOfStream => "EOF while parsing a value", ErrorImpl::MoreThanOneDocument => { "deserializing from YAML containing more than one document is not supported" @@ -241,6 +247,7 @@ impl ErrorImpl { ErrorImpl::Io(err) => Display::fmt(err, f), ErrorImpl::Utf8(err) => Display::fmt(err, f), ErrorImpl::FromUtf8(err) => Display::fmt(err, f), + ErrorImpl::Format(err) => Display::fmt(err, f), ErrorImpl::EndOfStream => f.write_str("EOF while parsing a value"), ErrorImpl::MoreThanOneDocument => f.write_str( "deserializing from YAML containing more than one document is not supported", @@ -258,6 +265,7 @@ impl ErrorImpl { ErrorImpl::Io(io) => f.debug_tuple("Io").field(io).finish(), ErrorImpl::Utf8(utf8) => f.debug_tuple("Utf8").field(utf8).finish(), ErrorImpl::FromUtf8(from_utf8) => f.debug_tuple("FromUtf8").field(from_utf8).finish(), + ErrorImpl::Format(s) => f.debug_tuple("Format").field(s).finish(), ErrorImpl::EndOfStream => f.debug_tuple("EndOfStream").finish(), ErrorImpl::MoreThanOneDocument => f.debug_tuple("MoreThanOneDocument").finish(), ErrorImpl::RecursionLimitExceeded => f.debug_tuple("RecursionLimitExceeded").finish(), diff --git a/src/lib.rs b/src/lib.rs index 58d64818..4fcda1ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,3 +119,4 @@ mod number; mod path; mod ser; mod value; +pub mod yamlformat; diff --git a/src/ser.rs b/src/ser.rs index b584f35a..e5a136d0 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -2,6 +2,7 @@ //! //! This module provides YAML serialization with the type `Serializer`. +use crate::yamlformat::{Format, MemberId, YamlFormat, YamlFormatType}; use crate::{error, Error, Result}; use serde::ser; use std::{fmt, io, num, str}; @@ -31,18 +32,29 @@ use yaml_rust::{yaml, Yaml, YamlEmitter}; /// Ok(()) /// } /// ``` -pub struct Serializer { +pub struct Serializer<'f, W> { + yamlformat: Option<&'f dyn YamlFormat>, documents: usize, writer: W, } -impl Serializer +impl<'f, W> Serializer<'f, W> where W: io::Write, { /// Creates a new YAML serializer. pub fn new(writer: W) -> Self { Serializer { + yamlformat: None, + documents: 0, + writer, + } + } + + /// Creates a new YAML serializer with formatting info. + pub fn new_with_format(writer: W, yamlformat: Option<&'f dyn YamlFormat>) -> Self { + Serializer { + yamlformat: yamlformat, documents: 0, writer, } @@ -69,108 +81,108 @@ where } } -impl<'a, W> ser::Serializer for &'a mut Serializer +impl<'a, 'f, W> ser::Serializer for &'a mut Serializer<'f, W> where W: io::Write, { type Ok = (); type Error = Error; - type SerializeSeq = ThenWrite<'a, W, SerializeArray>; - type SerializeTuple = ThenWrite<'a, W, SerializeArray>; - type SerializeTupleStruct = ThenWrite<'a, W, SerializeArray>; - type SerializeTupleVariant = ThenWrite<'a, W, SerializeTupleVariant>; - type SerializeMap = ThenWrite<'a, W, SerializeMap>; - type SerializeStruct = ThenWrite<'a, W, SerializeStruct>; - type SerializeStructVariant = ThenWrite<'a, W, SerializeStructVariant>; + type SerializeSeq = ThenWrite<'a, 'f, W, SerializeArray>; + type SerializeTuple = ThenWrite<'a, 'f, W, SerializeArray>; + type SerializeTupleStruct = ThenWrite<'a, 'f, W, SerializeArray>; + type SerializeTupleVariant = ThenWrite<'a, 'f, W, SerializeTupleVariant>; + type SerializeMap = ThenWrite<'a, 'f, W, SerializeMap>; + type SerializeStruct = ThenWrite<'a, 'f, W, SerializeStruct<'f>>; + type SerializeStructVariant = ThenWrite<'a, 'f, W, SerializeStructVariant>; fn serialize_bool(self, v: bool) -> Result<()> { - let doc = SerializerToYaml.serialize_bool(v)?; + let doc = SerializerToYaml::default().serialize_bool(v)?; self.write(doc) } fn serialize_i8(self, v: i8) -> Result<()> { - let doc = SerializerToYaml.serialize_i8(v)?; + let doc = SerializerToYaml::default().serialize_i8(v)?; self.write(doc) } fn serialize_i16(self, v: i16) -> Result<()> { - let doc = SerializerToYaml.serialize_i16(v)?; + let doc = SerializerToYaml::default().serialize_i16(v)?; self.write(doc) } fn serialize_i32(self, v: i32) -> Result<()> { - let doc = SerializerToYaml.serialize_i32(v)?; + let doc = SerializerToYaml::default().serialize_i32(v)?; self.write(doc) } fn serialize_i64(self, v: i64) -> Result<()> { - let doc = SerializerToYaml.serialize_i64(v)?; + let doc = SerializerToYaml::default().serialize_i64(v)?; self.write(doc) } fn serialize_i128(self, v: i128) -> Result<()> { - let doc = SerializerToYaml.serialize_i128(v)?; + let doc = SerializerToYaml::default().serialize_i128(v)?; self.write(doc) } fn serialize_u8(self, v: u8) -> Result<()> { - let doc = SerializerToYaml.serialize_u8(v)?; + let doc = SerializerToYaml::default().serialize_u8(v)?; self.write(doc) } fn serialize_u16(self, v: u16) -> Result<()> { - let doc = SerializerToYaml.serialize_u16(v)?; + let doc = SerializerToYaml::default().serialize_u16(v)?; self.write(doc) } fn serialize_u32(self, v: u32) -> Result<()> { - let doc = SerializerToYaml.serialize_u32(v)?; + let doc = SerializerToYaml::default().serialize_u32(v)?; self.write(doc) } fn serialize_u64(self, v: u64) -> Result<()> { - let doc = SerializerToYaml.serialize_u64(v)?; + let doc = SerializerToYaml::default().serialize_u64(v)?; self.write(doc) } fn serialize_u128(self, v: u128) -> Result<()> { - let doc = SerializerToYaml.serialize_u128(v)?; + let doc = SerializerToYaml::default().serialize_u128(v)?; self.write(doc) } fn serialize_f32(self, v: f32) -> Result<()> { - let doc = SerializerToYaml.serialize_f32(v)?; + let doc = SerializerToYaml::default().serialize_f32(v)?; self.write(doc) } fn serialize_f64(self, v: f64) -> Result<()> { - let doc = SerializerToYaml.serialize_f64(v)?; + let doc = SerializerToYaml::default().serialize_f64(v)?; self.write(doc) } fn serialize_char(self, value: char) -> Result<()> { - let doc = SerializerToYaml.serialize_char(value)?; + let doc = SerializerToYaml::default().serialize_char(value)?; self.write(doc) } fn serialize_str(self, value: &str) -> Result<()> { - let doc = SerializerToYaml.serialize_str(value)?; + let doc = SerializerToYaml::default().serialize_str(value)?; self.write(doc) } fn serialize_bytes(self, value: &[u8]) -> Result<()> { - let doc = SerializerToYaml.serialize_bytes(value)?; + let doc = SerializerToYaml::default().serialize_bytes(value)?; self.write(doc) } fn serialize_unit(self) -> Result<()> { - let doc = SerializerToYaml.serialize_unit()?; + let doc = SerializerToYaml::default().serialize_unit()?; self.write(doc) } fn serialize_unit_struct(self, name: &'static str) -> Result<()> { - let doc = SerializerToYaml.serialize_unit_struct(name)?; + let doc = SerializerToYaml::default().serialize_unit_struct(name)?; self.write(doc) } @@ -180,7 +192,8 @@ where variant_index: u32, variant: &'static str, ) -> Result<()> { - let doc = SerializerToYaml.serialize_unit_variant(name, variant_index, variant)?; + let doc = + SerializerToYaml::default().serialize_unit_variant(name, variant_index, variant)?; self.write(doc) } @@ -188,7 +201,7 @@ where where T: ser::Serialize, { - let doc = SerializerToYaml.serialize_newtype_struct(name, value)?; + let doc = SerializerToYaml::default().serialize_newtype_struct(name, value)?; self.write(doc) } @@ -202,13 +215,17 @@ where where T: ser::Serialize, { - let doc = - SerializerToYaml.serialize_newtype_variant(name, variant_index, variant, value)?; + let doc = SerializerToYaml::default().serialize_newtype_variant( + name, + variant_index, + variant, + value, + )?; self.write(doc) } fn serialize_none(self) -> Result<()> { - let doc = SerializerToYaml.serialize_none()?; + let doc = SerializerToYaml::default().serialize_none()?; self.write(doc) } @@ -216,17 +233,17 @@ where where V: ser::Serialize, { - let doc = SerializerToYaml.serialize_some(value)?; + let doc = SerializerToYaml::default().serialize_some(value)?; self.write(doc) } fn serialize_seq(self, len: Option) -> Result { - let delegate = SerializerToYaml.serialize_seq(len)?; + let delegate = SerializerToYaml::default().serialize_seq(len)?; Ok(ThenWrite::new(self, delegate)) } fn serialize_tuple(self, len: usize) -> Result { - let delegate = SerializerToYaml.serialize_tuple(len)?; + let delegate = SerializerToYaml::default().serialize_tuple(len)?; Ok(ThenWrite::new(self, delegate)) } @@ -235,7 +252,7 @@ where name: &'static str, len: usize, ) -> Result { - let delegate = SerializerToYaml.serialize_tuple_struct(name, len)?; + let delegate = SerializerToYaml::default().serialize_tuple_struct(name, len)?; Ok(ThenWrite::new(self, delegate)) } @@ -246,17 +263,18 @@ where variant: &'static str, len: usize, ) -> Result { - let delegate = SerializerToYaml.serialize_tuple_variant(enm, idx, variant, len)?; + let delegate = + SerializerToYaml::default().serialize_tuple_variant(enm, idx, variant, len)?; Ok(ThenWrite::new(self, delegate)) } fn serialize_map(self, len: Option) -> Result { - let delegate = SerializerToYaml.serialize_map(len)?; + let delegate = SerializerToYaml::default().serialize_map(len)?; Ok(ThenWrite::new(self, delegate)) } fn serialize_struct(self, name: &'static str, len: usize) -> Result { - let delegate = SerializerToYaml.serialize_struct(name, len)?; + let delegate = SerializerToYaml::default().serialize_struct(name, len)?; Ok(ThenWrite::new(self, delegate)) } @@ -267,26 +285,29 @@ where variant: &'static str, len: usize, ) -> Result { - let delegate = SerializerToYaml.serialize_struct_variant(enm, idx, variant, len)?; + let delegate = + SerializerToYaml::default().serialize_struct_variant(enm, idx, variant, len)?; Ok(ThenWrite::new(self, delegate)) } } -pub struct ThenWrite<'a, W, D> { - serializer: &'a mut Serializer, +pub struct ThenWrite<'a, 'f, W, D> { + serializer: &'a mut Serializer<'f, W>, delegate: D, + comment: Option, } -impl<'a, W, D> ThenWrite<'a, W, D> { - fn new(serializer: &'a mut Serializer, delegate: D) -> Self { +impl<'a, 'f, W, D> ThenWrite<'a, 'f, W, D> { + fn new(serializer: &'a mut Serializer<'f, W>, delegate: D) -> Self { ThenWrite { serializer, delegate, + comment: None, } } } -impl<'a, W> ser::SerializeSeq for ThenWrite<'a, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeSeq for ThenWrite<'a, 'f, W, SerializeArray> where W: io::Write, { @@ -306,7 +327,7 @@ where } } -impl<'a, W> ser::SerializeTuple for ThenWrite<'a, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeTuple for ThenWrite<'a, 'f, W, SerializeArray> where W: io::Write, { @@ -326,7 +347,7 @@ where } } -impl<'a, W> ser::SerializeTupleStruct for ThenWrite<'a, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeTupleStruct for ThenWrite<'a, 'f, W, SerializeArray> where W: io::Write, { @@ -346,7 +367,7 @@ where } } -impl<'a, W> ser::SerializeTupleVariant for ThenWrite<'a, W, SerializeTupleVariant> +impl<'a, 'f, W> ser::SerializeTupleVariant for ThenWrite<'a, 'f, W, SerializeTupleVariant> where W: io::Write, { @@ -366,7 +387,7 @@ where } } -impl<'a, W> ser::SerializeMap for ThenWrite<'a, W, SerializeMap> +impl<'a, 'f, W> ser::SerializeMap for ThenWrite<'a, 'f, W, SerializeMap> where W: io::Write, { @@ -401,7 +422,7 @@ where } } -impl<'a, W> ser::SerializeStruct for ThenWrite<'a, W, SerializeStruct> +impl<'a, 'f, W> ser::SerializeStruct for ThenWrite<'a, 'f, W, SerializeStruct<'f>> where W: io::Write, { @@ -412,6 +433,20 @@ where where V: ser::Serialize, { + let name = MemberId::Name(key); + //println!("serialize_field: {:?}", name); + + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(&name)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(&name)) + .flatten(); + self.delegate.serialize_field(key, value) } @@ -421,7 +456,7 @@ where } } -impl<'a, W> ser::SerializeStructVariant for ThenWrite<'a, W, SerializeStructVariant> +impl<'a, 'f, W> ser::SerializeStructVariant for ThenWrite<'a, 'f, W, SerializeStructVariant> where W: io::Write, { @@ -441,9 +476,33 @@ where } } -pub struct SerializerToYaml; +pub struct SerializerToYaml<'f> { + yamlformat: Option<&'f dyn YamlFormat>, + format: Option, + comment: Option, +} + +impl<'f> SerializerToYaml<'f> { + pub fn new(yf: Option<&'f dyn YamlFormat>, format: Option) -> Self { + SerializerToYaml { + yamlformat: yf, + format: format, + comment: None, + } + } +} + +impl<'f> Default for SerializerToYaml<'f> { + fn default() -> Self { + SerializerToYaml { + yamlformat: None, + format: None, + comment: None, + } + } +} -impl ser::Serializer for SerializerToYaml { +impl<'f> ser::Serializer for SerializerToYaml<'f> { type Ok = Yaml; type Error = Error; @@ -452,7 +511,7 @@ impl ser::Serializer for SerializerToYaml { type SerializeTupleStruct = SerializeArray; type SerializeTupleVariant = SerializeTupleVariant; type SerializeMap = SerializeMap; - type SerializeStruct = SerializeStruct; + type SerializeStruct = SerializeStruct<'f>; type SerializeStructVariant = SerializeStructVariant; fn serialize_bool(self, v: bool) -> Result { @@ -460,56 +519,142 @@ impl ser::Serializer for SerializerToYaml { } fn serialize_i8(self, v: i8) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#08b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#02x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#03o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type i8", + self.format + ))), + } } fn serialize_i16(self, v: i16) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#016b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#06o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type i16", + self.format + ))), + } } fn serialize_i32(self, v: i32) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#032b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#08x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#011o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type i32", + self.format + ))), + } } fn serialize_i64(self, v: i64) -> Result { - Ok(Yaml::Integer(v)) + match self.format { + None => Ok(Yaml::Integer(v)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#064b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#016x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#022o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type i64", + self.format + ))), + } } - #[allow(clippy::cast_possible_truncation)] fn serialize_i128(self, v: i128) -> Result { - if v <= i64::max_value() as i128 && v >= i64::min_value() as i128 { - self.serialize_i64(v as i64) - } else { - Ok(Yaml::Real(v.to_string())) + match self.format { + None => Ok(Yaml::Real(v.to_string())), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0128b}", v))), + Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#032x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#043o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type i128", + self.format + ))), } } fn serialize_u8(self, v: u8) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#08b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#02x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#03o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type u8", + self.format + ))), + } } fn serialize_u16(self, v: u16) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#016b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#06o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type u16", + self.format + ))), + } } fn serialize_u32(self, v: u32) -> Result { - self.serialize_i64(v as i64) + match self.format { + None => Ok(Yaml::Integer(v as i64)), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#032b}", v))), + Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#08x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#011o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type u32", + self.format + ))), + } } fn serialize_u64(self, v: u64) -> Result { - if v <= i64::max_value() as u64 { - self.serialize_i64(v as i64) - } else { - Ok(Yaml::Real(v.to_string())) + match self.format { + None => Ok(Yaml::Real(v.to_string())), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#064b}", v))), + Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#016x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#022o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type u64", + self.format + ))), } } - #[allow(clippy::cast_possible_truncation)] fn serialize_u128(self, v: u128) -> Result { - if v <= i64::max_value() as u128 { - self.serialize_i64(v as i64) - } else { - Ok(Yaml::Real(v.to_string())) + match self.format { + None => Ok(Yaml::Real(v.to_string())), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0128b}", v))), + Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#032x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#043o}", v))), + _ => Err(error::format(format!( + "Format {:?} not supported for type u128", + self.format + ))), } } @@ -536,7 +681,14 @@ impl ser::Serializer for SerializerToYaml { } fn serialize_str(self, value: &str) -> Result { - Ok(Yaml::String(value.to_owned())) + match self.format { + None => Ok(Yaml::String(value.to_owned())), + Some(Format::Block) => Ok(Yaml::BlockScalar(value.to_owned())), + _ => Err(error::format(format!( + "Format {:?} not supported for type str", + self.format + ))), + } } fn serialize_bytes(self, value: &[u8]) -> Result { @@ -578,7 +730,10 @@ impl ser::Serializer for SerializerToYaml { where T: ser::Serialize, { - Ok(singleton_hash(to_yaml(variant)?, to_yaml(value)?)) + Ok(singleton_hash( + to_yaml(variant, None, None)?, + to_yaml(value, None, None)?, + )) } fn serialize_none(self) -> Result { @@ -628,8 +783,9 @@ impl ser::Serializer for SerializerToYaml { }) } - fn serialize_struct(self, _name: &'static str, _len: usize) -> Result { + fn serialize_struct(self, _name: &'static str, _len: usize) -> Result> { Ok(SerializeStruct { + serializer: self, hash: yaml::Hash::new(), }) } @@ -666,7 +822,8 @@ pub struct SerializeMap { } #[doc(hidden)] -pub struct SerializeStruct { +pub struct SerializeStruct<'f> { + serializer: SerializerToYaml<'f>, hash: yaml::Hash, } @@ -684,7 +841,7 @@ impl ser::SerializeSeq for SerializeArray { where T: ser::Serialize, { - self.array.push(to_yaml(elem)?); + self.array.push(to_yaml(elem, None, None)?); Ok(()) } @@ -733,12 +890,15 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant { where V: ser::Serialize, { - self.array.push(to_yaml(v)?); + self.array.push(to_yaml(v, None, None)?); Ok(()) } fn end(self) -> Result { - Ok(singleton_hash(to_yaml(self.name)?, Yaml::Array(self.array))) + Ok(singleton_hash( + to_yaml(self.name, None, None)?, + Yaml::Array(self.array), + )) } } @@ -750,7 +910,7 @@ impl ser::SerializeMap for SerializeMap { where T: ser::Serialize, { - self.next_key = Some(to_yaml(key)?); + self.next_key = Some(to_yaml(key, None, None)?); Ok(()) } @@ -759,7 +919,7 @@ impl ser::SerializeMap for SerializeMap { T: ser::Serialize, { match self.next_key.take() { - Some(key) => self.hash.insert(key, to_yaml(value)?), + Some(key) => self.hash.insert(key, to_yaml(value, None, None)?), None => panic!("serialize_value called before serialize_key"), }; Ok(()) @@ -770,7 +930,8 @@ impl ser::SerializeMap for SerializeMap { K: ser::Serialize, V: ser::Serialize, { - self.hash.insert(to_yaml(key)?, to_yaml(value)?); + self.hash + .insert(to_yaml(key, None, None)?, to_yaml(value, None, None)?); Ok(()) } @@ -779,7 +940,7 @@ impl ser::SerializeMap for SerializeMap { } } -impl ser::SerializeStruct for SerializeStruct { +impl<'f> ser::SerializeStruct for SerializeStruct<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -787,7 +948,24 @@ impl ser::SerializeStruct for SerializeStruct { where V: ser::Serialize, { - self.hash.insert(to_yaml(key)?, to_yaml(value)?); + let name = MemberId::Name(key); + //println!("serialize_field name={:?} value={:p}", name, value); + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(&name)) + .flatten() + }); + + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(&name)) + .flatten() + }); + + self.hash + .insert(to_yaml(key, None, comment)?, to_yaml(value, format, None)?); Ok(()) } @@ -804,12 +982,16 @@ impl ser::SerializeStructVariant for SerializeStructVariant { where V: ser::Serialize, { - self.hash.insert(to_yaml(field)?, to_yaml(v)?); + self.hash + .insert(to_yaml(field, None, None)?, to_yaml(v, None, None)?); Ok(()) } fn end(self) -> Result { - Ok(singleton_hash(to_yaml(self.name)?, Yaml::Hash(self.hash))) + Ok(singleton_hash( + to_yaml(self.name, None, None)?, + Yaml::Hash(self.hash), + )) } } @@ -822,7 +1004,8 @@ where W: io::Write, T: ser::Serialize, { - value.serialize(&mut Serializer::new(writer)) + let yf = YamlFormatType::get(value); + value.serialize(&mut Serializer::new_with_format(writer, yf)) } /// Serialize the given data structure as a YAML byte vector. @@ -867,11 +1050,17 @@ where } } -fn to_yaml(elem: T) -> Result +fn to_yaml(elem: &T, format: Option, comment: Option) -> Result where - T: ser::Serialize, + T: ser::Serialize + ?Sized, { - elem.serialize(SerializerToYaml) + let yf = YamlFormatType::get(elem); + let yaml = elem.serialize(SerializerToYaml::new(yf, format))?; + if let Some(c) = comment { + Ok(Yaml::DocFragment(vec![Yaml::Comment(c), yaml])) + } else { + Ok(yaml) + } } fn singleton_hash(k: Yaml, v: Yaml) -> Yaml { diff --git a/src/value/mod.rs b/src/value/mod.rs index a71091bf..079aad0d 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -89,7 +89,9 @@ pub fn to_value(value: T) -> Result where T: Serialize, { - value.serialize(SerializerToYaml).map(yaml_to_value) + value + .serialize(SerializerToYaml::default()) + .map(yaml_to_value) } /// Interpret a `serde_yaml::Value` as an instance of type `T`. @@ -621,6 +623,9 @@ fn yaml_to_value(yaml: Yaml) -> Value { Yaml::Alias(_) => panic!("alias unsupported"), Yaml::Null => Value::Null, Yaml::BadValue => panic!("bad value"), + Yaml::BlockScalar(_) => panic!("block scalar unexpected"), + Yaml::DocFragment(_) => panic!("doc fragment unexpected"), + Yaml::Comment(_) => panic!("comment unexpected"), } } diff --git a/src/yamlformat.rs b/src/yamlformat.rs new file mode 100644 index 00000000..cc2add63 --- /dev/null +++ b/src/yamlformat.rs @@ -0,0 +1,109 @@ +//! yaml formatting. + +use once_cell::sync::OnceCell; +use serde::Serialize; +use std::collections::HashMap; +use std::sync::Mutex; +pub use yamlformat_derive::*; + +/// `MemberId` identifies struct fields and enum variants. +#[derive(Debug)] +pub enum MemberId<'a> { + /// `Name` identifies a named field. + Name(&'a str), + /// `Index` identifies an tuple field. + Index(u32), +} + +/// `Format` describes how a field is to be formatted. +#[derive(Debug, PartialEq)] +pub enum Format { + /// Format a string field using block formatting. + Block, + /// Format an integer field as binary. + Binary, + /// Format an integer field as decimal. + Decimal, + /// Format an integer field as Hexadecimal. + Hex, + /// Format an integer field as Octal. + Octal, +} + +/// YamlFormat is used by the serializer to choose the desired formatting. +pub trait YamlFormat { + /// Returns the format for a given field. + fn format(&self, field: &MemberId) -> Option; + /// Returns the comment associated with a given field. + fn comment(&self, field: &MemberId) -> Option; +} + +#[doc(hidden)] +pub type CastFn = unsafe fn(*const ()) -> &'static dyn YamlFormat; + +#[doc(hidden)] +pub type IdFn = fn() -> usize; + +#[doc(hidden)] +pub struct YamlFormatType { + pub id: IdFn, + pub reconstitute: CastFn, +} +inventory::collect!(YamlFormatType); + +impl YamlFormatType { + #[doc(hidden)] + pub fn of() -> usize + where + T: ?Sized, + { + // Just like https://github.com/rust-lang/rust/issues/41875#issuecomment-317292888 + // We monomorphize on T and then cast the function pointer address of + // the monomorphized `YamlFormatType::of` function to an integer identifier. + YamlFormatType::of:: as usize + } + + #[doc(hidden)] + pub unsafe fn cast(ptr: *const ()) -> &'static dyn YamlFormat + where + T: 'static + YamlFormat, + { + // Cast a generic pointer back to a reference to T and return a dyn + // reference to the YamlFormat trait. + &*(ptr as *const T) + } + + fn lookup(id: usize) -> Option { + static TYPEMAP: OnceCell>> = OnceCell::new(); + let typemap = TYPEMAP + .get_or_init(|| { + let mut types = HashMap::new(); + // Iterate over all registered YamlFormatTypes and store them in + // a HashMap for fast access. + for yf in inventory::iter:: { + types.insert((yf.id)(), yf.reconstitute); + } + Mutex::new(types) + }) + .lock() + .unwrap(); + typemap.get(&id).cloned() + } + + #[doc(hidden)] + pub fn get<'a, T>(object: &'a T) -> Option<&'a dyn YamlFormat> + where + T: ?Sized, + { + // Get the type-id of `object` and cast it back to a YamlFormat + // if we can. + let id = Self::of::(); + Self::lookup(id).map(|reconstitute| unsafe { + // Shorten the lifetime to 'a, as the dyn YamlFormat reference is + // really a reinterpretation of `object`, which has lifetime 'a. + std::mem::transmute::<&'static dyn YamlFormat, &'a dyn YamlFormat>(reconstitute( + object as *const T as *const (), + )) + }) + } +} diff --git a/tests/test_format.rs b/tests/test_format.rs new file mode 100644 index 00000000..3717e7ca --- /dev/null +++ b/tests/test_format.rs @@ -0,0 +1,136 @@ +use anyhow::Result; +use serde_derive::{Deserialize, Serialize}; +use serde_yaml::yamlformat::YamlFormat; + +// Numbers in different bases, static comments. +#[derive(Serialize, YamlFormat)] +struct Coordinate { + #[yaml(format=hex, comment="X-coordinate")] + x: u32, + #[yaml(format=dec, comment="Y-coordinate")] + y: u32, + #[yaml(format=oct, comment="Z-coordinate")] + z: u32, +} + +// Text blocks, comments a fields within the struct. +#[derive(Serialize, YamlFormat)] +struct Gettysburg { + author: &'static str, + + #[yaml(format=block, comment=_prelude)] + prelude: &'static str, + #[serde(skip)] + _prelude: &'static str, + + #[yaml(format=block, comment=_middle)] + middle: &'static str, + #[serde(skip)] + _middle: &'static str, + + #[yaml(format=block, comment=_end)] + end: &'static str, + #[serde(skip)] + _end: &'static str, +} + +#[test] +fn test_coordinate() -> Result<()> { + let foo = Coordinate { x: 16, y: 10, z: 8 }; + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} + +#[test] +fn test_gettysburg() -> Result<()> { + let foo = Gettysburg { + // Note: trailing newline should cause a "|+" block. + prelude: r#"Four score and seven years ago our fathers brought forth on this +continent, a new nation, conceived in Liberty, and dedicated to the +proposition that all men are created equal. +"#, + _prelude: "Bliss copy", + + // Note: trailing newline should cause a "|+" block. + middle: r#"Now we are engaged in a great civil war, testing whether that nation, +or any nation so conceived, and so dedicated, can long endure. We are met +on a great battle field of that war. We come to dedicate a portion of it, +as a final resting place for those who died here, that the nation might +live. This we may, in all propriety do. +"#, + _middle: "Nicolay Copy", + + // Note: NO trailing newline should cause a "|-" block. + end: r#"But in a larger sense, we can not dedicate we can not consecrate we can +not hallow this ground. The brave men, living and dead, who struggled +here, have consecrated it far above our poor power to add or detract. The +world will little note, nor long remember, what we say here, but can +never forget what they did here. + +It is for us, the living, rather to be dedicated here to the unfinished +work which they have, thus far, so nobly carried on. It is rather for +us to be here dedicated to the great task remaining before us that from +these honored dead we take increased devotion to that cause for which +they gave the last full measure of devotion that we here highly resolve +that these dead shall not have died in vain; that this nation shall +have a new birth of freedom; and that this government of the people, +by the people, for the people, shall not perish from the earth."#, + _end: "Hay Copy", + + author: "Abraham Lincoln", + }; + + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} + +// A containing struct which does not implement YamlFormat. +#[derive(Serialize)] +struct Sfdp { + header: SfdpHeader, +} + +// Numbers in different bases, comments from functions within the impl. +#[derive(Serialize, YamlFormat)] +struct SfdpHeader { + #[yaml(format=hex, comment=_signature())] + signature: u32, + #[yaml(comment = "SFDP version")] + minor: u8, + major: u8, + #[yaml(comment = "Number of parameter headers minus 1")] + nph: u8, + #[yaml(format=bin, comment="Reserved field should be all ones")] + reserved: u8, +} + +impl SfdpHeader { + fn _signature(&self) -> Option { + Some(format!( + "Signature value='{}' (should be 'SFDP')", + self.signature + .to_le_bytes() + .map(|b| char::from(b).to_string()) + .join("") + )) + } +} + +#[test] +fn test_sfdp() -> Result<()> { + let foo = Sfdp { + header: SfdpHeader { + signature: 0x50444653, + minor: 6, + major: 1, + nph: 2, + reserved: 255, + }, + }; + println!("foo.header={:p}", &foo.header); + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} diff --git a/yamlformat_derive/Cargo.toml b/yamlformat_derive/Cargo.toml new file mode 100644 index 00000000..37751f60 --- /dev/null +++ b/yamlformat_derive/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "yamlformat_derive" +version = "0.0.0" +authors = ["Chris Frantz "] +edition = "2018" +#rust-version = "1.38" +license = "MIT OR Apache-2.0" +description = "YAML format for serde-yaml" +keywords = ["yaml", "serde"] + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +proc-macro-error = "1.0" +quote = "1.0" +syn = {version="1.0", features=["extra-traits"]} diff --git a/yamlformat_derive/src/ast.rs b/yamlformat_derive/src/ast.rs new file mode 100644 index 00000000..3caa808a --- /dev/null +++ b/yamlformat_derive/src/ast.rs @@ -0,0 +1,124 @@ +use crate::attr::{self, Attrs}; +use proc_macro2::Span; +use syn::{ + Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Ident, Index, Member, Result, Type, +}; + +#[derive(Debug)] +pub enum Input<'a> { + Struct(Struct<'a>), + Enum(Enum<'a>), +} + +#[derive(Debug)] +pub struct Struct<'a> { + pub original: &'a DeriveInput, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub fields: Vec>, +} + +#[derive(Debug)] +pub struct Field<'a> { + pub original: &'a syn::Field, + pub attrs: Attrs<'a>, + pub member: Member, + pub ty: &'a Type, +} + +#[derive(Debug)] +pub struct Enum<'a> { + pub original: &'a DeriveInput, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub variants: Vec>, +} + +#[derive(Debug)] +pub struct Variant<'a> { + pub original: &'a syn::Variant, + pub attrs: Attrs<'a>, + pub ident: Ident, + pub fields: Vec>, +} + +impl<'a> Input<'a> { + pub fn from_syn(node: &'a DeriveInput) -> Result { + match &node.data { + Data::Struct(data) => Struct::from_syn(node, data).map(Input::Struct), + Data::Enum(data) => Enum::from_syn(node, data).map(Input::Enum), + Data::Union(_) => Err(Error::new_spanned(node, "unions are not supported")), + } + } +} + +impl<'a> Struct<'a> { + fn from_syn(node: &'a DeriveInput, data: &'a DataStruct) -> Result { + let attrs = attr::get(&node.attrs)?; + let span = Span::call_site(); + let fields = Field::multiple_from_syn(&data.fields, span)?; + Ok(Struct { + original: node, + attrs: attrs, + ident: node.ident.clone(), + fields: fields, + }) + } +} + +impl<'a> Enum<'a> { + fn from_syn(node: &'a DeriveInput, data: &'a DataEnum) -> Result { + let attrs = attr::get(&node.attrs)?; + let span = Span::call_site(); + let variants = data + .variants + .iter() + .map(|node| { + let v = Variant::from_syn(node, span)?; + Ok(v) + }) + .collect::>()?; + Ok(Enum { + original: node, + attrs: attrs, + ident: node.ident.clone(), + variants: variants, + }) + } +} + +impl<'a> Field<'a> { + fn multiple_from_syn(fields: &'a Fields, span: Span) -> Result> { + fields + .iter() + .enumerate() + .map(|(i, field)| Field::from_syn(i, field, span)) + .collect() + } + + fn from_syn(i: usize, node: &'a syn::Field, span: Span) -> Result { + Ok(Field { + original: node, + attrs: attr::get(&node.attrs)?, + member: node.ident.clone().map(Member::Named).unwrap_or_else(|| { + Member::Unnamed(Index { + index: i as u32, + span, + }) + }), + ty: &node.ty, + }) + } +} + +impl<'a> Variant<'a> { + fn from_syn(node: &'a syn::Variant, span: Span) -> Result { + let attrs = attr::get(&node.attrs)?; + Ok(Variant { + original: node, + attrs: attrs, + ident: node.ident.clone(), + fields: Field::multiple_from_syn(&node.fields, span)?, + }) + } +} diff --git a/yamlformat_derive/src/attr.rs b/yamlformat_derive/src/attr.rs new file mode 100644 index 00000000..53079bcd --- /dev/null +++ b/yamlformat_derive/src/attr.rs @@ -0,0 +1,103 @@ +use syn::parse::ParseStream; +use syn::{parenthesized, token, Attribute, Error, Ident, LitStr, Result, Token}; + +#[derive(Debug, PartialEq)] +pub enum Format { + None, + Block, + Binary, + Decimal, + Hex, + Octal, +} + +#[derive(Debug, PartialEq)] +pub enum Comment { + None, + Field(Ident), + Function(Ident), + Static(String), +} + +#[derive(Debug)] +pub struct Attrs<'a> { + pub yaml: Option<&'a Attribute>, + pub format: Format, + pub comment: Comment, +} + +pub fn get(input: &[Attribute]) -> Result { + let mut attrs = Attrs { + yaml: None, + format: Format::None, + comment: Comment::None, + }; + + for attr in input { + if attr.path.is_ident("yaml") { + attrs.yaml = Some(attr); + parse_yaml_attribute(&mut attrs, attr)?; + } + } + Ok(attrs) +} + +fn function_call(input: ParseStream) -> Result { + let content; + let result = parenthesized!(content in input); + Ok(content.is_empty()) +} + +fn parse_yaml_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Result<()> { + syn::custom_keyword!(format); + syn::custom_keyword!(comment); + + attr.parse_args_with(|input: ParseStream| { + let mut more = true; + while more { + if input.peek(format) { + let _kw = input.parse::()?; + let _eq: Token![=] = input.parse()?; + let ident: Ident = input.parse()?; + let istr = ident.to_string(); + let format = match istr.as_str() { + "block" => Format::Block, + "bin" => Format::Binary, + "dec" => Format::Decimal, + "oct" => Format::Octal, + "hex" => Format::Hex, + _ => Format::None, + }; + if format == Format::None { + return Err(Error::new_spanned(attr, "unknown yamlformat type")); + } + attrs.format = format; + } else if input.peek(comment) { + let _kw = input.parse::()?; + let _eq: Token![=] = input.parse()?; + if input.peek(Ident) { + let ident: Ident = input.parse()?; + let func = function_call(input); + attrs.comment = match func { + Ok(true) => Comment::Function(ident.clone()), + Ok(false) => { + return Err(Error::new_spanned(attr, "Function args not permitted")); + } + Err(_) => Comment::Field(ident.clone()), + }; + return Ok(()); + } + let comment: LitStr = input.parse()?; + attrs.comment = Comment::Static(comment.value()); + } else { + return Err(Error::new_spanned(attr, "parse error")); + } + + more = input.peek(Token![,]); + if more { + let _comma: Token![,] = input.parse()?; + } + } + Ok(()) + }) +} diff --git a/yamlformat_derive/src/expand.rs b/yamlformat_derive/src/expand.rs new file mode 100644 index 00000000..e1870ccb --- /dev/null +++ b/yamlformat_derive/src/expand.rs @@ -0,0 +1,116 @@ +use crate::ast::{Enum, Field, Input, Struct, Variant}; +use crate::attr::{Comment, Format}; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{DeriveInput, Index, Member, Result}; + +pub fn derive(node: &DeriveInput) -> Result { + let input = Input::from_syn(node)?; + + Ok(match input { + Input::Struct(input) => impl_struct(input), + Input::Enum(input) => impl_enum(input), + }) +} + +fn impl_field_format(fields: &[Field]) -> Vec { + fields + .iter() + .map(|f| { + let format = match &f.attrs.format { + Format::None => quote! { None }, + Format::Block => quote! { Some(Format::Block) }, + Format::Binary => quote! { Some(Format::Binary) }, + Format::Decimal => quote! { Some(Format::Decimal) }, + Format::Hex => quote! { Some(Format::Hex) }, + Format::Octal => quote! { Some(Format::Octal) }, + }; + match &f.member { + Member::Named(id) => { + let id = id.to_string(); + quote! { MemberId::Name(#id) => #format } + } + Member::Unnamed(Index { index: i, .. }) => { + quote! { MemberId::Index(#i) => #format } + } + } + }) + .collect::>() +} + +fn impl_field_comment(fields: &[Field]) -> Vec { + fields + .iter() + .map(|f| { + let comment = match &f.attrs.comment { + Comment::None => quote! { None }, + Comment::Static(s) => quote! { + Some(#s.to_string()) + }, + Comment::Field(id) => quote! { + Some(self.#id.to_string()) + }, + Comment::Function(id) => quote! { + self.#id() + }, + }; + match &f.member { + Member::Named(id) => { + let id = id.to_string(); + quote! { MemberId::Name(#id) => #comment } + } + Member::Unnamed(Index { index: i, .. }) => { + quote! { MemberId::Index(#i) => #comment } + } + } + }) + .collect::>() +} + +fn impl_struct(input: Struct) -> TokenStream { + let formats = impl_field_format(&input.fields); + let comments = impl_field_comment(&input.fields); + let name = &input.ident; + let namestr = name.to_string(); + quote! { + const _: () = { + extern crate serde_yaml; + extern crate inventory; + use serde_yaml::yamlformat::{YamlFormat, YamlFormatType, Format, MemberId}; + + impl YamlFormat for #name { + fn format(&self, field: &MemberId) -> Option { + match field { + #(#formats),*, + _ => None, + } + } + fn comment(&self, field: &MemberId) -> Option { + match field { + #(#comments),*, + _ => None, + } + } + } + impl #name { + fn __type_id() -> usize { + YamlFormatType::of::() + } + unsafe fn __reconstitute(ptr: *const ()) -> &'static dyn YamlFormat { + YamlFormatType::cast::(ptr) + } + } + inventory::submit! { + YamlFormatType { + id: #name::__type_id, + reconstitute: #name::__reconstitute, + } + } + }; + } +} + +fn impl_enum(input: Enum) -> TokenStream { + println!("{:?}", input); + quote! {} +} diff --git a/yamlformat_derive/src/lib.rs b/yamlformat_derive/src/lib.rs new file mode 100644 index 00000000..a08e148c --- /dev/null +++ b/yamlformat_derive/src/lib.rs @@ -0,0 +1,16 @@ +extern crate proc_macro; + +mod ast; +mod attr; +mod expand; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +#[proc_macro_derive(YamlFormat, attributes(yaml))] +pub fn derive_yaml_format(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand::derive(&input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} From 541d794dd878be893cf05c5fef3659ca62e4f625 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Sun, 2 Jan 2022 17:07:22 -0800 Subject: [PATCH 2/4] Add support for enums Signed-off-by: Chris Frantz --- src/ser.rs | 255 +++++++++++++++++++++++++------- src/value/mod.rs | 2 +- src/yamlformat.rs | 10 +- tests/test_format.rs | 91 ++++++++---- yamlformat_derive/src/attr.rs | 9 +- yamlformat_derive/src/expand.rs | 134 +++++++++++++---- 6 files changed, 392 insertions(+), 109 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index e5a136d0..7430fdb3 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -88,13 +88,13 @@ where type Ok = (); type Error = Error; - type SerializeSeq = ThenWrite<'a, 'f, W, SerializeArray>; - type SerializeTuple = ThenWrite<'a, 'f, W, SerializeArray>; - type SerializeTupleStruct = ThenWrite<'a, 'f, W, SerializeArray>; - type SerializeTupleVariant = ThenWrite<'a, 'f, W, SerializeTupleVariant>; + type SerializeSeq = ThenWrite<'a, 'f, W, SerializeArray<'f>>; + type SerializeTuple = ThenWrite<'a, 'f, W, SerializeArray<'f>>; + type SerializeTupleStruct = ThenWrite<'a, 'f, W, SerializeArray<'f>>; + type SerializeTupleVariant = ThenWrite<'a, 'f, W, SerializeTupleVariant<'f>>; type SerializeMap = ThenWrite<'a, 'f, W, SerializeMap>; type SerializeStruct = ThenWrite<'a, 'f, W, SerializeStruct<'f>>; - type SerializeStructVariant = ThenWrite<'a, 'f, W, SerializeStructVariant>; + type SerializeStructVariant = ThenWrite<'a, 'f, W, SerializeStructVariant<'f>>; fn serialize_bool(self, v: bool) -> Result<()> { let doc = SerializerToYaml::default().serialize_bool(v)?; @@ -215,7 +215,7 @@ where where T: ser::Serialize, { - let doc = SerializerToYaml::default().serialize_newtype_variant( + let doc = SerializerToYaml::new(self.yamlformat, None).serialize_newtype_variant( name, variant_index, variant, @@ -307,7 +307,7 @@ impl<'a, 'f, W, D> ThenWrite<'a, 'f, W, D> { } } -impl<'a, 'f, W> ser::SerializeSeq for ThenWrite<'a, 'f, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeSeq for ThenWrite<'a, 'f, W, SerializeArray<'f>> where W: io::Write, { @@ -327,7 +327,7 @@ where } } -impl<'a, 'f, W> ser::SerializeTuple for ThenWrite<'a, 'f, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeTuple for ThenWrite<'a, 'f, W, SerializeArray<'f>> where W: io::Write, { @@ -347,7 +347,7 @@ where } } -impl<'a, 'f, W> ser::SerializeTupleStruct for ThenWrite<'a, 'f, W, SerializeArray> +impl<'a, 'f, W> ser::SerializeTupleStruct for ThenWrite<'a, 'f, W, SerializeArray<'f>> where W: io::Write, { @@ -358,6 +358,17 @@ where where V: ser::Serialize, { + let field = MemberId::Index(self.delegate.array.len() as u32); + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(None, &field)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(None, &field)) + .flatten(); self.delegate.serialize_field(value) } @@ -367,7 +378,7 @@ where } } -impl<'a, 'f, W> ser::SerializeTupleVariant for ThenWrite<'a, 'f, W, SerializeTupleVariant> +impl<'a, 'f, W> ser::SerializeTupleVariant for ThenWrite<'a, 'f, W, SerializeTupleVariant<'f>> where W: io::Write, { @@ -378,10 +389,31 @@ where where V: ser::Serialize, { + let field = MemberId::Index(self.delegate.array.len() as u32); + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(Some(self.delegate.name), &field)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(Some(self.delegate.name), &field)) + .flatten(); self.delegate.serialize_field(v) } - fn end(self) -> Result<()> { + fn end(mut self) -> Result<()> { + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(Some(self.delegate.name), &MemberId::Variant)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(Some(self.delegate.name), &MemberId::Variant)) + .flatten(); let doc = self.delegate.end()?; self.serializer.write(doc) } @@ -433,18 +465,16 @@ where where V: ser::Serialize, { - let name = MemberId::Name(key); - //println!("serialize_field: {:?}", name); - + let field = MemberId::Name(key); self.delegate.serializer.format = self .serializer .yamlformat - .map(|y| y.format(&name)) + .map(|y| y.format(None, &field)) .flatten(); self.delegate.serializer.comment = self .serializer .yamlformat - .map(|y| y.comment(&name)) + .map(|y| y.comment(None, &field)) .flatten(); self.delegate.serialize_field(key, value) @@ -456,7 +486,7 @@ where } } -impl<'a, 'f, W> ser::SerializeStructVariant for ThenWrite<'a, 'f, W, SerializeStructVariant> +impl<'a, 'f, W> ser::SerializeStructVariant for ThenWrite<'a, 'f, W, SerializeStructVariant<'f>> where W: io::Write, { @@ -467,10 +497,31 @@ where where V: ser::Serialize, { - self.delegate.serialize_field(field, v) + let fld = MemberId::Name(field); + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(Some(self.delegate.name), &fld)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(Some(self.delegate.name), &fld)) + .flatten(); + self.delegate.serialize_field(self.delegate.name, v) } - fn end(self) -> Result<()> { + fn end(mut self) -> Result<()> { + self.delegate.serializer.format = self + .serializer + .yamlformat + .map(|y| y.format(Some(self.delegate.name), &MemberId::Variant)) + .flatten(); + self.delegate.serializer.comment = self + .serializer + .yamlformat + .map(|y| y.comment(Some(self.delegate.name), &MemberId::Variant)) + .flatten(); let doc = self.delegate.end()?; self.serializer.write(doc) } @@ -506,13 +557,13 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { type Ok = Yaml; type Error = Error; - type SerializeSeq = SerializeArray; - type SerializeTuple = SerializeArray; - type SerializeTupleStruct = SerializeArray; - type SerializeTupleVariant = SerializeTupleVariant; + type SerializeSeq = SerializeArray<'f>; + type SerializeTuple = SerializeArray<'f>; + type SerializeTupleStruct = SerializeArray<'f>; + type SerializeTupleVariant = SerializeTupleVariant<'f>; type SerializeMap = SerializeMap; type SerializeStruct = SerializeStruct<'f>; - type SerializeStructVariant = SerializeStructVariant; + type SerializeStructVariant = SerializeStructVariant<'f>; fn serialize_bool(self, v: bool) -> Result { Ok(Yaml::Boolean(v)) @@ -730,9 +781,19 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { where T: ser::Serialize, { + let format = self + .yamlformat + .map(|y| y.format(Some(variant), &MemberId::Variant)) + .flatten(); + let comment = self + .yamlformat + .map(|y| y.comment(Some(variant), &MemberId::Variant)) + .flatten(); Ok(singleton_hash( to_yaml(variant, None, None)?, to_yaml(value, None, None)?, + format, + comment, )) } @@ -747,19 +808,22 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { value.serialize(self) } - fn serialize_seq(self, len: Option) -> Result { + fn serialize_seq(self, len: Option) -> Result> { let array = match len { None => yaml::Array::new(), Some(len) => yaml::Array::with_capacity(len), }; - Ok(SerializeArray { array }) + Ok(SerializeArray { + serializer: self, + array, + }) } - fn serialize_tuple(self, len: usize) -> Result { + fn serialize_tuple(self, len: usize) -> Result> { self.serialize_seq(Some(len)) } - fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result { + fn serialize_tuple_struct(self, _name: &'static str, len: usize) -> Result> { self.serialize_seq(Some(len)) } @@ -769,8 +833,9 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { _idx: u32, variant: &'static str, len: usize, - ) -> Result { + ) -> Result> { Ok(SerializeTupleVariant { + serializer: self, name: variant, array: yaml::Array::with_capacity(len), }) @@ -796,8 +861,9 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { _idx: u32, variant: &'static str, _len: usize, - ) -> Result { + ) -> Result> { Ok(SerializeStructVariant { + serializer: self, name: variant, hash: yaml::Hash::new(), }) @@ -805,12 +871,14 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { } #[doc(hidden)] -pub struct SerializeArray { +pub struct SerializeArray<'f> { + serializer: SerializerToYaml<'f>, array: yaml::Array, } #[doc(hidden)] -pub struct SerializeTupleVariant { +pub struct SerializeTupleVariant<'f> { + serializer: SerializerToYaml<'f>, name: &'static str, array: yaml::Array, } @@ -828,12 +896,13 @@ pub struct SerializeStruct<'f> { } #[doc(hidden)] -pub struct SerializeStructVariant { +pub struct SerializeStructVariant<'f> { + serializer: SerializerToYaml<'f>, name: &'static str, hash: yaml::Hash, } -impl ser::SerializeSeq for SerializeArray { +impl<'f> ser::SerializeSeq for SerializeArray<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -850,7 +919,7 @@ impl ser::SerializeSeq for SerializeArray { } } -impl ser::SerializeTuple for SerializeArray { +impl<'f> ser::SerializeTuple for SerializeArray<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -866,7 +935,7 @@ impl ser::SerializeTuple for SerializeArray { } } -impl ser::SerializeTupleStruct for SerializeArray { +impl<'f> ser::SerializeTupleStruct for SerializeArray<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -874,7 +943,21 @@ impl ser::SerializeTupleStruct for SerializeArray { where V: ser::Serialize, { - ser::SerializeSeq::serialize_element(self, value) + let field = MemberId::Index(self.array.len() as u32); + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(None, &field)) + .flatten() + }); + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(None, &field)) + .flatten() + }); + self.array.push(to_yaml(value, format, comment)?); + Ok(()) } fn end(self) -> Result { @@ -882,7 +965,7 @@ impl ser::SerializeTupleStruct for SerializeArray { } } -impl ser::SerializeTupleVariant for SerializeTupleVariant { +impl<'f> ser::SerializeTupleVariant for SerializeTupleVariant<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -890,14 +973,41 @@ impl ser::SerializeTupleVariant for SerializeTupleVariant { where V: ser::Serialize, { - self.array.push(to_yaml(v, None, None)?); + let field = MemberId::Index(self.array.len() as u32); + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(Some(self.name), &field)) + .flatten() + }); + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(Some(self.name), &field)) + .flatten() + }); + self.array.push(to_yaml(v, format, comment)?); Ok(()) } - fn end(self) -> Result { + fn end(mut self) -> Result { + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(Some(self.name), &MemberId::Variant)) + .flatten() + }); + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(Some(self.name), &MemberId::Variant)) + .flatten() + }); Ok(singleton_hash( to_yaml(self.name, None, None)?, Yaml::Array(self.array), + format, + comment, )) } } @@ -948,19 +1058,17 @@ impl<'f> ser::SerializeStruct for SerializeStruct<'f> { where V: ser::Serialize, { - let name = MemberId::Name(key); - //println!("serialize_field name={:?} value={:p}", name, value); + let field = MemberId::Name(key); let format = self.serializer.format.take().or_else(|| { self.serializer .yamlformat - .map(|y| y.format(&name)) + .map(|y| y.format(None, &field)) .flatten() }); - let comment = self.serializer.comment.take().or_else(|| { self.serializer .yamlformat - .map(|y| y.comment(&name)) + .map(|y| y.comment(None, &field)) .flatten() }); @@ -974,7 +1082,7 @@ impl<'f> ser::SerializeStruct for SerializeStruct<'f> { } } -impl ser::SerializeStructVariant for SerializeStructVariant { +impl<'f> ser::SerializeStructVariant for SerializeStructVariant<'f> { type Ok = yaml::Yaml; type Error = Error; @@ -982,15 +1090,42 @@ impl ser::SerializeStructVariant for SerializeStructVariant { where V: ser::Serialize, { + let fld = MemberId::Name(field); + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(Some(self.name), &fld)) + .flatten() + }); + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(Some(self.name), &fld)) + .flatten() + }); self.hash - .insert(to_yaml(field, None, None)?, to_yaml(v, None, None)?); + .insert(to_yaml(field, None, comment)?, to_yaml(v, format, None)?); Ok(()) } - fn end(self) -> Result { + fn end(mut self) -> Result { + let format = self.serializer.format.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.format(Some(self.name), &MemberId::Variant)) + .flatten() + }); + let comment = self.serializer.comment.take().or_else(|| { + self.serializer + .yamlformat + .map(|y| y.comment(Some(self.name), &MemberId::Variant)) + .flatten() + }); Ok(singleton_hash( to_yaml(self.name, None, None)?, Yaml::Hash(self.hash), + format, + comment, )) } } @@ -1057,14 +1192,32 @@ where let yf = YamlFormatType::get(elem); let yaml = elem.serialize(SerializerToYaml::new(yf, format))?; if let Some(c) = comment { - Ok(Yaml::DocFragment(vec![Yaml::Comment(c), yaml])) + Ok(Yaml::DocFragment( + vec![Yaml::Comment(c), yaml], + yaml::FragStyle::None, + )) } else { Ok(yaml) } } -fn singleton_hash(k: Yaml, v: Yaml) -> Yaml { +fn singleton_hash(k: Yaml, v: Yaml, format: Option, comment: Option) -> Yaml { let mut hash = yaml::Hash::new(); hash.insert(k, v); - Yaml::Hash(hash) + let yaml = Yaml::Hash(hash); + + match (format, comment) { + (None, Some(c)) => Yaml::DocFragment(vec![Yaml::Comment(c), yaml], yaml::FragStyle::None), + (Some(f), None) if f == Format::Oneline => { + Yaml::DocFragment(vec![yaml], yaml::FragStyle::Oneline) + } + (Some(f), Some(c)) if f == Format::Oneline => Yaml::DocFragment( + vec![ + Yaml::Comment(c), + Yaml::DocFragment(vec![yaml], yaml::FragStyle::Oneline), + ], + yaml::FragStyle::Indented, + ), + (_, _) => yaml, + } } diff --git a/src/value/mod.rs b/src/value/mod.rs index 079aad0d..2b6eb560 100644 --- a/src/value/mod.rs +++ b/src/value/mod.rs @@ -624,7 +624,7 @@ fn yaml_to_value(yaml: Yaml) -> Value { Yaml::Null => Value::Null, Yaml::BadValue => panic!("bad value"), Yaml::BlockScalar(_) => panic!("block scalar unexpected"), - Yaml::DocFragment(_) => panic!("doc fragment unexpected"), + Yaml::DocFragment(_, _) => panic!("doc fragment unexpected"), Yaml::Comment(_) => panic!("comment unexpected"), } } diff --git a/src/yamlformat.rs b/src/yamlformat.rs index cc2add63..98797df5 100644 --- a/src/yamlformat.rs +++ b/src/yamlformat.rs @@ -11,8 +11,10 @@ pub use yamlformat_derive::*; pub enum MemberId<'a> { /// `Name` identifies a named field. Name(&'a str), - /// `Index` identifies an tuple field. + /// `Index` identifies a tuple field. Index(u32), + /// `Variant` identifies a variant wihin an enum. + Variant, } /// `Format` describes how a field is to be formatted. @@ -28,14 +30,16 @@ pub enum Format { Hex, /// Format an integer field as Octal. Octal, + /// Format hashes and arrays on one line. + Oneline, } /// YamlFormat is used by the serializer to choose the desired formatting. pub trait YamlFormat { /// Returns the format for a given field. - fn format(&self, field: &MemberId) -> Option; + fn format(&self, variant: Option<&str>, field: &MemberId) -> Option; /// Returns the comment associated with a given field. - fn comment(&self, field: &MemberId) -> Option; + fn comment(&self, variant: Option<&str>, field: &MemberId) -> Option; } #[doc(hidden)] diff --git a/tests/test_format.rs b/tests/test_format.rs index 3717e7ca..ddb4bb05 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -13,6 +13,14 @@ struct Coordinate { z: u32, } +#[test] +fn test_coordinate() -> Result<()> { + let foo = Coordinate { x: 16, y: 10, z: 8 }; + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} + // Text blocks, comments a fields within the struct. #[derive(Serialize, YamlFormat)] struct Gettysburg { @@ -34,23 +42,15 @@ struct Gettysburg { _end: &'static str, } -#[test] -fn test_coordinate() -> Result<()> { - let foo = Coordinate { x: 16, y: 10, z: 8 }; - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); - Ok(()) -} - #[test] fn test_gettysburg() -> Result<()> { let foo = Gettysburg { // Note: trailing newline should cause a "|+" block. - prelude: r#"Four score and seven years ago our fathers brought forth on this + prelude: r#"Four score and seven years ago our fathers brought forth, upon this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. "#, - _prelude: "Bliss copy", + _prelude: "Hay copy", // Note: trailing newline should cause a "|+" block. middle: r#"Now we are engaged in a great civil war, testing whether that nation, @@ -62,21 +62,20 @@ live. This we may, in all propriety do. _middle: "Nicolay Copy", // Note: NO trailing newline should cause a "|-" block. - end: r#"But in a larger sense, we can not dedicate we can not consecrate we can -not hallow this ground. The brave men, living and dead, who struggled -here, have consecrated it far above our poor power to add or detract. The -world will little note, nor long remember, what we say here, but can -never forget what they did here. - -It is for us, the living, rather to be dedicated here to the unfinished -work which they have, thus far, so nobly carried on. It is rather for -us to be here dedicated to the great task remaining before us that from -these honored dead we take increased devotion to that cause for which -they gave the last full measure of devotion that we here highly resolve -that these dead shall not have died in vain; that this nation shall -have a new birth of freedom; and that this government of the people, + end: r#"But, in a larger sense, we can not dedicate -- we can not consecrate -- +we can not hallow -- this ground. The brave men, living and dead, who +struggled here, have consecrated it, far above our poor power to add or +detract. The world will little note, nor long remember what we say here, +but it can never forget what they did here. It is for us the living, +rather, to be dedicated here to the unfinished work which they who +fought here have thus far so nobly advanced. It is rather for us to be +here dedicated to the great task remaining before us -- that from these +honored dead we take increased devotion to that cause for which they gave +the last full measure of devotion -- that we here highly resolve that +these dead shall not have died in vain -- that this nation, under God, +shall have a new birth of freedom -- and that government of the people, by the people, for the people, shall not perish from the earth."#, - _end: "Hay Copy", + _end: "Bliss Copy", author: "Abraham Lincoln", }; @@ -129,7 +128,49 @@ fn test_sfdp() -> Result<()> { reserved: 255, }, }; - println!("foo.header={:p}", &foo.header); + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} + +#[derive(Serialize, YamlFormat)] +struct IntelWord( + #[yaml(format=hex, comment="MSB")] u8, + #[yaml(format=hex, comment="LSB")] u8, +); + +#[test] +fn test_intel_word() -> Result<()> { + let foo = IntelWord(0x80, 0x01); + let s = serde_yaml::to_string(&foo)?; + println!("{}", s); + Ok(()) +} + +#[derive(Serialize, YamlFormat)] +enum NesAddress { + #[yaml(format=oneline, comment="NES file offset")] + File(u32), + #[yaml(format=oneline, comment="NES PRG bank:address")] + Prg(#[yaml(format=hex)] u8, #[yaml(format=hex)] u16), + #[yaml(format=oneline)] + Chr(#[yaml(format=hex)] u8, #[yaml(format=hex)] u16), +} + +#[derive(Serialize)] +struct Addresses { + a: NesAddress, + b: NesAddress, + c: NesAddress, +} + +#[test] +fn test_nes_address() -> Result<()> { + let foo = Addresses { + a: NesAddress::File(0x4010), + b: NesAddress::Prg(1, 0x8000), + c: NesAddress::Chr(0, 0x1000), + }; let s = serde_yaml::to_string(&foo)?; println!("{}", s); Ok(()) diff --git a/yamlformat_derive/src/attr.rs b/yamlformat_derive/src/attr.rs index 53079bcd..64f7c46c 100644 --- a/yamlformat_derive/src/attr.rs +++ b/yamlformat_derive/src/attr.rs @@ -9,6 +9,7 @@ pub enum Format { Decimal, Hex, Octal, + Oneline, } #[derive(Debug, PartialEq)] @@ -66,6 +67,7 @@ fn parse_yaml_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resul "dec" => Format::Decimal, "oct" => Format::Octal, "hex" => Format::Hex, + "oneline" => Format::Oneline, _ => Format::None, }; if format == Format::None { @@ -85,10 +87,10 @@ fn parse_yaml_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resul } Err(_) => Comment::Field(ident.clone()), }; - return Ok(()); + } else { + let comment: LitStr = input.parse()?; + attrs.comment = Comment::Static(comment.value()); } - let comment: LitStr = input.parse()?; - attrs.comment = Comment::Static(comment.value()); } else { return Err(Error::new_spanned(attr, "parse error")); } @@ -96,6 +98,7 @@ fn parse_yaml_attribute<'a>(attrs: &mut Attrs<'a>, attr: &'a Attribute) -> Resul more = input.peek(Token![,]); if more { let _comma: Token![,] = input.parse()?; + more = !input.is_empty(); } } Ok(()) diff --git a/yamlformat_derive/src/expand.rs b/yamlformat_derive/src/expand.rs index e1870ccb..78817bf1 100644 --- a/yamlformat_derive/src/expand.rs +++ b/yamlformat_derive/src/expand.rs @@ -1,5 +1,5 @@ use crate::ast::{Enum, Field, Input, Struct, Variant}; -use crate::attr::{Comment, Format}; +use crate::attr::{Attrs, Comment, Format}; use proc_macro2::TokenStream; use quote::quote; use syn::{DeriveInput, Index, Member, Result}; @@ -13,18 +13,23 @@ pub fn derive(node: &DeriveInput) -> Result { }) } +fn impl_format(a: &Attrs) -> TokenStream { + match &a.format { + Format::None => quote! { None }, + Format::Block => quote! { Some(Format::Block) }, + Format::Binary => quote! { Some(Format::Binary) }, + Format::Decimal => quote! { Some(Format::Decimal) }, + Format::Hex => quote! { Some(Format::Hex) }, + Format::Octal => quote! { Some(Format::Octal) }, + Format::Oneline => quote! { Some(Format::Oneline) }, + } +} + fn impl_field_format(fields: &[Field]) -> Vec { fields .iter() .map(|f| { - let format = match &f.attrs.format { - Format::None => quote! { None }, - Format::Block => quote! { Some(Format::Block) }, - Format::Binary => quote! { Some(Format::Binary) }, - Format::Decimal => quote! { Some(Format::Decimal) }, - Format::Hex => quote! { Some(Format::Hex) }, - Format::Octal => quote! { Some(Format::Octal) }, - }; + let format = impl_format(&f.attrs); match &f.member { Member::Named(id) => { let id = id.to_string(); @@ -38,22 +43,26 @@ fn impl_field_format(fields: &[Field]) -> Vec { .collect::>() } +fn impl_comment(a: &Attrs) -> TokenStream { + match &a.comment { + Comment::None => quote! { None }, + Comment::Static(s) => quote! { + Some(#s.to_string()) + }, + Comment::Field(id) => quote! { + Some(self.#id.to_string()) + }, + Comment::Function(id) => quote! { + self.#id() + }, + } +} + fn impl_field_comment(fields: &[Field]) -> Vec { fields .iter() .map(|f| { - let comment = match &f.attrs.comment { - Comment::None => quote! { None }, - Comment::Static(s) => quote! { - Some(#s.to_string()) - }, - Comment::Field(id) => quote! { - Some(self.#id.to_string()) - }, - Comment::Function(id) => quote! { - self.#id() - }, - }; + let comment = impl_comment(&f.attrs); match &f.member { Member::Named(id) => { let id = id.to_string(); @@ -67,11 +76,45 @@ fn impl_field_comment(fields: &[Field]) -> Vec { .collect::>() } +fn impl_variants(variants: &[Variant]) -> (Vec, Vec) { + let formats = variants + .iter() + .map(|v| { + let variant = v.ident.to_string(); + let formats = impl_field_format(&v.fields); + let vformat = impl_format(&v.attrs); + quote! { + #variant => match field { + MemberId::Variant => #vformat, + #(#formats),*, + _ => None, + } + } + }) + .collect::>(); + let comments = variants + .iter() + .map(|v| { + let variant = v.ident.to_string(); + let comments = impl_field_comment(&v.fields); + let vcomment = impl_comment(&v.attrs); + quote! { + #variant => match field { + MemberId::Variant => #vcomment, + #(#comments),*, + _ => None, + } + } + }) + .collect::>(); + + (formats, comments) +} + fn impl_struct(input: Struct) -> TokenStream { let formats = impl_field_format(&input.fields); let comments = impl_field_comment(&input.fields); let name = &input.ident; - let namestr = name.to_string(); quote! { const _: () = { extern crate serde_yaml; @@ -79,13 +122,13 @@ fn impl_struct(input: Struct) -> TokenStream { use serde_yaml::yamlformat::{YamlFormat, YamlFormatType, Format, MemberId}; impl YamlFormat for #name { - fn format(&self, field: &MemberId) -> Option { + fn format(&self, _variant: Option<&str>, field: &MemberId) -> Option { match field { #(#formats),*, _ => None, } } - fn comment(&self, field: &MemberId) -> Option { + fn comment(&self, _variant: Option<&str>, field: &MemberId) -> Option { match field { #(#comments),*, _ => None, @@ -108,9 +151,48 @@ fn impl_struct(input: Struct) -> TokenStream { } }; } + } fn impl_enum(input: Enum) -> TokenStream { - println!("{:?}", input); - quote! {} + let (formats, comments) = impl_variants(&input.variants); + let name = &input.ident; + quote! { + const _: () = { + extern crate serde_yaml; + extern crate inventory; + use serde_yaml::yamlformat::{YamlFormat, YamlFormatType, Format, MemberId}; + + impl YamlFormat for #name { + fn format(&self, variant: Option<&str>, field: &MemberId) -> Option { + let variant = variant?; + match variant { + #(#formats),*, + _ => None, + } + } + fn comment(&self, variant: Option<&str>, field: &MemberId) -> Option { + let variant = variant?; + match variant { + #(#comments),*, + _ => None, + } + } + } + impl #name { + fn __type_id() -> usize { + YamlFormatType::of::() + } + unsafe fn __reconstitute(ptr: *const ()) -> &'static dyn YamlFormat { + YamlFormatType::cast::(ptr) + } + } + inventory::submit! { + YamlFormatType { + id: #name::__type_id, + reconstitute: #name::__reconstitute, + } + } + }; + } } From ed659298fcd90063b7daec8e0013fef53397340a Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Tue, 4 Jan 2022 10:45:40 -0800 Subject: [PATCH 3/4] Cleanup output and tests Signed-off-by: Chris Frantz --- src/ser.rs | 62 +++++++------ src/yamlformat.rs | 1 - tests/test_format.rs | 159 ++++++++++++++++++++++++++-------- yamlformat_derive/src/attr.rs | 4 +- 4 files changed, 155 insertions(+), 71 deletions(-) diff --git a/src/ser.rs b/src/ser.rs index 7430fdb3..abaf15b9 100644 --- a/src/ser.rs +++ b/src/ser.rs @@ -294,7 +294,6 @@ where pub struct ThenWrite<'a, 'f, W, D> { serializer: &'a mut Serializer<'f, W>, delegate: D, - comment: Option, } impl<'a, 'f, W, D> ThenWrite<'a, 'f, W, D> { @@ -302,7 +301,6 @@ impl<'a, 'f, W, D> ThenWrite<'a, 'f, W, D> { ThenWrite { serializer, delegate, - comment: None, } } } @@ -572,10 +570,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_i8(self, v: i8) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#08b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#010b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#02x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#03o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#05o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type i8", self.format @@ -586,10 +584,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_i16(self, v: i16) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#016b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#018b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#06o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#06x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#08o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type i16", self.format @@ -600,10 +598,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_i32(self, v: i32) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#032b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#034b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#08x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#011o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#010x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#013o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type i32", self.format @@ -614,10 +612,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_i64(self, v: i64) -> Result { match self.format { None => Ok(Yaml::Integer(v)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#064b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#066b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#016x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#022o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#018x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#024o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type i64", self.format @@ -628,10 +626,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_i128(self, v: i128) -> Result { match self.format { None => Ok(Yaml::Real(v.to_string())), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0128b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0130}", v))), Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#032x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#043o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#034x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#045o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type i128", self.format @@ -642,10 +640,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_u8(self, v: u8) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#08b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#010b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#02x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#03o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#05o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type u8", self.format @@ -656,10 +654,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_u16(self, v: u16) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#016b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#018b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#04x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#06o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#06x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#08o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type u16", self.format @@ -670,10 +668,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_u32(self, v: u32) -> Result { match self.format { None => Ok(Yaml::Integer(v as i64)), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#032b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#034b}", v))), Some(Format::Decimal) => Ok(Yaml::Integer(v as i64)), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#08x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#011o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#010x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#013o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type u32", self.format @@ -684,10 +682,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_u64(self, v: u64) -> Result { match self.format { None => Ok(Yaml::Real(v.to_string())), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#064b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#066b}", v))), Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#016x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#022o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#018x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#024o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type u64", self.format @@ -698,10 +696,10 @@ impl<'f> ser::Serializer for SerializerToYaml<'f> { fn serialize_u128(self, v: u128) -> Result { match self.format { None => Ok(Yaml::Real(v.to_string())), - Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0128b}", v))), + Some(Format::Binary) => Ok(Yaml::Real(format!("{:#0130}", v))), Some(Format::Decimal) => Ok(Yaml::Real(v.to_string())), - Some(Format::Hex) => Ok(Yaml::Real(format!("{:#032x}", v))), - Some(Format::Octal) => Ok(Yaml::Real(format!("{:#043o}", v))), + Some(Format::Hex) => Ok(Yaml::Real(format!("{:#034x}", v))), + Some(Format::Octal) => Ok(Yaml::Real(format!("{:#045o}", v))), _ => Err(error::format(format!( "Format {:?} not supported for type u128", self.format diff --git a/src/yamlformat.rs b/src/yamlformat.rs index 98797df5..e326f968 100644 --- a/src/yamlformat.rs +++ b/src/yamlformat.rs @@ -1,7 +1,6 @@ //! yaml formatting. use once_cell::sync::OnceCell; -use serde::Serialize; use std::collections::HashMap; use std::sync::Mutex; pub use yamlformat_derive::*; diff --git a/tests/test_format.rs b/tests/test_format.rs index ddb4bb05..1bbc62e7 100644 --- a/tests/test_format.rs +++ b/tests/test_format.rs @@ -3,7 +3,7 @@ use serde_derive::{Deserialize, Serialize}; use serde_yaml::yamlformat::YamlFormat; // Numbers in different bases, static comments. -#[derive(Serialize, YamlFormat)] +#[derive(Serialize, Deserialize, YamlFormat, Debug, PartialEq)] struct Coordinate { #[yaml(format=hex, comment="X-coordinate")] x: u32, @@ -13,44 +13,86 @@ struct Coordinate { z: u32, } +const COORDINATE: &str = r#"--- +# X-coordinate +x: 0x00000010 +# Y-coordinate +y: 10 +# Z-coordinate +z: 0o00000000010 +"#; + #[test] fn test_coordinate() -> Result<()> { - let foo = Coordinate { x: 16, y: 10, z: 8 }; - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); + let val = Coordinate { x: 16, y: 10, z: 8 }; + let s = serde_yaml::to_string(&val)?; + let d = serde_yaml::from_str::(&s)?; + assert_eq!(s, COORDINATE); + assert_eq!(d, val); Ok(()) } // Text blocks, comments a fields within the struct. -#[derive(Serialize, YamlFormat)] +#[derive(Serialize, Deserialize, YamlFormat, Debug, PartialEq)] struct Gettysburg { - author: &'static str, + author: String, #[yaml(format=block, comment=_prelude)] - prelude: &'static str, + prelude: String, #[serde(skip)] - _prelude: &'static str, + _prelude: String, #[yaml(format=block, comment=_middle)] - middle: &'static str, + middle: String, #[serde(skip)] - _middle: &'static str, + _middle: String, #[yaml(format=block, comment=_end)] - end: &'static str, + end: String, #[serde(skip)] - _end: &'static str, + _end: String, } +const GETTYSBURG: &str = r#"--- +author: Abraham Lincoln +# Hay copy +prelude: |+ + Four score and seven years ago our fathers brought forth, upon this + continent, a new nation, conceived in Liberty, and dedicated to the + proposition that all men are created equal. +# Nicolay Copy +middle: |+ + Now we are engaged in a great civil war, testing whether that nation, + or any nation so conceived, and so dedicated, can long endure. We are met + on a great battle field of that war. We come to dedicate a portion of it, + as a final resting place for those who died here, that the nation might + live. This we may, in all propriety do. +# Bliss Copy +end: |- + But, in a larger sense, we can not dedicate -- we can not consecrate -- + we can not hallow -- this ground. The brave men, living and dead, who + struggled here, have consecrated it, far above our poor power to add or + detract. The world will little note, nor long remember what we say here, + but it can never forget what they did here. It is for us the living, + rather, to be dedicated here to the unfinished work which they who + fought here have thus far so nobly advanced. It is rather for us to be + here dedicated to the great task remaining before us -- that from these + honored dead we take increased devotion to that cause for which they gave + the last full measure of devotion -- that we here highly resolve that + these dead shall not have died in vain -- that this nation, under God, + shall have a new birth of freedom -- and that government of the people, + by the people, for the people, shall not perish from the earth. +"#; + #[test] fn test_gettysburg() -> Result<()> { - let foo = Gettysburg { + let mut val = Gettysburg { // Note: trailing newline should cause a "|+" block. prelude: r#"Four score and seven years ago our fathers brought forth, upon this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal. -"#, - _prelude: "Hay copy", +"#.to_string(), + _prelude: "Hay copy".to_string(), // Note: trailing newline should cause a "|+" block. middle: r#"Now we are engaged in a great civil war, testing whether that nation, @@ -58,8 +100,8 @@ or any nation so conceived, and so dedicated, can long endure. We are met on a great battle field of that war. We come to dedicate a portion of it, as a final resting place for those who died here, that the nation might live. This we may, in all propriety do. -"#, - _middle: "Nicolay Copy", +"#.to_string(), + _middle: "Nicolay Copy".to_string(), // Note: NO trailing newline should cause a "|-" block. end: r#"But, in a larger sense, we can not dedicate -- we can not consecrate -- @@ -74,25 +116,32 @@ honored dead we take increased devotion to that cause for which they gave the last full measure of devotion -- that we here highly resolve that these dead shall not have died in vain -- that this nation, under God, shall have a new birth of freedom -- and that government of the people, -by the people, for the people, shall not perish from the earth."#, - _end: "Bliss Copy", +by the people, for the people, shall not perish from the earth."#.to_string(), + _end: "Bliss Copy".to_string(), - author: "Abraham Lincoln", + author: "Abraham Lincoln".to_string(), }; - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); + let s = serde_yaml::to_string(&val)?; + let d = serde_yaml::from_str::(&s)?; + assert_eq!(s, GETTYSBURG); + + // The deserialized struct will have empty strings in the comment fields. + val._prelude = String::default(); + val._middle = String::default(); + val._end = String::default(); + assert_eq!(d, val); Ok(()) } // A containing struct which does not implement YamlFormat. -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] struct Sfdp { header: SfdpHeader, } // Numbers in different bases, comments from functions within the impl. -#[derive(Serialize, YamlFormat)] +#[derive(Serialize, Deserialize, YamlFormat, Debug, PartialEq)] struct SfdpHeader { #[yaml(format=hex, comment=_signature())] signature: u32, @@ -117,9 +166,23 @@ impl SfdpHeader { } } + +const SFDP: &str = r#"--- +header: + # Signature value='SFDP' (should be 'SFDP') + signature: 0x50444653 + # SFDP version + minor: 6 + major: 1 + # Number of parameter headers minus 1 + nph: 2 + # Reserved field should be all ones + reserved: 0b11111111 +"#; + #[test] fn test_sfdp() -> Result<()> { - let foo = Sfdp { + let val = Sfdp { header: SfdpHeader { signature: 0x50444653, minor: 6, @@ -128,26 +191,37 @@ fn test_sfdp() -> Result<()> { reserved: 255, }, }; - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); + let s = serde_yaml::to_string(&val)?; + let d = serde_yaml::from_str::(&s)?; + assert_eq!(s, SFDP); + assert_eq!(d, val); Ok(()) } -#[derive(Serialize, YamlFormat)] +#[derive(Serialize, Deserialize, YamlFormat, Debug, PartialEq)] struct IntelWord( #[yaml(format=hex, comment="MSB")] u8, #[yaml(format=hex, comment="LSB")] u8, ); +const INTEL_WORD: &str = r#"--- +# MSB +- 0x80 +# LSB +- 0x01 +"#; + #[test] fn test_intel_word() -> Result<()> { - let foo = IntelWord(0x80, 0x01); - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); + let val = IntelWord(0x80, 0x01); + let s = serde_yaml::to_string(&val)?; + let d = serde_yaml::from_str::(&s)?; + assert_eq!(s, INTEL_WORD); + assert_eq!(d, val); Ok(()) } -#[derive(Serialize, YamlFormat)] +#[derive(Serialize, Deserialize, YamlFormat, Debug, PartialEq)] enum NesAddress { #[yaml(format=oneline, comment="NES file offset")] File(u32), @@ -157,21 +231,34 @@ enum NesAddress { Chr(#[yaml(format=hex)] u8, #[yaml(format=hex)] u16), } -#[derive(Serialize)] +#[derive(Serialize, Deserialize, Debug, PartialEq)] struct Addresses { a: NesAddress, b: NesAddress, c: NesAddress, } +// Note: there is an extra space after the `a` and `b` key fields. +const ADDRESSES: &str = r#"--- +a: + # NES file offset + {"File": 16400} +b: + # NES PRG bank:address + {"Prg": [0x01, 0x8000]} +c: {"Chr": [0x00, 0x1000]} +"#; + #[test] fn test_nes_address() -> Result<()> { - let foo = Addresses { + let val = Addresses { a: NesAddress::File(0x4010), b: NesAddress::Prg(1, 0x8000), c: NesAddress::Chr(0, 0x1000), }; - let s = serde_yaml::to_string(&foo)?; - println!("{}", s); + let s = serde_yaml::to_string(&val)?; + let d = serde_yaml::from_str::(&s)?; + assert_eq!(s, ADDRESSES); + assert_eq!(d, val); Ok(()) } diff --git a/yamlformat_derive/src/attr.rs b/yamlformat_derive/src/attr.rs index 64f7c46c..210dcba8 100644 --- a/yamlformat_derive/src/attr.rs +++ b/yamlformat_derive/src/attr.rs @@ -1,5 +1,5 @@ use syn::parse::ParseStream; -use syn::{parenthesized, token, Attribute, Error, Ident, LitStr, Result, Token}; +use syn::{parenthesized, Attribute, Error, Ident, LitStr, Result, Token}; #[derive(Debug, PartialEq)] pub enum Format { @@ -45,7 +45,7 @@ pub fn get(input: &[Attribute]) -> Result { fn function_call(input: ParseStream) -> Result { let content; - let result = parenthesized!(content in input); + let _result = parenthesized!(content in input); Ok(content.is_empty()) } From caeae8a3df908a73ac5a9dabbdf803d2e3e6d144 Mon Sep 17 00:00:00 2001 From: Chris Frantz Date: Tue, 8 Feb 2022 09:56:21 -0800 Subject: [PATCH 4/4] Change `yaml-rust` to my github fork. Signed-off-by: Chris Frantz --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 019c58d1..8dfb484a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ ryu = "1.0" indexmap = "1.5" serde = "1.0.69" #yaml-rust = "0.4.5" -yaml-rust = {path = "../yaml-rust"} +yaml-rust = {git = "https://github.com/cfrantz/yaml-rust", branch="blocks_and_comments"} yamlformat_derive = {path = "yamlformat_derive"} inventory = "0.2" once_cell = "1.9"