diff --git a/src/encode.rs b/src/encode.rs index 76297e33..a8474666 100644 --- a/src/encode.rs +++ b/src/encode.rs @@ -86,23 +86,17 @@ impl Display for InlineTable { self.decor.prefix().unwrap_or(DEFAULT_VALUE_DECOR.0) )?; write!(f, "{}", self.preamble)?; - for (i, (key, value)) in self - .items - .iter() - .filter(|&(_, kv)| kv.value.is_value()) - .map(|(_, kv)| { - ( - kv.key.decor.display(&kv.key, DEFAULT_INLINE_KEY_DECOR), - kv.value.as_value().unwrap(), - ) - }) - .enumerate() - { + + let mut children = Vec::new(); + self.get_children(&mut children); + for (i, (key_path, value)) in children.into_iter().enumerate() { + let key = key_path_display(&key_path, DEFAULT_INLINE_KEY_DECOR); if i > 0 { write!(f, ",")?; } write!(f, "{}={}", key, value)?; } + write!( f, "}}{}", @@ -125,7 +119,7 @@ impl Table { for kv in self.items.values() { match kv.value { - Item::Table(ref t) => { + Item::Table(ref t) if !t.is_dotted() => { path.push(&kv.key); t.visit_nested_tables(path, false, callback)?; path.pop(); @@ -150,6 +144,9 @@ fn visit_table( path: &[&Key], is_array_of_tables: bool, ) -> Result { + let mut children = Vec::new(); + table.get_children(&mut children); + if path.is_empty() { // don't print header for the root node } else if is_array_of_tables { @@ -170,7 +167,7 @@ fn visit_table( "]]{}", table.decor.suffix().unwrap_or(DEFAULT_TABLE_DECOR.1) )?; - } else if !(table.implicit && table.values_len() == 0) { + } else if !(table.implicit && children.is_empty()) { write!( f, "{}[", @@ -190,19 +187,33 @@ fn visit_table( )?; } // print table body - for kv in table.items.values() { - if let Item::Value(ref value) = kv.value { - writeln!( - f, - "{}={}", - kv.key.decor.display(&kv.key, DEFAULT_KEY_DECOR), - value - )?; - } + for (key_path, value) in children { + let key = key_path_display(&key_path, DEFAULT_KEY_DECOR); + writeln!(f, "{}={}", key, value)?; } Ok(()) } +fn key_path_display(key_path: &[&Key], default: (&'static str, &'static str)) -> impl Display { + key_path + .iter() + .enumerate() + .map(|(ki, k)| { + let prefix = if ki == 0 { + default.0 + } else { + DEFAULT_KEY_PATH_DECOR.0 + }; + let suffix = if ki + 1 == key_path.len() { + default.1 + } else { + DEFAULT_KEY_PATH_DECOR.1 + }; + k.decor.display(k, (prefix, suffix)) + }) + .join(".") +} + impl Display for Table { fn fmt(&self, f: &mut Formatter<'_>) -> Result { let mut path = Vec::new(); diff --git a/src/inline_table.rs b/src/inline_table.rs index 17bbd934..e0c3b796 100644 --- a/src/inline_table.rs +++ b/src/inline_table.rs @@ -14,6 +14,8 @@ pub struct InlineTable { pub(crate) preamble: InternalString, // prefix before `{` and suffix after `}` pub(crate) decor: Decor, + // whether this is a proxy for dotted keys + pub(crate) dotted: bool, pub(crate) items: KeyValuePairs, } @@ -169,6 +171,44 @@ impl InlineTable { } impl InlineTable { + /// Get key/values for values that are visually children of this table + /// + /// For example, this will return dotted keys + pub fn get_children<'s, 'c>(&'s self, children: &'c mut Vec<(Vec<&'s Key>, &'s Value)>) { + let root = Vec::new(); + self.get_children_internal(&root, children); + } + + fn get_children_internal<'s, 'c>( + &'s self, + parent: &[&'s Key], + children: &'c mut Vec<(Vec<&'s Key>, &'s Value)>, + ) { + for value in self.items.values() { + let mut path = parent.to_vec(); + path.push(&value.key); + match &value.value { + Item::Value(Value::InlineTable(table)) if table.is_dotted() => { + table.get_children_internal(&path, children); + } + Item::Value(value) => { + children.push((path, value)); + } + _ => {} + } + } + } + + /// Check if this is a wrapper for dotted keys, rather than a standard table + pub fn is_dotted(&self) -> bool { + self.dotted + } + + /// Change this table's dotted status + pub fn set_dotted(&mut self, yes: bool) { + self.dotted = yes; + } + /// Sorts the key/value pairs by key. pub fn sort(&mut self) { sort_key_value_pairs(&mut self.items); diff --git a/src/parser/document.rs b/src/parser/document.rs index 5a1b8a0a..1fa377e4 100644 --- a/src/parser/document.rs +++ b/src/parser/document.rs @@ -133,9 +133,9 @@ impl TomlParser { } let root = self.document.as_table_mut(); - let table = Self::descend_path(root, self.current_table_path.as_slice(), 0) + let table = Self::descend_path(root, self.current_table_path.as_slice(), 0, false) .expect("the table path is valid; qed"); - let table = Self::descend_path(table, &path, 0)?; + let table = Self::descend_path(table, &path, 0, true)?; if table.contains_key(kv.key.get()) { Err(CustomError::DuplicateKey { key: kv.key.into(), diff --git a/src/parser/inline_table.rs b/src/parser/inline_table.rs index 83eb1c84..97e44903 100644 --- a/src/parser/inline_table.rs +++ b/src/parser/inline_table.rs @@ -48,7 +48,8 @@ fn descend_path<'a>( ) -> Result<&'a mut InlineTable, CustomError> { if let Some(key) = path.get(i) { let entry = table.entry_format(key).or_insert_with(|| { - let new_table = InlineTable::new(); + let mut new_table = InlineTable::new(); + new_table.set_dotted(true); Value::InlineTable(new_table) }); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0ffe6e0d..4d6d4b2c 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -390,6 +390,7 @@ trimmed in raw strings. r#"{ }"#, r#"{a = 1e165}"#, r#"{ hello = "world", a = 1}"#, + r#"{ hello.world = "a" }"#, ]; for input in &inputs { parsed_value_eq!(input); @@ -495,6 +496,8 @@ that "#, r#"[parent . child] key = "value" +"#, + r#"hello.world = "a" "#, ]; for document in &documents { diff --git a/src/parser/table.rs b/src/parser/table.rs index 50a16e61..e059e09c 100644 --- a/src/parser/table.rs +++ b/src/parser/table.rs @@ -83,11 +83,13 @@ impl TomlParser { table: &'a mut Table, path: &'a [Key], i: usize, + dotted: bool, ) -> Result<&'a mut Table, CustomError> { if let Some(key) = path.get(i) { let entry = table.entry_format(key).or_insert_with(|| { let mut new_table = Table::new(); new_table.set_implicit(true); + new_table.set_dotted(dotted); Item::Table(new_table) }); @@ -99,10 +101,10 @@ impl TomlParser { let index = array.len() - 1; let last_child = array.get_mut(index).unwrap(); - Self::descend_path(last_child, path, i + 1) + Self::descend_path(last_child, path, i + 1, dotted) } Item::Table(ref mut sweet_child_of_mine) => { - TomlParser::descend_path(sweet_child_of_mine, path, i + 1) + TomlParser::descend_path(sweet_child_of_mine, path, i + 1, dotted) } _ => unreachable!(), } @@ -118,7 +120,7 @@ impl TomlParser { let table = self.document.as_table_mut(); self.current_table_position += 1; - let table = Self::descend_path(table, &path[..path.len() - 1], 0)?; + let table = Self::descend_path(table, &path[..path.len() - 1], 0, false)?; let key = &path[path.len() - 1]; let decor = Decor::new(leading, trailing); @@ -160,7 +162,7 @@ impl TomlParser { let table = self.document.as_table_mut(); let key = &path[path.len() - 1]; - let table = Self::descend_path(table, &path[..path.len() - 1], 0); + let table = Self::descend_path(table, &path[..path.len() - 1], 0, false); match table { Ok(table) => { diff --git a/src/table.rs b/src/table.rs index 1d8377e8..624dc482 100644 --- a/src/table.rs +++ b/src/table.rs @@ -14,6 +14,8 @@ pub struct Table { pub(crate) decor: Decor, // whether to hide an empty table pub(crate) implicit: bool, + // whether this is a proxy for dotted keys + pub(crate) dotted: bool, // used for putting tables back in their original order when serialising. // Will be None when the Table wasn't parsed from a file. pub(crate) position: Option, @@ -79,7 +81,9 @@ impl Table { } /// Returns the number of key/value pairs in the table. - pub fn values_len(&self) -> usize { + /// + /// NOTE: Not accurate for dotted keys, see `get_children` + pub(crate) fn values_len(&self) -> usize { self.items.iter().filter(|i| (i.1).value.is_value()).count() } @@ -182,6 +186,34 @@ impl Table { } impl Table { + /// Get key/values for values that are visually children of this table + /// + /// For example, this will return dotted keys + pub fn get_children<'s, 'c>(&'s self, children: &'c mut Vec<(Vec<&'s Key>, &'s Value)>) { + let root = Vec::new(); + self.get_children_internal(&root, children); + } + + fn get_children_internal<'s, 'c>( + &'s self, + parent: &[&'s Key], + children: &'c mut Vec<(Vec<&'s Key>, &'s Value)>, + ) { + for value in self.items.values() { + let mut path = parent.to_vec(); + path.push(&value.key); + match &value.value { + Item::Table(table) if table.is_dotted() => { + table.get_children_internal(&path, children); + } + Item::Value(value) => { + children.push((path, value)); + } + _ => {} + } + } + } + /// If a table has no key/value pairs and implicit, it will not be displayed. /// /// # Examples @@ -203,6 +235,16 @@ impl Table { self.implicit = implicit; } + /// Check if this is a wrapper for dotted keys, rather than a standard table + pub fn is_dotted(&self) -> bool { + self.dotted + } + + /// Change this table's dotted status + pub fn set_dotted(&mut self, yes: bool) { + self.dotted = yes; + } + /// Sorts Key/Value Pairs of the table. /// /// Doesn't affect subtables or subarrays.