diff options
Diffstat (limited to 'src')
34 files changed, 1099 insertions, 446 deletions
diff --git a/src/array.rs b/src/array.rs index 045b451..377f676 100644 --- a/src/array.rs +++ b/src/array.rs @@ -183,9 +183,11 @@ impl Array { /// # Examples /// /// ```rust + /// # #[cfg(feature = "parse")] { /// let formatted_value = "'literal'".parse::<toml_edit::Value>().unwrap(); /// let mut arr = toml_edit::Array::new(); /// arr.push_formatted(formatted_value); + /// # } /// ``` pub fn push_formatted(&mut self, v: Value) { self.values.push(Item::Value(v)); @@ -223,12 +225,14 @@ impl Array { /// # Examples /// /// ```rust + /// # #[cfg(feature = "parse")] { /// let mut arr = toml_edit::Array::new(); /// arr.push(1); /// arr.push("foo"); /// /// let formatted_value = "'start'".parse::<toml_edit::Value>().unwrap(); /// arr.insert_formatted(0, formatted_value); + /// # } /// ``` pub fn insert_formatted(&mut self, index: usize, v: Value) { self.values.insert(index, Item::Value(v)) @@ -269,12 +273,14 @@ impl Array { /// # Examples /// /// ```rust + /// # #[cfg(feature = "parse")] { /// let mut arr = toml_edit::Array::new(); /// arr.push(1); /// arr.push("foo"); /// /// let formatted_value = "'start'".parse::<toml_edit::Value>().unwrap(); /// arr.replace_formatted(0, formatted_value); + /// # } /// ``` pub fn replace_formatted(&mut self, index: usize, v: Value) -> Value { match mem::replace(&mut self.values[index], Item::Value(v)) { @@ -317,6 +323,56 @@ impl Array { .retain(|item| item.as_value().map(&mut keep).unwrap_or(false)); } + /// Sorts the slice with a comparator function. + /// + /// This sort is stable (i.e., does not reorder equal elements) and *O*(*n* \* log(*n*)) worst-case. + /// + /// The comparator function must define a total ordering for the elements in the slice. If + /// the ordering is not total, the order of the elements is unspecified. An order is a + /// total order if it is (for all `a`, `b` and `c`): + /// + /// * total and antisymmetric: exactly one of `a < b`, `a == b` or `a > b` is true, and + /// * transitive, `a < b` and `b < c` implies `a < c`. The same must hold for both `==` and `>`. + /// + /// For example, while [`f64`] doesn't implement [`Ord`] because `NaN != NaN`, we can use + /// `partial_cmp` as our sort function when we know the slice doesn't contain a `NaN`. + #[inline] + pub fn sort_by<F>(&mut self, mut compare: F) + where + F: FnMut(&Value, &Value) -> std::cmp::Ordering, + { + self.values.sort_by(move |lhs, rhs| { + let lhs = lhs.as_value(); + let rhs = rhs.as_value(); + match (lhs, rhs) { + (None, None) => std::cmp::Ordering::Equal, + (Some(_), None) => std::cmp::Ordering::Greater, + (None, Some(_)) => std::cmp::Ordering::Less, + (Some(lhs), Some(rhs)) => compare(lhs, rhs), + } + }) + } + + /// Sorts the array with a key extraction function. + /// + /// This sort is stable (i.e., does not reorder equal elements) and *O*(*m* \* *n* \* log(*n*)) + /// worst-case, where the key function is *O*(*m*). + #[inline] + pub fn sort_by_key<K, F>(&mut self, mut f: F) + where + F: FnMut(&Value) -> K, + K: Ord, + { + #[allow(clippy::manual_map)] // needed for lifetimes + self.values.sort_by_key(move |item| { + if let Some(value) = item.as_value() { + Some(f(value)) + } else { + None + } + }); + } + fn value_op<T>( &mut self, v: Value, @@ -333,9 +389,10 @@ impl Array { } } +#[cfg(feature = "display")] impl std::fmt::Display for Array { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - crate::encode::Encode::encode(self, f, None, ("", "")) + crate::encode::encode_array(self, f, None, ("", "")) } } diff --git a/src/array_of_tables.rs b/src/array_of_tables.rs index c4d7194..2e602a2 100644 --- a/src/array_of_tables.rs +++ b/src/array_of_tables.rs @@ -158,6 +158,7 @@ impl<'s> IntoIterator for &'s ArrayOfTables { } } +#[cfg(feature = "display")] impl std::fmt::Display for ArrayOfTables { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { // HACK: Without the header, we don't really have a proper way of printing this diff --git a/src/de/key.rs b/src/de/key.rs index 3da41df..a3b2825 100644 --- a/src/de/key.rs +++ b/src/de/key.rs @@ -62,9 +62,20 @@ impl<'de> serde::de::Deserializer<'de> for KeyDeserializer { self.deserialize_any(visitor) } + fn deserialize_newtype_struct<V>( + self, + _name: &'static str, + visitor: V, + ) -> Result<V::Value, Error> + where + V: serde::de::Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + serde::forward_to_deserialize_any! { bool u8 u16 u32 u64 i8 i16 i32 i64 f32 f64 char str string seq - bytes byte_buf map option unit newtype_struct + bytes byte_buf map option unit ignored_any unit_struct tuple_struct tuple identifier } } diff --git a/src/de/mod.rs b/src/de/mod.rs index 09ea120..9b8a2c7 100644 --- a/src/de/mod.rs +++ b/src/de/mod.rs @@ -87,6 +87,7 @@ impl From<Error> for crate::TomlError { impl std::error::Error for Error {} /// Convert a value into `T`. +#[cfg(feature = "parse")] pub fn from_str<T>(s: &'_ str) -> Result<T, Error> where T: DeserializeOwned, @@ -96,6 +97,7 @@ where } /// Convert a value into `T`. +#[cfg(feature = "parse")] pub fn from_slice<T>(s: &'_ [u8]) -> Result<T, Error> where T: DeserializeOwned, @@ -125,6 +127,7 @@ impl Deserializer { } } +#[cfg(feature = "parse")] impl std::str::FromStr for Deserializer { type Err = Error; diff --git a/src/de/table.rs b/src/de/table.rs index 0b6183e..33aa397 100644 --- a/src/de/table.rs +++ b/src/de/table.rs @@ -118,7 +118,7 @@ impl crate::InlineTable { pub(crate) struct TableMapAccess { iter: indexmap::map::IntoIter<crate::InternalString, crate::table::TableKeyValue>, span: Option<std::ops::Range<usize>>, - value: Option<(crate::InternalString, crate::Item)>, + value: Option<(crate::Key, crate::Item)>, } impl TableMapAccess { @@ -149,7 +149,7 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess { } e }); - self.value = Some((v.key.into(), v.value)); + self.value = Some((v.key, v.value)); ret } None => Ok(None), @@ -162,13 +162,13 @@ impl<'de> serde::de::MapAccess<'de> for TableMapAccess { { match self.value.take() { Some((k, v)) => { - let span = v.span(); + let span = v.span().or_else(|| k.span()); seed.deserialize(crate::de::ValueDeserializer::new(v)) .map_err(|mut e: Self::Error| { if e.span().is_none() { e.set_span(span); } - e.add_key(k.as_str().to_owned()); + e.add_key(k.get().to_owned()); e }) } diff --git a/src/de/table_enum.rs b/src/de/table_enum.rs index 197ad6e..0ceeab6 100644 --- a/src/de/table_enum.rs +++ b/src/de/table_enum.rs @@ -16,6 +16,20 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { fn unit_variant(self) -> Result<(), Self::Error> { match self.value { + crate::Item::ArrayOfTables(values) => { + if values.is_empty() { + Ok(()) + } else { + Err(Error::custom("expected empty array", values.span())) + } + } + crate::Item::Value(crate::Value::Array(values)) => { + if values.is_empty() { + Ok(()) + } else { + Err(Error::custom("expected empty table", values.span())) + } + } crate::Item::Table(values) => { if values.is_empty() { Ok(()) @@ -49,9 +63,41 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { V: serde::de::Visitor<'de>, { match self.value { + crate::Item::ArrayOfTables(values) => { + let values_span = values.span(); + let tuple_values = values.values.into_iter().collect::<Vec<_>>(); + + if tuple_values.len() == len { + serde::de::Deserializer::deserialize_seq( + super::ArrayDeserializer::new(tuple_values, values_span), + visitor, + ) + } else { + Err(Error::custom( + format!("expected tuple with length {}", len), + values_span, + )) + } + } + crate::Item::Value(crate::Value::Array(values)) => { + let values_span = values.span(); + let tuple_values = values.values.into_iter().collect::<Vec<_>>(); + + if tuple_values.len() == len { + serde::de::Deserializer::deserialize_seq( + super::ArrayDeserializer::new(tuple_values, values_span), + visitor, + ) + } else { + Err(Error::custom( + format!("expected tuple with length {}", len), + values_span, + )) + } + } crate::Item::Table(values) => { let values_span = values.span(); - let tuple_values = values + let tuple_values: Result<Vec<_>, _> = values .items .into_iter() .enumerate() @@ -68,17 +114,8 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { )), }, ) - // Fold all values into a `Vec`, or return the first error. - .fold(Ok(Vec::with_capacity(len)), |result, value_result| { - result.and_then(move |mut tuple_values| match value_result { - Ok(value) => { - tuple_values.push(value); - Ok(tuple_values) - } - // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>` - Err(e) => Err(e), - }) - })?; + .collect(); + let tuple_values = tuple_values?; if tuple_values.len() == len { serde::de::Deserializer::deserialize_seq( @@ -94,7 +131,7 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { } crate::Item::Value(crate::Value::InlineTable(values)) => { let values_span = values.span(); - let tuple_values = values + let tuple_values: Result<Vec<_>, _> = values .items .into_iter() .enumerate() @@ -111,17 +148,8 @@ impl<'de> serde::de::VariantAccess<'de> for TableEnumDeserializer { )), }, ) - // Fold all values into a `Vec`, or return the first error. - .fold(Ok(Vec::with_capacity(len)), |result, value_result| { - result.and_then(move |mut tuple_values| match value_result { - Ok(value) => { - tuple_values.push(value); - Ok(tuple_values) - } - // `Result<de::Value, Self::Error>` to `Result<Vec<_>, Self::Error>` - Err(e) => Err(e), - }) - })?; + .collect(); + let tuple_values = tuple_values?; if tuple_values.len() == len { serde::de::Deserializer::deserialize_seq( diff --git a/src/de/value.rs b/src/de/value.rs index 3984287..ba6ce6d 100644 --- a/src/de/value.rs +++ b/src/de/value.rs @@ -5,7 +5,7 @@ use crate::de::Error; /// Deserialization implementation for TOML [values][crate::Value]. /// -/// Can be creater either directly from TOML strings, using [`std::str::FromStr`], +/// Can be created either directly from TOML strings, using [`std::str::FromStr`], /// or from parsed [values][crate::Value] using [`serde::de::IntoDeserializer::into_deserializer`]. /// /// # Example @@ -241,6 +241,7 @@ impl crate::Item { } } +#[cfg(feature = "parse")] impl std::str::FromStr for ValueDeserializer { type Err = Error; diff --git a/src/document.rs b/src/document.rs index 67dd293..f20e61a 100644 --- a/src/document.rs +++ b/src/document.rs @@ -1,6 +1,5 @@ use std::str::FromStr; -use crate::parser; use crate::table::Iter; use crate::{Item, RawString, Table}; @@ -78,12 +77,13 @@ impl Default for Document { } } +#[cfg(feature = "parse")] impl FromStr for Document { type Err = crate::TomlError; /// Parses a document from a &str fn from_str(s: &str) -> Result<Self, Self::Err> { - let mut d = parser::parse_document(s)?; + let mut d = crate::parser::parse_document(s)?; d.despan(); Ok(d) } diff --git a/src/encode.rs b/src/encode.rs index 9940f28..30b153a 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -13,218 +13,185 @@ use crate::value::{ }; use crate::{Array, InlineTable, Item, Table, Value}; -pub(crate) trait Encode { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result; +pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result { + if let Some(input) = input { + let repr = this + .as_repr() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(this.default_repr())); + repr.encode(buf, input)?; + } else { + let repr = this.display_repr(); + write!(buf, "{}", repr)?; + }; + + Ok(()) } -impl Encode for Key { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - let decor = self.decor(); - decor.prefix_encode(buf, input, default_decor.0)?; - - if let Some(input) = input { - let repr = self - .as_repr() - .map(Cow::Borrowed) - .unwrap_or_else(|| Cow::Owned(self.default_repr())); - repr.encode(buf, input)?; +fn encode_key_path( + this: &[Key], + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + let leaf_decor = this.last().expect("always at least one key").leaf_decor(); + for (i, key) in this.iter().enumerate() { + let dotted_decor = key.dotted_decor(); + + let first = i == 0; + let last = i + 1 == this.len(); + + if first { + leaf_decor.prefix_encode(buf, input, default_decor.0)?; } else { - let repr = self.display_repr(); - write!(buf, "{}", repr)?; - }; + write!(buf, ".")?; + dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?; + } + + encode_key(key, buf, input)?; - decor.suffix_encode(buf, input, default_decor.1)?; - Ok(()) + if last { + leaf_decor.suffix_encode(buf, input, default_decor.1)?; + } else { + dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?; + } } + Ok(()) } -impl<'k> Encode for &'k [Key] { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - for (i, key) in self.iter().enumerate() { - let first = i == 0; - let last = i + 1 == self.len(); - - let prefix = if first { - default_decor.0 - } else { - DEFAULT_KEY_PATH_DECOR.0 - }; - let suffix = if last { - default_decor.1 - } else { - DEFAULT_KEY_PATH_DECOR.1 - }; +pub(crate) fn encode_key_path_ref( + this: &[&Key], + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + let leaf_decor = this.last().expect("always at least one key").leaf_decor(); + for (i, key) in this.iter().enumerate() { + let dotted_decor = key.dotted_decor(); - if !first { - write!(buf, ".")?; - } - key.encode(buf, input, (prefix, suffix))?; + let first = i == 0; + let last = i + 1 == this.len(); + + if first { + leaf_decor.prefix_encode(buf, input, default_decor.0)?; + } else { + write!(buf, ".")?; + dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?; } - Ok(()) - } -} -impl<'k> Encode for &'k [&'k Key] { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - for (i, key) in self.iter().enumerate() { - let first = i == 0; - let last = i + 1 == self.len(); - - let prefix = if first { - default_decor.0 - } else { - DEFAULT_KEY_PATH_DECOR.0 - }; - let suffix = if last { - default_decor.1 - } else { - DEFAULT_KEY_PATH_DECOR.1 - }; + encode_key(key, buf, input)?; - if !first { - write!(buf, ".")?; - } - key.encode(buf, input, (prefix, suffix))?; + if last { + leaf_decor.suffix_encode(buf, input, default_decor.1)?; + } else { + dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?; } - Ok(()) } + Ok(()) } -impl<T> Encode for Formatted<T> -where - T: ValueRepr, -{ - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - let decor = self.decor(); - decor.prefix_encode(buf, input, default_decor.0)?; - - if let Some(input) = input { - let repr = self - .as_repr() - .map(Cow::Borrowed) - .unwrap_or_else(|| Cow::Owned(self.default_repr())); - repr.encode(buf, input)?; - } else { - let repr = self.display_repr(); - write!(buf, "{}", repr)?; - }; +pub(crate) fn encode_formatted<T: ValueRepr>( + this: &Formatted<T>, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + let decor = this.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + + if let Some(input) = input { + let repr = this + .as_repr() + .map(Cow::Borrowed) + .unwrap_or_else(|| Cow::Owned(this.default_repr())); + repr.encode(buf, input)?; + } else { + let repr = this.display_repr(); + write!(buf, "{}", repr)?; + }; - decor.suffix_encode(buf, input, default_decor.1)?; - Ok(()) - } + decor.suffix_encode(buf, input, default_decor.1)?; + Ok(()) } -impl Encode for Array { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - let decor = self.decor(); - decor.prefix_encode(buf, input, default_decor.0)?; - write!(buf, "[")?; - - for (i, elem) in self.iter().enumerate() { - let inner_decor; - if i == 0 { - inner_decor = DEFAULT_LEADING_VALUE_DECOR; - } else { - inner_decor = DEFAULT_VALUE_DECOR; - write!(buf, ",")?; - } - elem.encode(buf, input, inner_decor)?; - } - if self.trailing_comma() && !self.is_empty() { +pub(crate) fn encode_array( + this: &Array, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + let decor = this.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "[")?; + + for (i, elem) in this.iter().enumerate() { + let inner_decor; + if i == 0 { + inner_decor = DEFAULT_LEADING_VALUE_DECOR; + } else { + inner_decor = DEFAULT_VALUE_DECOR; write!(buf, ",")?; } + encode_value(elem, buf, input, inner_decor)?; + } + if this.trailing_comma() && !this.is_empty() { + write!(buf, ",")?; + } - self.trailing().encode_with_default(buf, input, "")?; - write!(buf, "]")?; - decor.suffix_encode(buf, input, default_decor.1)?; + this.trailing().encode_with_default(buf, input, "")?; + write!(buf, "]")?; + decor.suffix_encode(buf, input, default_decor.1)?; - Ok(()) - } + Ok(()) } -impl Encode for InlineTable { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - let decor = self.decor(); - decor.prefix_encode(buf, input, default_decor.0)?; - write!(buf, "{{")?; - self.preamble().encode_with_default(buf, input, "")?; - - let children = self.get_values(); - let len = children.len(); - for (i, (key_path, value)) in children.into_iter().enumerate() { - if i != 0 { - write!(buf, ",")?; - } - let inner_decor = if i == len - 1 { - DEFAULT_TRAILING_VALUE_DECOR - } else { - DEFAULT_VALUE_DECOR - }; - key_path - .as_slice() - .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?; - write!(buf, "=")?; - value.encode(buf, input, inner_decor)?; +pub(crate) fn encode_table( + this: &InlineTable, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + let decor = this.decor(); + decor.prefix_encode(buf, input, default_decor.0)?; + write!(buf, "{{")?; + this.preamble().encode_with_default(buf, input, "")?; + + let children = this.get_values(); + let len = children.len(); + for (i, (key_path, value)) in children.into_iter().enumerate() { + if i != 0 { + write!(buf, ",")?; } + let inner_decor = if i == len - 1 { + DEFAULT_TRAILING_VALUE_DECOR + } else { + DEFAULT_VALUE_DECOR + }; + encode_key_path_ref(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR)?; + write!(buf, "=")?; + encode_value(value, buf, input, inner_decor)?; + } - write!(buf, "}}")?; - decor.suffix_encode(buf, input, default_decor.1)?; + write!(buf, "}}")?; + decor.suffix_encode(buf, input, default_decor.1)?; - Ok(()) - } + Ok(()) } -impl Encode for Value { - fn encode( - &self, - buf: &mut dyn Write, - input: Option<&str>, - default_decor: (&str, &str), - ) -> Result { - match self { - Value::String(repr) => repr.encode(buf, input, default_decor), - Value::Integer(repr) => repr.encode(buf, input, default_decor), - Value::Float(repr) => repr.encode(buf, input, default_decor), - Value::Boolean(repr) => repr.encode(buf, input, default_decor), - Value::Datetime(repr) => repr.encode(buf, input, default_decor), - Value::Array(array) => array.encode(buf, input, default_decor), - Value::InlineTable(table) => table.encode(buf, input, default_decor), - } +pub(crate) fn encode_value( + this: &Value, + buf: &mut dyn Write, + input: Option<&str>, + default_decor: (&str, &str), +) -> Result { + match this { + Value::String(repr) => encode_formatted(repr, buf, input, default_decor), + Value::Integer(repr) => encode_formatted(repr, buf, input, default_decor), + Value::Float(repr) => encode_formatted(repr, buf, input, default_decor), + Value::Boolean(repr) => encode_formatted(repr, buf, input, default_decor), + Value::Datetime(repr) => encode_formatted(repr, buf, input, default_decor), + Value::Array(array) => encode_array(array, buf, input, default_decor), + Value::InlineTable(table) => encode_table(table, buf, input, default_decor), } } @@ -275,11 +242,7 @@ where for kv in table.items.values() { match kv.value { Item::Table(ref t) => { - let mut key = kv.key.clone(); - if t.is_dotted() { - // May have newlines and generally isn't written for standard tables - key.decor_mut().clear(); - } + let key = kv.key.clone(); path.push(key); visit_nested_tables(t, path, false, callback)?; path.pop(); @@ -332,7 +295,7 @@ fn visit_table( }; table.decor.prefix_encode(buf, input, default_decor.0)?; write!(buf, "[[")?; - path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; + encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?; write!(buf, "]]")?; table.decor.suffix_encode(buf, input, default_decor.1)?; writeln!(buf)?; @@ -345,16 +308,16 @@ fn visit_table( }; table.decor.prefix_encode(buf, input, default_decor.0)?; write!(buf, "[")?; - path.encode(buf, input, DEFAULT_KEY_PATH_DECOR)?; + encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?; write!(buf, "]")?; table.decor.suffix_encode(buf, input, default_decor.1)?; writeln!(buf)?; } // print table body for (key_path, value) in children { - key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?; + encode_key_path_ref(&key_path, buf, input, DEFAULT_KEY_DECOR)?; write!(buf, "=")?; - value.encode(buf, input, DEFAULT_VALUE_DECOR)?; + encode_value(value, buf, input, DEFAULT_VALUE_DECOR)?; writeln!(buf)?; } Ok(()) @@ -390,7 +353,7 @@ pub(crate) fn to_string_repr( '\u{8}' => output.push_str("\\b"), '\u{9}' => output.push_str("\\t"), '\u{a}' => match style { - StringStyle::NewlineTripple => output.push('\n'), + StringStyle::NewlineTriple => output.push('\n'), StringStyle::OnelineSingle => output.push_str("\\n"), _ => unreachable!(), }, @@ -412,59 +375,56 @@ pub(crate) fn to_string_repr( #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum StringStyle { - NewlineTripple, - OnelineTripple, + NewlineTriple, + OnelineTriple, OnelineSingle, } impl StringStyle { fn literal_start(self) -> &'static str { match self { - Self::NewlineTripple => "'''\n", - Self::OnelineTripple => "'''", + Self::NewlineTriple => "'''\n", + Self::OnelineTriple => "'''", Self::OnelineSingle => "'", } } fn literal_end(self) -> &'static str { match self { - Self::NewlineTripple => "'''", - Self::OnelineTripple => "'''", + Self::NewlineTriple => "'''", + Self::OnelineTriple => "'''", Self::OnelineSingle => "'", } } fn standard_start(self) -> &'static str { match self { - Self::NewlineTripple => "\"\"\"\n", - // note: OnelineTripple can happen if do_pretty wants to do + Self::NewlineTriple => "\"\"\"\n", + // note: OnelineTriple can happen if do_pretty wants to do // '''it's one line''' // but literal == false - Self::OnelineTripple | Self::OnelineSingle => "\"", + Self::OnelineTriple | Self::OnelineSingle => "\"", } } fn standard_end(self) -> &'static str { match self { - Self::NewlineTripple => "\"\"\"", - // note: OnelineTripple can happen if do_pretty wants to do + Self::NewlineTriple => "\"\"\"", + // note: OnelineTriple can happen if do_pretty wants to do // '''it's one line''' // but literal == false - Self::OnelineTripple | Self::OnelineSingle => "\"", + Self::OnelineTriple | Self::OnelineSingle => "\"", } } } fn infer_style(value: &str) -> (StringStyle, bool) { - // For doing pretty prints we store in a new String - // because there are too many cases where pretty cannot - // work. We need to determine: + // We need to determine: // - if we are a "multi-line" pretty (if there are \n) // - if ['''] appears if multi or ['] if single // - if there are any invalid control characters // // Doing it any other way would require multiple passes // to determine if a pretty string works or not. - let mut out = String::with_capacity(value.len() * 2); let mut ty = StringStyle::OnelineSingle; // found consecutive single quotes let mut max_found_singles = 0; @@ -490,18 +450,17 @@ fn infer_style(value: &str) -> (StringStyle, bool) { '\\' => { prefer_literal = true; } - '\n' => ty = StringStyle::NewlineTripple, + '\n' => ty = StringStyle::NewlineTriple, // Escape codes are needed if any ascii control // characters are present, including \b \f \r. c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false, _ => {} } - out.push(ch); } else { // the string cannot be represented as pretty, // still check if it should be multiline if ch == '\n' { - ty = StringStyle::NewlineTripple; + ty = StringStyle::NewlineTriple; } } } @@ -513,7 +472,7 @@ fn infer_style(value: &str) -> (StringStyle, bool) { can_be_pretty = false; } if !can_be_pretty { - debug_assert!(ty != StringStyle::OnelineTripple); + debug_assert!(ty != StringStyle::OnelineTriple); return (ty, false); } if found_singles > max_found_singles { @@ -522,7 +481,7 @@ fn infer_style(value: &str) -> (StringStyle, bool) { debug_assert!(max_found_singles < 3); if ty == StringStyle::OnelineSingle && max_found_singles >= 1 { // no newlines, but must use ''' because it has ' in it - ty = StringStyle::OnelineTripple; + ty = StringStyle::OnelineTriple; } (ty, true) } diff --git a/src/parser/errors.rs b/src/error.rs index 859ed53..a983019 100644 --- a/src/parser/errors.rs +++ b/src/error.rs @@ -1,12 +1,6 @@ use std::error::Error as StdError; use std::fmt::{Display, Formatter, Result}; -use crate::parser::prelude::*; -use crate::Key; - -use winnow::error::ContextError; -use winnow::error::ParseError; - /// Type representing a TOML parse error #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct TomlError { @@ -17,7 +11,14 @@ pub struct TomlError { } impl TomlError { - pub(crate) fn new(error: ParseError<Input<'_>, ContextError>, mut original: Input<'_>) -> Self { + #[cfg(feature = "parse")] + pub(crate) fn new( + error: winnow::error::ParseError< + crate::parser::prelude::Input<'_>, + winnow::error::ContextError, + >, + mut original: crate::parser::prelude::Input<'_>, + ) -> Self { use winnow::stream::Stream; let offset = error.offset(); @@ -166,7 +167,6 @@ fn translate_position(input: &[u8], index: usize) -> (usize, usize) { None => 0, }; let line = input[0..line_start].iter().filter(|b| **b == b'\n').count(); - let line = line; let column = std::str::from_utf8(&input[line_start..=index]) .map(|s| s.chars().count() - 1) @@ -244,73 +244,3 @@ mod test_translate_position { assert_eq!(position, (1, 2)); } } - -#[derive(Debug, Clone)] -pub(crate) enum CustomError { - DuplicateKey { - key: String, - table: Option<Vec<Key>>, - }, - DottedKeyExtendWrongType { - key: Vec<Key>, - actual: &'static str, - }, - OutOfRange, - #[cfg_attr(feature = "unbounded", allow(dead_code))] - RecursionLimitExceeded, -} - -impl CustomError { - pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self { - assert!(i < path.len()); - let key = &path[i]; - let repr = key.display_repr(); - Self::DuplicateKey { - key: repr.into(), - table: Some(path[..i].to_vec()), - } - } - - pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self { - assert!(i < path.len()); - Self::DottedKeyExtendWrongType { - key: path[..=i].to_vec(), - actual, - } - } -} - -impl StdError for CustomError { - fn description(&self) -> &'static str { - "TOML parse error" - } -} - -impl Display for CustomError { - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - CustomError::DuplicateKey { key, table } => { - if let Some(table) = table { - if table.is_empty() { - write!(f, "duplicate key `{}` in document root", key) - } else { - let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join("."); - write!(f, "duplicate key `{}` in table `{}`", key, path) - } - } else { - write!(f, "duplicate key `{}`", key) - } - } - CustomError::DottedKeyExtendWrongType { key, actual } => { - let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join("."); - write!( - f, - "dotted key `{}` attempted to extend non-table type ({})", - path, actual - ) - } - CustomError::OutOfRange => write!(f, "value is out of range"), - CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceded"), - } - } -} diff --git a/src/inline_table.rs b/src/inline_table.rs index 3dc6c0c..316637d 100644 --- a/src/inline_table.rs +++ b/src/inline_table.rs @@ -11,6 +11,8 @@ use crate::{InternalString, Item, KeyMut, RawString, Table, Value}; pub struct InlineTable { // `preamble` represents whitespaces in an empty table preamble: RawString, + // Whether to hide an empty table + pub(crate) implicit: bool, // prefix before `{` and suffix after `}` decor: Decor, pub(crate) span: Option<std::ops::Range<usize>>, @@ -55,10 +57,10 @@ impl InlineTable { values } - pub(crate) fn append_values<'s, 'c>( + pub(crate) fn append_values<'s>( &'s self, parent: &[&'s Key], - values: &'c mut Vec<(Vec<&'s Key>, &'s Value)>, + values: &mut Vec<(Vec<&'s Key>, &'s Value)>, ) { for value in self.items.values() { let mut path = parent.to_vec(); @@ -133,6 +135,36 @@ impl InlineTable { } } + /// If a table has no key/value pairs and implicit, it will not be displayed. + /// + /// # Examples + /// + /// ```notrust + /// [target."x86_64/windows.json".dependencies] + /// ``` + /// + /// In the document above, tables `target` and `target."x86_64/windows.json"` are implicit. + /// + /// ``` + /// # #[cfg(feature = "parse")] { + /// # #[cfg(feature = "display")] { + /// use toml_edit::Document; + /// let mut doc = "[a]\n[a.b]\n".parse::<Document>().expect("invalid toml"); + /// + /// doc["a"].as_table_mut().unwrap().set_implicit(true); + /// assert_eq!(doc.to_string(), "[a.b]\n"); + /// # } + /// # } + /// ``` + pub(crate) fn set_implicit(&mut self, implicit: bool) { + self.implicit = implicit; + } + + /// If a table has no key/value pairs and implicit, it will not be displayed. + pub(crate) fn is_implicit(&self) -> bool { + self.implicit + } + /// Change this table's dotted status pub fn set_dotted(&mut self, yes: bool) { self.dotted = yes; @@ -153,14 +185,28 @@ impl InlineTable { &self.decor } + /// Returns an accessor to a key's formatting + pub fn key(&self, key: &str) -> Option<&'_ Key> { + self.items.get(key).map(|kv| &kv.key) + } + + /// Returns an accessor to a key's formatting + pub fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> { + self.items.get_mut(key).map(|kv| kv.key.as_mut()) + } + /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { - self.items.get_mut(key).map(|kv| &mut kv.key.decor) + #![allow(deprecated)] + self.items.get_mut(key).map(|kv| kv.key.leaf_decor_mut()) } /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] pub fn key_decor(&self, key: &str) -> Option<&Decor> { - self.items.get(key).map(|kv| &kv.key.decor) + #![allow(deprecated)] + self.items.get(key).map(|kv| kv.key.leaf_decor()) } /// Set whitespace after before element @@ -383,9 +429,10 @@ impl InlineTable { } } +#[cfg(feature = "display")] impl std::fmt::Display for InlineTable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - crate::encode::Encode::encode(self, f, None, ("", "")) + crate::encode::encode_table(self, f, None, ("", "")) } } @@ -436,13 +483,14 @@ impl<'s> IntoIterator for &'s InlineTable { } fn decorate_inline_table(table: &mut InlineTable) { - for (key_decor, value) in table + for (mut key, value) in table .items .iter_mut() - .filter(|&(_, ref kv)| kv.value.is_value()) - .map(|(_, kv)| (&mut kv.key.decor, kv.value.as_value_mut().unwrap())) + .filter(|(_, kv)| kv.value.is_value()) + .map(|(_, kv)| (kv.key.as_mut(), kv.value.as_value_mut().unwrap())) { - key_decor.clear(); + key.leaf_decor_mut().clear(); + key.dotted_decor_mut().clear(); value.decor_mut().clear(); } } @@ -530,10 +578,18 @@ impl TableLike for InlineTable { self.is_dotted() } + fn key(&self, key: &str) -> Option<&'_ Key> { + self.key(key) + } + fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> { + self.key_mut(key) + } fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { + #![allow(deprecated)] self.key_decor_mut(key) } fn key_decor(&self, key: &str) -> Option<&Decor> { + #![allow(deprecated)] self.key_decor(key) } } diff --git a/src/item.rs b/src/item.rs index 2025fd9..b58806e 100644 --- a/src/item.rs +++ b/src/item.rs @@ -7,9 +7,10 @@ use crate::table::TableLike; use crate::{Array, InlineTable, Table, Value}; /// Type representing either a value, a table, an array of tables, or none. -#[derive(Debug)] +#[derive(Debug, Default)] pub enum Item { /// Type representing none. + #[default] None, /// Type representing value. Value(Value), @@ -328,12 +329,7 @@ impl Clone for Item { } } -impl Default for Item { - fn default() -> Self { - Item::None - } -} - +#[cfg(feature = "parse")] impl FromStr for Item { type Err = crate::TomlError; @@ -344,6 +340,7 @@ impl FromStr for Item { } } +#[cfg(feature = "display")] impl std::fmt::Display for Item { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match &self { @@ -363,6 +360,7 @@ impl std::fmt::Display for Item { /// /// # Examples /// ```rust +/// # #[cfg(feature = "display")] { /// # use snapbox::assert_eq; /// # use toml_edit::*; /// let mut table = Table::default(); @@ -377,6 +375,7 @@ impl std::fmt::Display for Item { /// key2 = 42 /// key3 = ["hello", '\, world'] /// "#); +/// # } /// ``` pub fn value<V: Into<Value>>(v: V) -> Item { Item::Value(v.into()) @@ -1,9 +1,6 @@ use std::borrow::Cow; use std::str::FromStr; -use crate::encode::{to_string_repr, StringStyle}; -use crate::parser; -use crate::parser::key::is_unquoted_char; use crate::repr::{Decor, Repr}; use crate::InternalString; @@ -33,7 +30,8 @@ use crate::InternalString; pub struct Key { key: InternalString, pub(crate) repr: Option<Repr>, - pub(crate) decor: Decor, + pub(crate) leaf_decor: Decor, + pub(crate) dotted_decor: Decor, } impl Key { @@ -42,13 +40,15 @@ impl Key { Self { key: key.into(), repr: None, - decor: Default::default(), + leaf_decor: Default::default(), + dotted_decor: Default::default(), } } /// Parse a TOML key expression /// /// Unlike `"".parse<Key>()`, this supports dotted keys. + #[cfg(feature = "parse")] pub fn parse(repr: &str) -> Result<Vec<Self>, crate::TomlError> { Self::try_parse_path(repr) } @@ -59,8 +59,20 @@ impl Key { } /// While creating the `Key`, add `Decor` to it - pub fn with_decor(mut self, decor: Decor) -> Self { - self.decor = decor; + #[deprecated(since = "0.21.1", note = "Replaced with `with_leaf_decor`")] + pub fn with_decor(self, decor: Decor) -> Self { + self.with_leaf_decor(decor) + } + + /// While creating the `Key`, add `Decor` to it for the line entry + pub fn with_leaf_decor(mut self, decor: Decor) -> Self { + self.leaf_decor = decor; + self + } + + /// While creating the `Key`, add `Decor` to it for between dots + pub fn with_dotted_decor(mut self, decor: Decor) -> Self { + self.dotted_decor = decor; self } @@ -84,11 +96,13 @@ impl Key { } /// Returns the default raw representation. + #[cfg(feature = "display")] pub fn default_repr(&self) -> Repr { to_key_repr(&self.key) } /// Returns a raw representation. + #[cfg(feature = "display")] pub fn display_repr(&self) -> Cow<'_, str> { self.as_repr() .and_then(|r| r.as_raw().as_str()) @@ -99,13 +113,35 @@ impl Key { } /// Returns the surrounding whitespace + #[deprecated(since = "0.21.1", note = "Replaced with `decor_mut`")] pub fn decor_mut(&mut self) -> &mut Decor { - &mut self.decor + self.leaf_decor_mut() + } + + /// Returns the surrounding whitespace for the line entry + pub fn leaf_decor_mut(&mut self) -> &mut Decor { + &mut self.leaf_decor + } + + /// Returns the surrounding whitespace for between dots + pub fn dotted_decor_mut(&mut self) -> &mut Decor { + &mut self.dotted_decor } /// Returns the surrounding whitespace + #[deprecated(since = "0.21.1", note = "Replaced with `decor`")] pub fn decor(&self) -> &Decor { - &self.decor + self.leaf_decor() + } + + /// Returns the surrounding whitespace for the line entry + pub fn leaf_decor(&self) -> &Decor { + &self.leaf_decor + } + + /// Returns the surrounding whitespace for between dots + pub fn dotted_decor(&self) -> &Decor { + &self.dotted_decor } /// Returns the location within the original document @@ -115,7 +151,8 @@ impl Key { } pub(crate) fn despan(&mut self, input: &str) { - self.decor.despan(input); + self.leaf_decor.despan(input); + self.dotted_decor.despan(input); if let Some(repr) = &mut self.repr { repr.despan(input) } @@ -123,18 +160,21 @@ impl Key { /// Auto formats the key. pub fn fmt(&mut self) { - self.repr = Some(to_key_repr(&self.key)); - self.decor.clear(); + self.repr = None; + self.leaf_decor.clear(); + self.dotted_decor.clear(); } + #[cfg(feature = "parse")] fn try_parse_simple(s: &str) -> Result<Key, crate::TomlError> { - let mut key = parser::parse_key(s)?; + let mut key = crate::parser::parse_key(s)?; key.despan(s); Ok(key) } + #[cfg(feature = "parse")] fn try_parse_path(s: &str) -> Result<Vec<Key>, crate::TomlError> { - let mut keys = parser::parse_key_path(s)?; + let mut keys = crate::parser::parse_key_path(s)?; for key in &mut keys { key.despan(s); } @@ -148,7 +188,8 @@ impl Clone for Key { Self { key: self.key.clone(), repr: self.repr.clone(), - decor: self.decor.clone(), + leaf_decor: self.leaf_decor.clone(), + dotted_decor: self.dotted_decor.clone(), } } } @@ -209,12 +250,14 @@ impl PartialEq<String> for Key { } } +#[cfg(feature = "display")] impl std::fmt::Display for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - crate::encode::Encode::encode(self, f, None, ("", "")) + crate::encode::encode_key(self, f, None) } } +#[cfg(feature = "parse")] impl FromStr for Key { type Err = crate::TomlError; @@ -226,11 +269,33 @@ impl FromStr for Key { } } +#[cfg(feature = "display")] fn to_key_repr(key: &str) -> Repr { - if key.as_bytes().iter().copied().all(is_unquoted_char) && !key.is_empty() { - Repr::new_unchecked(key) - } else { - to_string_repr(key, Some(StringStyle::OnelineSingle), Some(false)) + #[cfg(feature = "parse")] + { + if key + .as_bytes() + .iter() + .copied() + .all(crate::parser::key::is_unquoted_char) + && !key.is_empty() + { + Repr::new_unchecked(key) + } else { + crate::encode::to_string_repr( + key, + Some(crate::encode::StringStyle::OnelineSingle), + Some(false), + ) + } + } + #[cfg(not(feature = "parse"))] + { + crate::encode::to_string_repr( + key, + Some(crate::encode::StringStyle::OnelineSingle), + Some(false), + ) } } @@ -265,7 +330,7 @@ impl From<Key> for InternalString { } } -/// A mutable reference to a `Key` +/// A mutable reference to a `Key`'s formatting #[derive(Debug, Eq, PartialEq, PartialOrd, Ord, Hash)] pub struct KeyMut<'k> { key: &'k mut Key, @@ -283,25 +348,51 @@ impl<'k> KeyMut<'k> { } /// Returns the default raw representation. + #[cfg(feature = "display")] pub fn default_repr(&self) -> Repr { self.key.default_repr() } /// Returns a raw representation. + #[cfg(feature = "display")] pub fn display_repr(&self) -> Cow<str> { self.key.display_repr() } /// Returns the surrounding whitespace + #[deprecated(since = "0.21.1", note = "Replaced with `decor_mut`")] pub fn decor_mut(&mut self) -> &mut Decor { + #![allow(deprecated)] self.key.decor_mut() } + /// Returns the surrounding whitespace for the line entry + pub fn leaf_decor_mut(&mut self) -> &mut Decor { + self.key.leaf_decor_mut() + } + + /// Returns the surrounding whitespace for between dots + pub fn dotted_decor_mut(&mut self) -> &mut Decor { + self.key.dotted_decor_mut() + } + /// Returns the surrounding whitespace + #[deprecated(since = "0.21.1", note = "Replaced with `decor`")] pub fn decor(&self) -> &Decor { + #![allow(deprecated)] self.key.decor() } + /// Returns the surrounding whitespace for the line entry + pub fn leaf_decor(&self) -> &Decor { + self.key.leaf_decor() + } + + /// Returns the surrounding whitespace for between dots + pub fn dotted_decor(&self) -> &Decor { + self.key.dotted_decor() + } + /// Auto formats the key. pub fn fmt(&mut self) { self.key.fmt() @@ -337,6 +428,7 @@ impl<'s> PartialEq<String> for KeyMut<'s> { } } +#[cfg(feature = "display")] impl<'k> std::fmt::Display for KeyMut<'k> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.key, f) @@ -14,6 +14,8 @@ //! # Example //! //! ```rust +//! # #[cfg(feature = "parse")] { +//! # #[cfg(feature = "display")] { //! use toml_edit::{Document, value}; //! //! let toml = r#" @@ -32,47 +34,54 @@ //! c = { d = "hello" } //! "#; //! assert_eq!(doc.to_string(), expected); +//! # } +//! # } //! ``` //! //! ## Controlling formatting //! //! By default, values are created with default formatting //! ```rust +//! # #[cfg(feature = "display")] { //! let mut doc = toml_edit::Document::new(); //! doc["foo"] = toml_edit::value("bar"); //! let expected = r#"foo = "bar" //! "#; //! assert_eq!(doc.to_string(), expected); +//! # } //! ``` //! //! You can choose a custom TOML representation by parsing the value. //! ```rust +//! # #[cfg(feature = "display")] { //! let mut doc = toml_edit::Document::new(); //! doc["foo"] = "'bar'".parse::<toml_edit::Item>().unwrap(); //! let expected = r#"foo = 'bar' //! "#; //! assert_eq!(doc.to_string(), expected); +//! # } //! ``` //! //! ## Limitations //! //! Things it does not preserve: //! -//! * Scattered array of tables (tables are reordered by default, see [test]). -//! * Order of dotted keys, see [issue](https://github.com/ordian/toml_edit/issues/163). +//! * Order of dotted keys, see [issue](https://github.com/toml-rs/toml/issues/163). //! //! [`toml`]: https://docs.rs/toml/latest/toml/ -//! [test]: https://github.com/ordian/toml_edit/blob/f09bd5d075fdb7d2ef8d9bb3270a34506c276753/tests/test_valid.rs#L84 mod array; mod array_of_tables; mod document; +#[cfg(feature = "display")] mod encode; +mod error; mod index; mod inline_table; mod internal_string; mod item; mod key; +#[cfg(feature = "parse")] mod parser; mod raw_string; mod repr; @@ -92,6 +101,7 @@ pub use crate::array_of_tables::{ ArrayOfTables, ArrayOfTablesIntoIter, ArrayOfTablesIter, ArrayOfTablesIterMut, }; pub use crate::document::Document; +pub use crate::error::TomlError; pub use crate::inline_table::{ InlineEntry, InlineOccupiedEntry, InlineTable, InlineTableIntoIter, InlineTableIter, InlineTableIterMut, InlineVacantEntry, @@ -99,7 +109,6 @@ pub use crate::inline_table::{ pub use crate::internal_string::InternalString; pub use crate::item::{array, table, value, Item}; pub use crate::key::{Key, KeyMut}; -pub use crate::parser::TomlError; pub use crate::raw_string::RawString; pub use crate::repr::{Decor, Formatted, Repr}; pub use crate::table::{ diff --git a/src/parser/array.rs b/src/parser/array.rs index e3b1f3f..0783191 100644 --- a/src/parser/array.rs +++ b/src/parser/array.rs @@ -81,6 +81,8 @@ pub(crate) fn array_value<'i>( } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; diff --git a/src/parser/datetime.rs b/src/parser/datetime.rs index 6e89b97..945dc69 100644 --- a/src/parser/datetime.rs +++ b/src/parser/datetime.rs @@ -1,6 +1,6 @@ use std::ops::RangeInclusive; -use crate::parser::errors::CustomError; +use crate::parser::error::CustomError; use crate::parser::prelude::*; use crate::parser::trivia::from_utf8_unchecked; @@ -9,6 +9,7 @@ use winnow::combinator::alt; use winnow::combinator::cut_err; use winnow::combinator::opt; use winnow::combinator::preceded; +use winnow::stream::Stream as _; use winnow::token::one_of; use winnow::token::take_while; use winnow::trace::trace; @@ -53,12 +54,35 @@ pub(crate) fn date_time(input: &mut Input<'_>) -> PResult<Datetime> { // full-date = date-fullyear "-" date-month "-" date-mday pub(crate) fn full_date(input: &mut Input<'_>) -> PResult<Date> { - trace( - "full-date", - (date_fullyear, b'-', cut_err((date_month, b'-', date_mday))) - .map(|(year, _, (month, _, day))| Date { year, month, day }), - ) - .parse_next(input) + trace("full-date", full_date_).parse_next(input) +} + +fn full_date_(input: &mut Input<'_>) -> PResult<Date> { + let year = date_fullyear.parse_next(input)?; + let _ = b'-'.parse_next(input)?; + let month = cut_err(date_month).parse_next(input)?; + let _ = cut_err(b'-').parse_next(input)?; + let day_start = input.checkpoint(); + let day = cut_err(date_mday).parse_next(input)?; + + let is_leap_year = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + let max_days_in_month = match month { + 2 if is_leap_year => 29, + 2 => 28, + 4 | 6 | 9 | 11 => 30, + _ => 31, + }; + if max_days_in_month < day { + input.reset(day_start); + return Err(winnow::error::ErrMode::from_external_error( + input, + winnow::error::ErrorKind::Verify, + CustomError::OutOfRange, + ) + .cut()); + } + + Ok(Date { year, month, day }) } // partial-time = time-hour ":" time-minute ":" time-second [time-secfrac] @@ -239,6 +263,8 @@ pub(crate) fn unsigned_digits<'i, const MIN: usize, const MAX: usize>( const DIGIT: RangeInclusive<u8> = b'0'..=b'9'; #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; diff --git a/src/parser/error.rs b/src/parser/error.rs new file mode 100644 index 0000000..22e8e66 --- /dev/null +++ b/src/parser/error.rs @@ -0,0 +1,87 @@ +use std::error::Error as StdError; +use std::fmt::{Display, Formatter, Result}; + +use crate::Key; + +#[derive(Debug, Clone)] +pub(crate) enum CustomError { + DuplicateKey { + key: String, + table: Option<Vec<Key>>, + }, + DottedKeyExtendWrongType { + key: Vec<Key>, + actual: &'static str, + }, + OutOfRange, + #[cfg_attr(feature = "unbounded", allow(dead_code))] + RecursionLimitExceeded, +} + +impl CustomError { + pub(crate) fn duplicate_key(path: &[Key], i: usize) -> Self { + assert!(i < path.len()); + let key = &path[i]; + let repr = key + .as_repr() + .and_then(|key| key.as_raw().as_str()) + .map(|s| s.to_owned()) + .unwrap_or_else(|| { + #[cfg(feature = "display")] + { + key.default_repr().as_raw().as_str().unwrap().to_owned() + } + #[cfg(not(feature = "display"))] + { + format!("{:?}", key.get()) + } + }); + Self::DuplicateKey { + key: repr, + table: Some(path[..i].to_vec()), + } + } + + pub(crate) fn extend_wrong_type(path: &[Key], i: usize, actual: &'static str) -> Self { + assert!(i < path.len()); + Self::DottedKeyExtendWrongType { + key: path[..=i].to_vec(), + actual, + } + } +} + +impl StdError for CustomError { + fn description(&self) -> &'static str { + "TOML parse error" + } +} + +impl Display for CustomError { + fn fmt(&self, f: &mut Formatter<'_>) -> Result { + match self { + CustomError::DuplicateKey { key, table } => { + if let Some(table) = table { + if table.is_empty() { + write!(f, "duplicate key `{}` in document root", key) + } else { + let path = table.iter().map(|k| k.get()).collect::<Vec<_>>().join("."); + write!(f, "duplicate key `{}` in table `{}`", key, path) + } + } else { + write!(f, "duplicate key `{}`", key) + } + } + CustomError::DottedKeyExtendWrongType { key, actual } => { + let path = key.iter().map(|k| k.get()).collect::<Vec<_>>().join("."); + write!( + f, + "dotted key `{}` attempted to extend non-table type ({})", + path, actual + ) + } + CustomError::OutOfRange => write!(f, "value is out of range"), + CustomError::RecursionLimitExceeded => write!(f, "recursion limit exceeded"), + } + } +} diff --git a/src/parser/inline_table.rs b/src/parser/inline_table.rs index 994e003..c2e6619 100644 --- a/src/parser/inline_table.rs +++ b/src/parser/inline_table.rs @@ -5,7 +5,7 @@ use winnow::token::one_of; use winnow::trace::trace; use crate::key::Key; -use crate::parser::errors::CustomError; +use crate::parser::error::CustomError; use crate::parser::key::key; use crate::parser::prelude::*; use crate::parser::trivia::ws; @@ -44,6 +44,16 @@ fn table_from_pairs( for (path, kv) in v { let table = descend_path(&mut root, &path)?; + + // "Likewise, using dotted keys to redefine tables already defined in [table] form is not allowed" + let mixed_table_types = table.is_dotted() == path.is_empty(); + if mixed_table_types { + return Err(CustomError::DuplicateKey { + key: kv.key.get().into(), + table: None, + }); + } + let key: InternalString = kv.key.get_internal().into(); match table.items.entry(key) { Entry::Vacant(o) => { @@ -64,15 +74,26 @@ fn descend_path<'a>( mut table: &'a mut InlineTable, path: &'a [Key], ) -> Result<&'a mut InlineTable, CustomError> { + let dotted = !path.is_empty(); for (i, key) in path.iter().enumerate() { let entry = table.entry_format(key).or_insert_with(|| { let mut new_table = InlineTable::new(); - new_table.set_dotted(true); + new_table.set_implicit(dotted); + new_table.set_dotted(dotted); Value::InlineTable(new_table) }); match *entry { Value::InlineTable(ref mut sweet_child_of_mine) => { + // Since tables cannot be defined more than once, redefining such tables using a + // [table] header is not allowed. Likewise, using dotted keys to redefine tables + // already defined in [table] form is not allowed. + if dotted && !sweet_child_of_mine.is_implicit() { + return Err(CustomError::DuplicateKey { + key: key.get().into(), + table: None, + }); + } table = sweet_child_of_mine; } ref v => { @@ -144,6 +165,8 @@ fn keyval<'i>( } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; diff --git a/src/parser/key.rs b/src/parser/key.rs index 12715da..e72b195 100644 --- a/src/parser/key.rs +++ b/src/parser/key.rs @@ -7,7 +7,7 @@ use winnow::token::take_while; use winnow::trace::trace; use crate::key::Key; -use crate::parser::errors::CustomError; +use crate::parser::error::CustomError; use crate::parser::prelude::*; use crate::parser::strings::{basic_string, literal_string}; use crate::parser::trivia::{from_utf8_unchecked, ws}; @@ -18,13 +18,13 @@ use crate::RawString; // key = simple-key / dotted-key // dotted-key = simple-key 1*( dot-sep simple-key ) pub(crate) fn key(input: &mut Input<'_>) -> PResult<Vec<Key>> { - trace( + let mut key_path = trace( "dotted-key", separated1( (ws.span(), simple_key, ws.span()).map(|(pre, (raw, key), suffix)| { Key::new(key) .with_repr_unchecked(Repr::new_unchecked(raw)) - .with_decor(Decor::new( + .with_dotted_decor(Decor::new( RawString::with_span(pre), RawString::with_span(suffix), )) @@ -38,7 +38,31 @@ pub(crate) fn key(input: &mut Input<'_>) -> PResult<Vec<Key>> { Ok::<_, CustomError>(k) }), ) - .parse_next(input) + .parse_next(input)?; + + let mut leaf_decor = Decor::new("", ""); + { + let first_dotted_decor = key_path + .first_mut() + .expect("always at least one key") + .dotted_decor_mut(); + if let Some(prefix) = first_dotted_decor.prefix().cloned() { + leaf_decor.set_prefix(prefix); + first_dotted_decor.set_prefix(""); + } + } + let last_key = &mut key_path.last_mut().expect("always at least one key"); + { + let last_dotted_decor = last_key.dotted_decor_mut(); + if let Some(suffix) = last_dotted_decor.suffix().cloned() { + leaf_decor.set_suffix(suffix); + last_dotted_decor.set_suffix(""); + } + } + + *last_key.leaf_decor_mut() = leaf_decor; + + Ok(key_path) } // simple-key = quoted-key / unquoted-key @@ -88,6 +112,8 @@ const UNQUOTED_CHAR: ( const DOT_SEP: u8 = b'.'; #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; @@ -96,7 +122,7 @@ mod test { let cases = [ ("a", "a"), (r#""hello\n ""#, "hello\n "), - (r#"'hello\n '"#, "hello\\n "), + (r"'hello\n '", "hello\\n "), ]; for (input, expected) in cases { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1b3cc4f..e032202 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod array; pub(crate) mod datetime; pub(crate) mod document; -pub(crate) mod errors; +pub(crate) mod error; pub(crate) mod inline_table; pub(crate) mod key; pub(crate) mod numbers; @@ -13,7 +13,7 @@ pub(crate) mod table; pub(crate) mod trivia; pub(crate) mod value; -pub use errors::TomlError; +pub use crate::error::TomlError; pub(crate) fn parse_document(raw: &str) -> Result<crate::Document, TomlError> { use prelude::*; @@ -95,11 +95,11 @@ pub(crate) mod prelude { #[cfg(not(feature = "unbounded"))] impl RecursionCheck { - pub(crate) fn check_depth(depth: usize) -> Result<(), super::errors::CustomError> { + pub(crate) fn check_depth(depth: usize) -> Result<(), super::error::CustomError> { if depth < 128 { Ok(()) } else { - Err(super::errors::CustomError::RecursionLimitExceeded) + Err(super::error::CustomError::RecursionLimitExceeded) } } @@ -114,7 +114,7 @@ pub(crate) mod prelude { Err(winnow::error::ErrMode::from_external_error( input, winnow::error::ErrorKind::Eof, - super::errors::CustomError::RecursionLimitExceeded, + super::error::CustomError::RecursionLimitExceeded, )) } } @@ -126,7 +126,7 @@ pub(crate) mod prelude { #[cfg(feature = "unbounded")] impl RecursionCheck { - pub(crate) fn check_depth(_depth: usize) -> Result<(), super::errors::CustomError> { + pub(crate) fn check_depth(_depth: usize) -> Result<(), super::error::CustomError> { Ok(()) } @@ -140,6 +140,8 @@ pub(crate) mod prelude { } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; @@ -182,10 +184,10 @@ hosts = [ "omega" ] - 'some.wierd .stuff' = """ + 'some.weird .stuff' = """ like that - # """ # this broke my sintax highlighting + # """ # this broke my syntax highlighting " also. like " = ''' that ''' diff --git a/src/parser/numbers.rs b/src/parser/numbers.rs index 6e4757f..9681526 100644 --- a/src/parser/numbers.rs +++ b/src/parser/numbers.rs @@ -301,7 +301,7 @@ pub(crate) fn inf(input: &mut Input<'_>) -> PResult<f64> { const INF: &[u8] = b"inf"; // nan = %x6e.61.6e ; nan pub(crate) fn nan(input: &mut Input<'_>) -> PResult<f64> { - tag(NAN).value(f64::NAN).parse_next(input) + tag(NAN).value(f64::NAN.copysign(1.0)).parse_next(input) } const NAN: &[u8] = b"nan"; @@ -319,6 +319,8 @@ pub(crate) const HEXDIG: (RangeInclusive<u8>, RangeInclusive<u8>, RangeInclusive (DIGIT, b'A'..=b'F', b'a'..=b'f'); #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; @@ -353,6 +355,7 @@ mod test { fn assert_float_eq(actual: f64, expected: f64) { if expected.is_nan() { assert!(actual.is_nan()); + assert_eq!(expected.is_sign_positive(), actual.is_sign_positive()); } else if expected.is_infinite() { assert!(actual.is_infinite()); assert_eq!(expected.is_sign_positive(), actual.is_sign_positive()); @@ -376,9 +379,9 @@ mod test { ("9_224_617.445_991_228_313", 9_224_617.445_991_227), ("-1.7976931348623157e+308", std::f64::MIN), ("1.7976931348623157e+308", std::f64::MAX), - ("nan", f64::NAN), - ("+nan", f64::NAN), - ("-nan", f64::NAN), + ("nan", f64::NAN.copysign(1.0)), + ("+nan", f64::NAN.copysign(1.0)), + ("-nan", f64::NAN.copysign(-1.0)), ("inf", f64::INFINITY), ("+inf", f64::INFINITY), ("-inf", f64::NEG_INFINITY), diff --git a/src/parser/state.rs b/src/parser/state.rs index efa884d..187dd5f 100644 --- a/src/parser/state.rs +++ b/src/parser/state.rs @@ -1,5 +1,5 @@ use crate::key::Key; -use crate::parser::errors::CustomError; +use crate::parser::error::CustomError; use crate::repr::Decor; use crate::table::TableKeyValue; use crate::{ArrayOfTables, Document, InternalString, Item, RawString, Table}; @@ -39,26 +39,21 @@ impl ParseState { pub(crate) fn on_keyval( &mut self, - mut path: Vec<Key>, + path: Vec<Key>, mut kv: TableKeyValue, ) -> Result<(), CustomError> { { let mut prefix = self.trailing.take(); - let first_key = if path.is_empty() { - &mut kv.key - } else { - &mut path[0] - }; let prefix = match ( prefix.take(), - first_key.decor.prefix().and_then(|d| d.span()), + kv.key.leaf_decor.prefix().and_then(|d| d.span()), ) { (Some(p), Some(k)) => Some(p.start..k.end), (Some(p), None) | (None, Some(p)) => Some(p), (None, None) => None, }; - first_key - .decor + kv.key + .leaf_decor .set_prefix(prefix.map(RawString::with_span).unwrap_or_default()); } @@ -94,7 +89,7 @@ impl ParseState { Ok(()) } - pub(crate) fn start_aray_table( + pub(crate) fn start_array_table( &mut self, path: Vec<Key>, decor: Decor, @@ -217,9 +212,9 @@ impl ParseState { Ok(()) } - pub(crate) fn descend_path<'t, 'k>( + pub(crate) fn descend_path<'t>( mut table: &'t mut Table, - path: &'k [Key], + path: &[Key], dotted: bool, ) -> Result<&'t mut Table, CustomError> { for (i, key) in path.iter().enumerate() { @@ -297,7 +292,7 @@ impl ParseState { .take() .map(RawString::with_span) .unwrap_or_default(); - self.start_aray_table( + self.start_array_table( path, Decor::new(leading, RawString::with_span(trailing)), span, diff --git a/src/parser/strings.rs b/src/parser/strings.rs index 26f9cc2..675b5c6 100644 --- a/src/parser/strings.rs +++ b/src/parser/strings.rs @@ -21,7 +21,7 @@ use winnow::token::tag; use winnow::token::take_while; use winnow::trace::trace; -use crate::parser::errors::CustomError; +use crate::parser::error::CustomError; use crate::parser::numbers::HEXDIG; use crate::parser::prelude::*; use crate::parser::trivia::{from_utf8_unchecked, newline, ws, ws_newlines, NON_ASCII, WSCHAR}; @@ -363,6 +363,8 @@ fn mll_quotes<'i>( } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; @@ -440,10 +442,10 @@ The quick brown \ #[test] fn literal_string() { let inputs = [ - r#"'C:\Users\nodejs\templates'"#, - r#"'\\ServerX\admin$\system32\'"#, + r"'C:\Users\nodejs\templates'", + r"'\\ServerX\admin$\system32\'", r#"'Tom "Dubs" Preston-Werner'"#, - r#"'<\i\c*\s*>'"#, + r"'<\i\c*\s*>'", ]; for input in &inputs { @@ -456,7 +458,7 @@ The quick brown \ #[test] fn ml_literal_string() { let inputs = [ - r#"'''I [dw]on't need \d{2} apples'''"#, + r"'''I [dw]on't need \d{2} apples'''", r#"''''one_quote''''"#, ]; for input in &inputs { diff --git a/src/parser/trivia.rs b/src/parser/trivia.rs index a359805..4575fb1 100644 --- a/src/parser/trivia.rs +++ b/src/parser/trivia.rs @@ -120,6 +120,8 @@ pub(crate) fn line_trailing(input: &mut Input<'_>) -> PResult<std::ops::Range<us } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; diff --git a/src/parser/value.rs b/src/parser/value.rs index 14cd951..33300ec 100644 --- a/src/parser/value.rs +++ b/src/parser/value.rs @@ -121,6 +121,8 @@ fn apply_raw(mut val: Value, span: std::ops::Range<usize>) -> Result<Value, std: } #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod test { use super::*; @@ -131,7 +133,7 @@ mod test { "-239", "1e200", "9_224_617.445_991_228_313", - r#"'''I [dw]on't need \d{2} apples'''"#, + r"'''I [dw]on't need \d{2} apples'''", r#"''' The first newline is trimmed in raw strings. diff --git a/src/raw_string.rs b/src/raw_string.rs index c5961f1..53714a1 100644 --- a/src/raw_string.rs +++ b/src/raw_string.rs @@ -80,6 +80,7 @@ impl RawString { } } + #[cfg(feature = "display")] pub(crate) fn encode(&self, buf: &mut dyn std::fmt::Write, input: &str) -> std::fmt::Result { let raw = self.to_str(input); for part in raw.split('\r') { @@ -88,6 +89,7 @@ impl RawString { Ok(()) } + #[cfg(feature = "display")] pub(crate) fn encode_with_default( &self, buf: &mut dyn std::fmt::Write, diff --git a/src/repr.rs b/src/repr.rs index d4ab6c2..ad41bbf 100644 --- a/src/repr.rs +++ b/src/repr.rs @@ -44,11 +44,13 @@ where } /// Returns the default raw representation. + #[cfg(feature = "display")] pub fn default_repr(&self) -> Repr { self.value.to_repr() } /// Returns a raw representation. + #[cfg(feature = "display")] pub fn display_repr(&self) -> Cow<str> { self.as_repr() .and_then(|r| r.as_raw().as_str()) @@ -82,7 +84,7 @@ where /// Auto formats the value. pub fn fmt(&mut self) { - self.repr = Some(self.value.to_repr()); + self.repr = None; } } @@ -103,20 +105,33 @@ where } } +#[cfg(feature = "display")] impl<T> std::fmt::Display for Formatted<T> where T: ValueRepr, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - crate::encode::Encode::encode(self, f, None, ("", "")) + crate::encode::encode_formatted(self, f, None, ("", "")) } } pub trait ValueRepr: crate::private::Sealed { /// The TOML representation of the value + #[cfg(feature = "display")] fn to_repr(&self) -> Repr; } +#[cfg(not(feature = "display"))] +mod inner { + use super::ValueRepr; + + impl ValueRepr for String {} + impl ValueRepr for i64 {} + impl ValueRepr for f64 {} + impl ValueRepr for bool {} + impl ValueRepr for toml_datetime::Datetime {} +} + /// TOML-encoded value #[derive(Eq, PartialEq, Clone, Hash)] pub struct Repr { @@ -144,6 +159,7 @@ impl Repr { self.raw_value.despan(input) } + #[cfg(feature = "display")] pub(crate) fn encode(&self, buf: &mut dyn std::fmt::Write, input: &str) -> std::fmt::Result { self.as_raw().encode(buf, input) } @@ -185,6 +201,7 @@ impl Decor { self.prefix.as_ref() } + #[cfg(feature = "display")] pub(crate) fn prefix_encode( &self, buf: &mut dyn std::fmt::Write, @@ -208,6 +225,7 @@ impl Decor { self.suffix.as_ref() } + #[cfg(feature = "display")] pub(crate) fn suffix_encode( &self, buf: &mut dyn std::fmt::Write, diff --git a/src/ser/map.rs b/src/ser/map.rs index d743e3d..47e56ba 100644 --- a/src/ser/map.rs +++ b/src/ser/map.rs @@ -1,4 +1,4 @@ -use super::{Error, KeySerializer}; +use super::{Error, KeySerializer, SerializeValueArray, ValueSerializer}; #[doc(hidden)] pub enum SerializeMap { @@ -165,7 +165,6 @@ impl serde::ser::SerializeMap for SerializeInlineTable { where T: serde::ser::Serialize, { - self.key = None; self.key = Some(input.serialize(KeySerializer)?); Ok(()) } @@ -174,7 +173,8 @@ impl serde::ser::SerializeMap for SerializeInlineTable { where T: serde::ser::Serialize, { - let res = value.serialize(super::ValueSerializer {}); + let mut value_serializer = MapValueSerializer::new(); + let res = value.serialize(&mut value_serializer); match res { Ok(item) => { let key = self.key.take().unwrap(); @@ -185,7 +185,7 @@ impl serde::ser::SerializeMap for SerializeInlineTable { self.items.insert(key, kv); } Err(e) => { - if e != Error::UnsupportedNone { + if !(e == Error::UnsupportedNone && value_serializer.is_none) { return Err(e); } } @@ -210,7 +210,8 @@ impl serde::ser::SerializeStruct for SerializeInlineTable { where T: serde::ser::Serialize, { - let res = value.serialize(super::ValueSerializer {}); + let mut value_serializer = MapValueSerializer::new(); + let res = value.serialize(&mut value_serializer); match res { Ok(item) => { let kv = crate::table::TableKeyValue::new( @@ -220,7 +221,7 @@ impl serde::ser::SerializeStruct for SerializeInlineTable { self.items.insert(crate::InternalString::from(key), kv); } Err(e) => { - if e != Error::UnsupportedNone { + if !(e == Error::UnsupportedNone && value_serializer.is_none) { return Err(e); } } @@ -403,3 +404,261 @@ impl serde::ser::Serializer for DatetimeFieldSerializer { Err(Error::DateInvalid) } } + +#[derive(Default)] +struct MapValueSerializer { + is_none: bool, +} + +impl MapValueSerializer { + fn new() -> Self { + Self { is_none: false } + } +} + +impl serde::ser::Serializer for &mut MapValueSerializer { + type Ok = crate::Value; + type Error = Error; + type SerializeSeq = super::SerializeValueArray; + type SerializeTuple = super::SerializeValueArray; + type SerializeTupleStruct = super::SerializeValueArray; + type SerializeTupleVariant = super::SerializeTupleVariant; + type SerializeMap = super::SerializeMap; + type SerializeStruct = super::SerializeMap; + type SerializeStructVariant = super::SerializeStructVariant; + + fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_bool(v) + } + + fn serialize_i8(self, v: i8) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_i8(v) + } + + fn serialize_i16(self, v: i16) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_i16(v) + } + + fn serialize_i32(self, v: i32) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_i32(v) + } + + fn serialize_i64(self, v: i64) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_i64(v) + } + + fn serialize_u8(self, v: u8) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_u8(v) + } + + fn serialize_u16(self, v: u16) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_u16(v) + } + + fn serialize_u32(self, v: u32) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_u32(v) + } + + fn serialize_u64(self, v: u64) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_u64(v) + } + + fn serialize_f32(self, v: f32) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_f32(v) + } + + fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_f64(v) + } + + fn serialize_char(self, v: char) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_char(v) + } + + fn serialize_str(self, v: &str) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_str(v) + } + + fn serialize_bytes(self, value: &[u8]) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_bytes(value) + } + + fn serialize_none(self) -> Result<Self::Ok, Self::Error> { + self.is_none = true; + Err(Error::UnsupportedNone) + } + + fn serialize_some<T: ?Sized>(self, value: &T) -> Result<Self::Ok, Self::Error> + where + T: serde::ser::Serialize, + { + ValueSerializer::new().serialize_some(value) + } + + fn serialize_unit(self) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_unit() + } + + fn serialize_unit_struct(self, name: &'static str) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_unit_struct(name) + } + + fn serialize_unit_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + ) -> Result<Self::Ok, Self::Error> { + ValueSerializer::new().serialize_unit_variant(name, variant_index, variant) + } + + fn serialize_newtype_struct<T: ?Sized>( + self, + name: &'static str, + value: &T, + ) -> Result<Self::Ok, Self::Error> + where + T: serde::ser::Serialize, + { + ValueSerializer::new().serialize_newtype_struct(name, value) + } + + fn serialize_newtype_variant<T: ?Sized>( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result<Self::Ok, Self::Error> + where + T: serde::ser::Serialize, + { + ValueSerializer::new().serialize_newtype_variant(name, variant_index, variant, value) + } + + fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> { + ValueSerializer::new().serialize_seq(len) + } + + fn serialize_tuple(self, len: usize) -> Result<Self::SerializeTuple, Self::Error> { + ValueSerializer::new().serialize_tuple(len) + } + + fn serialize_tuple_struct( + self, + name: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleStruct, Self::Error> { + ValueSerializer::new().serialize_tuple_struct(name, len) + } + + fn serialize_tuple_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<Self::SerializeTupleVariant, Self::Error> { + ValueSerializer::new().serialize_tuple_variant(name, variant_index, variant, len) + } + + fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { + ValueSerializer::new().serialize_map(len) + } + + fn serialize_struct( + self, + name: &'static str, + len: usize, + ) -> Result<Self::SerializeStruct, Self::Error> { + ValueSerializer::new().serialize_struct(name, len) + } + + fn serialize_struct_variant( + self, + name: &'static str, + variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result<Self::SerializeStructVariant, Self::Error> { + ValueSerializer::new().serialize_struct_variant(name, variant_index, variant, len) + } +} + +pub type SerializeTupleVariant = SerializeVariant<SerializeValueArray>; +pub type SerializeStructVariant = SerializeVariant<SerializeMap>; + +pub struct SerializeVariant<T> { + variant: &'static str, + inner: T, +} + +impl SerializeVariant<SerializeValueArray> { + pub(crate) fn tuple(variant: &'static str, len: usize) -> Self { + Self { + variant, + inner: SerializeValueArray::with_capacity(len), + } + } +} + +impl SerializeVariant<SerializeMap> { + pub(crate) fn struct_(variant: &'static str, len: usize) -> Self { + Self { + variant, + inner: SerializeMap::table_with_capacity(len), + } + } +} + +impl serde::ser::SerializeTupleVariant for SerializeVariant<SerializeValueArray> { + type Ok = crate::Value; + type Error = Error; + + fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<(), Error> + where + T: serde::ser::Serialize, + { + serde::ser::SerializeSeq::serialize_element(&mut self.inner, value) + } + + fn end(self) -> Result<Self::Ok, Self::Error> { + let inner = serde::ser::SerializeSeq::end(self.inner)?; + let mut items = crate::table::KeyValuePairs::new(); + let kv = crate::table::TableKeyValue::new( + crate::Key::new(self.variant), + crate::Item::Value(inner), + ); + items.insert(crate::InternalString::from(self.variant), kv); + Ok(crate::Value::InlineTable(crate::InlineTable::with_pairs( + items, + ))) + } +} + +impl serde::ser::SerializeStructVariant for SerializeVariant<SerializeMap> { + type Ok = crate::Value; + type Error = Error; + + #[inline] + fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error> + where + T: serde::ser::Serialize + ?Sized, + { + serde::ser::SerializeStruct::serialize_field(&mut self.inner, key, value) + } + + #[inline] + fn end(self) -> Result<Self::Ok, Self::Error> { + let inner = serde::ser::SerializeStruct::end(self.inner)?; + let mut items = crate::table::KeyValuePairs::new(); + let kv = crate::table::TableKeyValue::new( + crate::Key::new(self.variant), + crate::Item::Value(inner), + ); + items.insert(crate::InternalString::from(self.variant), kv); + Ok(crate::Value::InlineTable(crate::InlineTable::with_pairs( + items, + ))) + } +} diff --git a/src/ser/mod.rs b/src/ser/mod.rs index 2c31020..ba31708 100644 --- a/src/ser/mod.rs +++ b/src/ser/mod.rs @@ -24,7 +24,7 @@ pub enum Error { OutOfRange(Option<&'static str>), /// `None` could not be serialized to TOML UnsupportedNone, - /// Key was not convertable to `String` for serializing to TOML + /// Key was not convertible to `String` for serializing to TOML KeyNotString, /// A serialized date was invalid DateInvalid, @@ -84,6 +84,7 @@ impl std::error::Error for Error {} /// Serialization can fail if `T`'s implementation of `Serialize` decides to /// fail, if `T` contains a map with non-string keys, or if `T` attempts to /// serialize an unsupported datatype such as an enum, tuple, or tuple struct. +#[cfg(feature = "display")] pub fn to_vec<T: ?Sized>(value: &T) -> Result<Vec<u8>, Error> where T: serde::ser::Serialize, @@ -127,6 +128,7 @@ where /// let toml = toml_edit::ser::to_string(&config).unwrap(); /// println!("{}", toml) /// ``` +#[cfg(feature = "display")] pub fn to_string<T: ?Sized>(value: &T) -> Result<String, Error> where T: serde::ser::Serialize, @@ -138,6 +140,7 @@ where /// /// This is identical to `to_string` except the output string has a more /// "pretty" output. See `ValueSerializer::pretty` for more details. +#[cfg(feature = "display")] pub fn to_string_pretty<T: ?Sized>(value: &T) -> Result<String, Error> where T: serde::ser::Serialize, diff --git a/src/ser/value.rs b/src/ser/value.rs index d29390a..47a17a3 100644 --- a/src/ser/value.rs +++ b/src/ser/value.rs @@ -60,10 +60,10 @@ impl serde::ser::Serializer for ValueSerializer { type SerializeSeq = super::SerializeValueArray; type SerializeTuple = super::SerializeValueArray; type SerializeTupleStruct = super::SerializeValueArray; - type SerializeTupleVariant = super::SerializeValueArray; + type SerializeTupleVariant = super::SerializeTupleVariant; type SerializeMap = super::SerializeMap; type SerializeStruct = super::SerializeMap; - type SerializeStructVariant = serde::ser::Impossible<Self::Ok, Self::Error>; + type SerializeStructVariant = super::SerializeStructVariant; fn serialize_bool(self, v: bool) -> Result<Self::Ok, Self::Error> { Ok(v.into()) @@ -108,7 +108,17 @@ impl serde::ser::Serializer for ValueSerializer { self.serialize_f64(v as f64) } - fn serialize_f64(self, v: f64) -> Result<Self::Ok, Self::Error> { + fn serialize_f64(self, mut v: f64) -> Result<Self::Ok, Self::Error> { + // Discard sign of NaN when serialized using Serde. + // + // In all likelihood the sign of NaNs is not meaningful in the user's + // program. Ending up with `-nan` in the TOML document would usually be + // surprising and undesirable, when the sign of the NaN was not + // intentionally controlled by the caller, or may even be + // nondeterministic if it comes from arithmetic operations or a cast. + if v.is_nan() { + v = v.copysign(1.0); + } Ok(v.into()) } @@ -205,10 +215,10 @@ impl serde::ser::Serializer for ValueSerializer { self, _name: &'static str, _variant_index: u32, - _variant: &'static str, + variant: &'static str, len: usize, ) -> Result<Self::SerializeTupleVariant, Self::Error> { - self.serialize_seq(Some(len)) + Ok(super::SerializeTupleVariant::tuple(variant, len)) } fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap, Self::Error> { @@ -233,11 +243,11 @@ impl serde::ser::Serializer for ValueSerializer { fn serialize_struct_variant( self, - name: &'static str, + _name: &'static str, _variant_index: u32, - _variant: &'static str, - _len: usize, + variant: &'static str, + len: usize, ) -> Result<Self::SerializeStructVariant, Self::Error> { - Err(Error::UnsupportedType(Some(name))) + Ok(super::SerializeStructVariant::struct_(variant, len)) } } diff --git a/src/table.rs b/src/table.rs index 45d6d61..cc0d2b2 100644 --- a/src/table.rs +++ b/src/table.rs @@ -70,10 +70,10 @@ impl Table { values } - fn append_values<'s, 'c>( + fn append_values<'s>( &'s self, parent: &[&'s Key], - values: &'c mut Vec<(Vec<&'s Key>, &'s Value)>, + values: &mut Vec<(Vec<&'s Key>, &'s Value)>, ) { for value in self.items.values() { let mut path = parent.to_vec(); @@ -165,11 +165,15 @@ impl Table { /// In the document above, tables `target` and `target."x86_64/windows.json"` are implicit. /// /// ``` + /// # #[cfg(feature = "parse")] { + /// # #[cfg(feature = "display")] { /// use toml_edit::Document; /// let mut doc = "[a]\n[a.b]\n".parse::<Document>().expect("invalid toml"); /// /// doc["a"].as_table_mut().unwrap().set_implicit(true); /// assert_eq!(doc.to_string(), "[a.b]\n"); + /// # } + /// # } /// ``` pub fn set_implicit(&mut self, implicit: bool) { self.implicit = implicit; @@ -214,14 +218,28 @@ impl Table { &self.decor } + /// Returns an accessor to a key's formatting + pub fn key(&self, key: &str) -> Option<&'_ Key> { + self.items.get(key).map(|kv| &kv.key) + } + + /// Returns an accessor to a key's formatting + pub fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> { + self.items.get_mut(key).map(|kv| kv.key.as_mut()) + } + /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] pub fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { - self.items.get_mut(key).map(|kv| &mut kv.key.decor) + #![allow(deprecated)] + self.items.get_mut(key).map(|kv| kv.key.leaf_decor_mut()) } /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] pub fn key_decor(&self, key: &str) -> Option<&Decor> { - self.items.get(key).map(|kv| &kv.key.decor) + #![allow(deprecated)] + self.items.get(key).map(|kv| kv.key.leaf_decor()) } /// Returns the location within the original document @@ -413,15 +431,15 @@ impl Table { } } +#[cfg(feature = "display")] impl std::fmt::Display for Table { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use crate::encode::Encode; let children = self.get_values(); // print table body for (key_path, value) in children { - key_path.as_slice().encode(f, None, DEFAULT_KEY_DECOR)?; + crate::encode::encode_key_path_ref(&key_path, f, None, DEFAULT_KEY_DECOR)?; write!(f, "=")?; - value.encode(f, None, DEFAULT_VALUE_DECOR)?; + crate::encode::encode_value(value, f, None, DEFAULT_VALUE_DECOR)?; writeln!(f)?; } Ok(()) @@ -471,13 +489,14 @@ impl<'s> IntoIterator for &'s Table { pub(crate) type KeyValuePairs = IndexMap<InternalString, TableKeyValue>; fn decorate_table(table: &mut Table) { - for (key_decor, value) in table + for (mut key, value) in table .items .iter_mut() - .filter(|&(_, ref kv)| kv.value.is_value()) - .map(|(_, kv)| (&mut kv.key.decor, kv.value.as_value_mut().unwrap())) + .filter(|(_, kv)| kv.value.is_value()) + .map(|(_, kv)| (kv.key.as_mut(), kv.value.as_value_mut().unwrap())) { - key_decor.clear(); + key.leaf_decor_mut().clear(); + key.dotted_decor_mut().clear(); value.decor_mut().clear(); } } @@ -557,9 +576,15 @@ pub trait TableLike: crate::private::Sealed { /// Check if this is a wrapper for dotted keys, rather than a standard table fn is_dotted(&self) -> bool; + /// Returns an accessor to a key's formatting + fn key(&self, key: &str) -> Option<&'_ Key>; + /// Returns an accessor to a key's formatting + fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>>; /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor>; /// Returns the decor associated with a given key of the table. + #[deprecated(since = "0.21.1", note = "Replaced with `key_mut`")] fn key_decor(&self, key: &str) -> Option<&Decor>; } @@ -617,10 +642,18 @@ impl TableLike for Table { self.set_dotted(yes) } + fn key(&self, key: &str) -> Option<&'_ Key> { + self.key(key) + } + fn key_mut(&mut self, key: &str) -> Option<KeyMut<'_>> { + self.key_mut(key) + } fn key_decor_mut(&mut self, key: &str) -> Option<&mut Decor> { + #![allow(deprecated)] self.key_decor_mut(key) } fn key_decor(&self, key: &str) -> Option<&Decor> { + #![allow(deprecated)] self.key_decor(key) } } diff --git a/src/value.rs b/src/value.rs index f10da9a..f416434 100644 --- a/src/value.rs +++ b/src/value.rs @@ -4,7 +4,6 @@ use std::str::FromStr; use toml_datetime::*; use crate::key::Key; -use crate::parser; use crate::repr::{Decor, Formatted}; use crate::{Array, InlineTable, InternalString, RawString}; @@ -190,10 +189,12 @@ impl Value { /// Sets the prefix and the suffix for value. /// # Example /// ```rust + /// # #[cfg(feature = "display")] { /// let mut v = toml_edit::Value::from(42); /// assert_eq!(&v.to_string(), "42"); /// let d = v.decorated(" ", " "); /// assert_eq!(&d.to_string(), " 42 "); + /// # } /// ``` pub fn decorated(mut self, prefix: impl Into<RawString>, suffix: impl Into<RawString>) -> Self { self.decorate(prefix, suffix); @@ -231,12 +232,13 @@ impl Value { } } +#[cfg(feature = "parse")] impl FromStr for Value { type Err = crate::TomlError; /// Parses a value from a &str fn from_str(s: &str) -> Result<Self, Self::Err> { - parser::parse_value(s) + crate::parser::parse_value(s) } } @@ -284,6 +286,7 @@ impl From<i64> for Value { impl From<f64> for Value { fn from(f: f64) -> Self { + // Preserve sign of NaN. It may get written to TOML as `-nan`. Value::Float(Formatted::new(f)) } } @@ -346,9 +349,10 @@ impl<K: Into<Key>, V: Into<Value>> FromIterator<(K, V)> for Value { } } +#[cfg(feature = "display")] impl std::fmt::Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - crate::encode::Encode::encode(self, f, None, ("", "")) + crate::encode::encode_value(self, f, None, ("", "")) } } @@ -360,6 +364,8 @@ pub(crate) const DEFAULT_TRAILING_VALUE_DECOR: (&str, &str) = (" ", " "); pub(crate) const DEFAULT_LEADING_VALUE_DECOR: (&str, &str) = ("", ""); #[cfg(test)] +#[cfg(feature = "parse")] +#[cfg(feature = "display")] mod tests { use super::*; diff --git a/src/visit.rs b/src/visit.rs index 1bc640a..6d7be0b 100644 --- a/src/visit.rs +++ b/src/visit.rs @@ -43,6 +43,7 @@ //! This visitor stores every string in the document. //! //! ``` +//! # #[cfg(feature = "parse")] { //! # use toml_edit::*; //! use toml_edit::visit::*; //! @@ -67,10 +68,11 @@ //! visitor.visit_document(&document); //! //! assert_eq!(visitor.strings, vec!["sky-castle", "surrounds-you"]); +//! # } //! ``` //! //! For a more complex example where the visitor has internal state, see `examples/visit.rs` -//! [on GitHub](https://github.com/ordian/toml_edit/blob/master/examples/visit.rs). +//! [on GitHub](https://github.com/toml-rs/toml/blob/main/crates/toml_edit/examples/visit.rs). use crate::{ Array, ArrayOfTables, Datetime, Document, Formatted, InlineTable, Item, Table, TableLike, Value, diff --git a/src/visit_mut.rs b/src/visit_mut.rs index 2c2af97..c823cfb 100644 --- a/src/visit_mut.rs +++ b/src/visit_mut.rs @@ -45,6 +45,8 @@ //! 2 decimal points. //! //! ``` +//! # #[cfg(feature = "parse")] { +//! # #[cfg(feature = "display")] { //! # use toml_edit::*; //! use toml_edit::visit_mut::*; //! @@ -80,10 +82,12 @@ //! "#; //! //! assert_eq!(format!("{}", document), output); +//! # } +//! # } //! ``` //! //! For a more complex example where the visitor has internal state, see `examples/visit.rs` -//! [on GitHub](https://github.com/ordian/toml_edit/blob/master/examples/visit.rs). +//! [on GitHub](https://github.com/toml-rs/toml/blob/main/crates/toml_edit/examples/visit.rs). use crate::{ Array, ArrayOfTables, Datetime, Document, Formatted, InlineTable, Item, KeyMut, Table, |