diff --git a/src/item.rs b/src/item.rs index e3017aa5..ac0601f1 100644 --- a/src/item.rs +++ b/src/item.rs @@ -28,6 +28,7 @@ impl Item { self } } + // TODO: This should be generated by macro or derive /// Downcasting impl Item { @@ -128,19 +129,15 @@ impl Item { } /// In-place convert to a value pub fn make_value(&mut self) { - let mut other = Item::None; - std::mem::swap(self, &mut other); - let mut other = other.into_value().map(Item::Value).unwrap_or(Item::None); - std::mem::swap(self, &mut other); + let other = std::mem::take(self); + let other = other.into_value().map(Item::Value).unwrap_or(Item::None); + *self = other; } /// Casts `self` to table. pub fn into_table(self) -> Result { match self { Item::Table(t) => Ok(t), - Item::Value(v) => match v { - Value::InlineTable(t) => Ok(t.into_table()), - _ => Err(Item::Value(v)), - }, + Item::Value(Value::InlineTable(t)) => Ok(t.into_table()), _ => Err(self), } } @@ -148,9 +145,36 @@ impl Item { pub fn into_array_of_tables(self) -> Result { match self { Item::ArrayOfTables(a) => Ok(a), + Item::Value(Value::Array(a)) => { + if a.is_empty() { + Err(Item::Value(Value::Array(a))) + } else if a.iter().all(|v| v.is_inline_table()) { + let mut aot = ArrayOfTables::new(); + aot.values = a.values; + for value in aot.values.iter_mut() { + value.make_item(); + } + Ok(aot) + } else { + Err(Item::Value(Value::Array(a))) + } + } _ => Err(self), } } + // Starting private because the name is unclear + pub(crate) fn make_item(&mut self) { + let other = std::mem::take(self); + let other = match other.into_table().map(crate::Item::Table) { + Ok(i) => i, + Err(i) => i, + }; + let other = match other.into_array_of_tables().map(crate::Item::ArrayOfTables) { + Ok(i) => i, + Err(i) => i, + }; + *self = other; + } /// Returns true iff `self` is a value. pub fn is_value(&self) -> bool { self.as_value().is_some() diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 4f266541..86ea5625 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -5,6 +5,7 @@ mod array; mod item; mod key; +mod pretty; mod table; pub(crate) use array::*; @@ -12,6 +13,8 @@ pub(crate) use item::*; pub(crate) use key::*; pub(crate) use table::*; +use crate::visit_mut::VisitMut; + /// Errors that can occur when deserializing a type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Error { @@ -146,7 +149,9 @@ pub fn to_string_pretty(value: &T) -> Result where T: serde::ser::Serialize, { - to_document(value).map(|e| e.to_string()) + let mut document = to_document(value)?; + pretty::Pretty.visit_document_mut(&mut document); + Ok(document.to_string()) } /// Serialize the given data structure into a TOML document. diff --git a/src/ser/pretty.rs b/src/ser/pretty.rs new file mode 100644 index 00000000..fa4da4e3 --- /dev/null +++ b/src/ser/pretty.rs @@ -0,0 +1,56 @@ +pub(crate) struct Pretty; + +impl crate::visit_mut::VisitMut for Pretty { + fn visit_document_mut(&mut self, node: &mut crate::Document) { + crate::visit_mut::visit_document_mut(self, node); + if let Some((_, first)) = node.iter_mut().next() { + remove_table_prefix(first); + } + } + + fn visit_item_mut(&mut self, node: &mut crate::Item) { + node.make_item(); + + crate::visit_mut::visit_item_mut(self, node); + } + + fn visit_table_mut(&mut self, node: &mut crate::Table) { + node.decor_mut().clear(); + + crate::visit_mut::visit_table_mut(self, node); + } + + fn visit_value_mut(&mut self, node: &mut crate::Value) { + node.decor_mut().clear(); + + crate::visit_mut::visit_value_mut(self, node); + } + + fn visit_array_mut(&mut self, node: &mut crate::Array) { + crate::visit_mut::visit_array_mut(self, node); + + if (0..=1).contains(&node.len()) { + node.set_trailing(""); + node.set_trailing_comma(false); + } else { + for item in node.iter_mut() { + item.decor_mut().set_prefix("\n "); + } + node.set_trailing("\n"); + node.set_trailing_comma(true); + } + } +} + +fn remove_table_prefix(node: &mut crate::Item) { + match node { + crate::Item::None => {} + crate::Item::Value(_) => {} + crate::Item::Table(t) => t.decor_mut().set_prefix(""), + crate::Item::ArrayOfTables(a) => { + if let Some(first) = a.values.iter_mut().next() { + remove_table_prefix(first); + } + } + } +} diff --git a/tests/pretty.rs b/tests/pretty.rs new file mode 100644 index 00000000..40497c25 --- /dev/null +++ b/tests/pretty.rs @@ -0,0 +1,110 @@ +#![cfg(feature = "easy")] + +use pretty_assertions::assert_eq; + +const PRETTY_STD: &'static str = r#"[example] +array = [ + "item 1", + "item 2", +] +empty = [] +one = ["one"] +oneline = "this has no newlines." +text = """ +this is the first line +this is the second line +""" +"#; + +#[test] +fn pretty_std() { + let toml = PRETTY_STD; + let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap(); + let result = toml_edit::easy::to_string_pretty(&value).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_TRICKY: &'static str = r##"[example] +f = "\f" +glass = """ +Nothing too unusual, except that I can eat glass in: +- Greek: Μπορώ να φάω σπασμένα γυαλιά χωρίς να πάθω τίποτα. +- Polish: Mogę jeść szkło, i mi nie szkodzi. +- Hindi: मैं काँच खा सकता हूँ, मुझे उस से कोई पीडा नहीं होती. +- Japanese: 私はガラスを食べられます。それは私を傷つけません。 +""" +r = "\r" +r_newline = """ +\r +""" +single = "this is a single line but has \"\" cuz it\"s tricky" +single_tricky = "single line with ''' in it" +tabs = """ +this is pretty standard +\texcept for some \ttabs right here +""" +text = """ +this is the first line. +This has a ''' in it and \"\"\" cuz it's tricky yo +Also ' and \" because why not +this is the fourth line +""" +"##; + +#[test] +fn pretty_tricky() { + let toml = PRETTY_TRICKY; + let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap(); + let result = toml_edit::easy::to_string_pretty(&value).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const PRETTY_TABLE_ARRAY: &'static str = r##"[[array]] +key = "foo" + +[[array]] +key = "bar" + +[abc] +doc = "this is a table" + +[example] +single = "this is a single line string" +"##; + +#[test] +fn pretty_table_array() { + let toml = PRETTY_TABLE_ARRAY; + let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap(); + let result = toml_edit::easy::to_string_pretty(&value).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} + +const TABLE_ARRAY: &'static str = r##"[[array]] +key = "foo" + +[[array]] +key = "bar" + +[abc] +doc = "this is a table" + +[example] +single = "this is a single line string" +"##; + +#[test] +fn table_array() { + let toml = TABLE_ARRAY; + let value: toml_edit::easy::Value = toml_edit::easy::from_str(toml).unwrap(); + let result = toml_edit::easy::to_string_pretty(&value).unwrap(); + println!("EXPECTED:\n{}", toml); + println!("\nRESULT:\n{}", result); + assert_eq!(toml, &result); +} diff --git a/tests/serde.rs b/tests/serde.rs index d9557792..4d1d0d4b 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -29,9 +29,15 @@ macro_rules! equivalent { // Through a string equivalent println!("to_string(literal)"); - assert_eq!(t!(toml_edit::easy::to_string(&literal)), toml.to_string()); + assert_eq!( + t!(toml_edit::easy::to_string_pretty(&literal)), + toml.to_string() + ); println!("to_string(toml)"); - assert_eq!(t!(toml_edit::easy::to_string(&toml)), toml.to_string()); + assert_eq!( + t!(toml_edit::easy::to_string_pretty(&toml)), + toml.to_string() + ); println!("literal, from_str(toml)"); assert_eq!(literal, t!(toml_edit::easy::from_str(&toml.to_string()))); println!("toml, from_str(toml)");