Skip to main content

toml_edit/
encode.rs

1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result, Write};
3
4use toml_datetime::Datetime;
5use toml_writer::ToTomlValue as _;
6use toml_writer::TomlWrite as _;
7
8use crate::DocumentMut;
9use crate::inline_table::DEFAULT_INLINE_KEY_DECOR;
10use crate::key::Key;
11use crate::repr::{Decor, Formatted, Repr, ValueRepr};
12use crate::table::{
13    DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_ROOT_DECOR, DEFAULT_TABLE_DECOR,
14};
15use crate::value::{
16    DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR,
17};
18use crate::{Array, InlineTable, Item, Table, Value};
19
20pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result {
21    if let Some(input) = input {
22        let repr = this
23            .as_repr()
24            .map(Cow::Borrowed)
25            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
26        repr.encode(buf, input)?;
27    } else {
28        let repr = this.display_repr();
29        buf.write_str(&repr)?;
30    };
31
32    Ok(())
33}
34
35pub(crate) fn encode_key_path(
36    this: &[&Key],
37    mut buf: &mut dyn Write,
38    input: Option<&str>,
39    default_decor: (&str, &str),
40    leaf_decor: &Decor,
41) -> Result {
42    for (i, key) in this.iter().enumerate() {
43        let dotted_decor = key.dotted_decor();
44
45        let first = i == 0;
46        let last = i + 1 == this.len();
47
48        if first {
49            leaf_decor.prefix_encode(buf, input, default_decor.0)?;
50        } else {
51            buf.key_sep()?;
52            dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
53        }
54
55        encode_key(key, buf, input)?;
56
57        if last {
58            leaf_decor.suffix_encode(buf, input, default_decor.1)?;
59        } else {
60            dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
61        }
62    }
63    Ok(())
64}
65
66pub(crate) fn encode_formatted<T: ValueRepr>(
67    this: &Formatted<T>,
68    buf: &mut dyn Write,
69    input: Option<&str>,
70    default_decor: (&str, &str),
71) -> Result {
72    let decor = this.decor();
73    decor.prefix_encode(buf, input, default_decor.0)?;
74
75    if let Some(input) = input {
76        let repr = this
77            .as_repr()
78            .map(Cow::Borrowed)
79            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
80        repr.encode(buf, input)?;
81    } else {
82        let repr = this.display_repr();
83        buf.write_str(&repr)?;
84    };
85
86    decor.suffix_encode(buf, input, default_decor.1)?;
87    Ok(())
88}
89
90pub(crate) fn encode_array(
91    this: &Array,
92    mut buf: &mut dyn Write,
93    input: Option<&str>,
94    default_decor: (&str, &str),
95) -> Result {
96    let decor = this.decor();
97    decor.prefix_encode(buf, input, default_decor.0)?;
98    buf.open_array()?;
99
100    for (i, elem) in this.iter_values().enumerate() {
101        let inner_decor;
102        if i == 0 {
103            inner_decor = DEFAULT_LEADING_VALUE_DECOR;
104        } else {
105            inner_decor = DEFAULT_VALUE_DECOR;
106            buf.val_sep()?;
107        }
108        encode_value(elem, buf, input, inner_decor)?;
109    }
110    if this.trailing_comma() && !this.is_empty() {
111        buf.val_sep()?;
112    }
113
114    this.trailing().encode_with_default(buf, input, "")?;
115    buf.close_array()?;
116    decor.suffix_encode(buf, input, default_decor.1)?;
117
118    Ok(())
119}
120
121pub(crate) fn encode_table(
122    this: &InlineTable,
123    mut buf: &mut dyn Write,
124    input: Option<&str>,
125    default_decor: (&str, &str),
126) -> Result {
127    let decor = this.decor();
128    decor.prefix_encode(buf, input, default_decor.0)?;
129    buf.open_inline_table()?;
130
131    let children = this.get_values();
132    let len = children.len();
133    for (i, (key_path, value)) in children.into_iter().enumerate() {
134        if i != 0 {
135            buf.val_sep()?;
136        }
137        let inner_decor = if i == len - 1 {
138            DEFAULT_TRAILING_VALUE_DECOR
139        } else {
140            DEFAULT_VALUE_DECOR
141        };
142        let leaf_decor = key_path
143            .last()
144            .expect("always at least one key")
145            .leaf_decor();
146        encode_key_path(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR, leaf_decor)?;
147        buf.keyval_sep()?;
148        encode_value(value, buf, input, inner_decor)?;
149    }
150    if this.trailing_comma() && !this.is_empty() {
151        buf.val_sep()?;
152    }
153
154    this.trailing().encode_with_default(buf, input, "")?;
155    buf.close_inline_table()?;
156    decor.suffix_encode(buf, input, default_decor.1)?;
157
158    Ok(())
159}
160
161pub(crate) fn encode_value(
162    this: &Value,
163    buf: &mut dyn Write,
164    input: Option<&str>,
165    default_decor: (&str, &str),
166) -> Result {
167    match this {
168        Value::String(repr) => encode_formatted(repr, buf, input, default_decor),
169        Value::Integer(repr) => encode_formatted(repr, buf, input, default_decor),
170        Value::Float(repr) => encode_formatted(repr, buf, input, default_decor),
171        Value::Boolean(repr) => encode_formatted(repr, buf, input, default_decor),
172        Value::Datetime(repr) => encode_formatted(repr, buf, input, default_decor),
173        Value::Array(array) => encode_array(array, buf, input, default_decor),
174        Value::InlineTable(table) => encode_table(table, buf, input, default_decor),
175    }
176}
177
178impl Display for DocumentMut {
179    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
180        let decor = self.decor();
181        decor.prefix_encode(f, None, DEFAULT_ROOT_DECOR.0)?;
182
183        let mut path = Vec::new();
184        let mut last_position = 0;
185        let mut tables = Vec::new();
186        visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| {
187            if let Some(pos) = t.position() {
188                last_position = pos;
189            }
190            tables.push((last_position, t, p.to_vec(), is_array));
191            Ok(())
192        })
193        .unwrap();
194
195        tables.sort_by_key(|(id, _, path, _)| (!path.is_empty(), *id));
196        let mut first_table = true;
197        for (_, table, path, is_array) in tables {
198            visit_table(f, None, table, &path, is_array, &mut first_table)?;
199        }
200        decor.suffix_encode(f, None, DEFAULT_ROOT_DECOR.1)?;
201        self.trailing().encode_with_default(f, None, "")
202    }
203}
204
205fn visit_nested_tables<'t, F>(
206    table: &'t Table,
207    path: &mut Vec<&'t Key>,
208    is_array_of_tables: bool,
209    callback: &mut F,
210) -> Result
211where
212    F: FnMut(&'t Table, &[&'t Key], bool) -> Result,
213{
214    if !table.is_dotted() {
215        callback(table, path, is_array_of_tables)?;
216    }
217
218    for (key, value) in table.items.iter() {
219        match value {
220            Item::Table(t) => {
221                path.push(key);
222                visit_nested_tables(t, path, false, callback)?;
223                path.pop();
224            }
225            Item::ArrayOfTables(a) => {
226                for t in a.iter_tables() {
227                    path.push(key);
228                    visit_nested_tables(t, path, true, callback)?;
229                    path.pop();
230                }
231            }
232            _ => {}
233        }
234    }
235    Ok(())
236}
237
238/// Split the leaf decor for a table header into the part before `[` and the part inside it.
239///
240/// The TOML spec only allows spaces and tabs (ws) between brackets and keys, so any prefix
241/// containing newlines must be placed before `[`.
242///
243/// Returns `(before_bracket, inside_header)`:
244/// - `before_bracket`: `Some(&Decor)` whose prefix should be written before the opening bracket,
245///   when the leaf decor prefix contains newlines.
246/// - `inside_header`: `&Decor` to use around the key path inside the brackets.
247fn leaf_decor_before_bracket<'a>(
248    path: &'a [&Key],
249    input: Option<&str>,
250) -> (Option<&'a Decor>, &'a Decor) {
251    let Some(last_key) = path.last() else {
252        return (None, &Decor::EMPTY);
253    };
254    let leaf_decor = last_key.leaf_decor();
255    let needs_extraction = leaf_decor.prefix().is_some_and(|prefix| {
256        prefix
257            .to_str_with_default(input, DEFAULT_KEY_PATH_DECOR.0)
258            .contains('\n')
259    });
260    if needs_extraction {
261        (Some(leaf_decor), &Decor::EMPTY)
262    } else {
263        (None, leaf_decor)
264    }
265}
266
267fn visit_table(
268    mut buf: &mut dyn Write,
269    input: Option<&str>,
270    table: &Table,
271    path: &[&Key],
272    is_array_of_tables: bool,
273    first_table: &mut bool,
274) -> Result {
275    let children = table.get_values();
276    // We are intentionally hiding implicit tables without any tables nested under them (ie
277    // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`).  We are
278    // trusting the user that an empty implicit table is not semantically meaningful
279    //
280    // This allows a user to delete all tables under this implicit table and the implicit table
281    // will disappear.
282    //
283    // However, this means that users need to take care in deciding what tables get marked as
284    // implicit.
285    let is_visible_std_table = !(table.implicit && children.is_empty());
286
287    if path.is_empty() {
288        // don't print header for the root node
289        if !children.is_empty() {
290            *first_table = false;
291        }
292    } else if is_array_of_tables {
293        let default_decor = if *first_table {
294            *first_table = false;
295            ("", DEFAULT_TABLE_DECOR.1)
296        } else {
297            DEFAULT_TABLE_DECOR
298        };
299        let (before_bracket, key_decor) = leaf_decor_before_bracket(path, input);
300        if let Some(decor) = before_bracket {
301            decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
302        }
303        table.decor.prefix_encode(buf, input, default_decor.0)?;
304        buf.open_array_of_tables_header()?;
305        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR, key_decor)?;
306        buf.close_array_of_tables_header()?;
307        table.decor.suffix_encode(buf, input, default_decor.1)?;
308        writeln!(buf)?;
309    } else if is_visible_std_table {
310        let default_decor = if *first_table {
311            *first_table = false;
312            ("", DEFAULT_TABLE_DECOR.1)
313        } else {
314            DEFAULT_TABLE_DECOR
315        };
316        let (before_bracket, key_decor) = leaf_decor_before_bracket(path, input);
317        if let Some(decor) = before_bracket {
318            decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
319        }
320        table.decor.prefix_encode(buf, input, default_decor.0)?;
321        buf.open_table_header()?;
322        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR, key_decor)?;
323        buf.close_table_header()?;
324        table.decor.suffix_encode(buf, input, default_decor.1)?;
325        writeln!(buf)?;
326    }
327    // print table body
328    for (key_path, value) in children {
329        let leaf_decor = key_path
330            .last()
331            .expect("always at least one key")
332            .leaf_decor();
333        encode_key_path(&key_path, buf, input, DEFAULT_KEY_DECOR, leaf_decor)?;
334        buf.keyval_sep()?;
335        encode_value(value, buf, input, DEFAULT_VALUE_DECOR)?;
336        writeln!(buf)?;
337    }
338    Ok(())
339}
340
341impl ValueRepr for String {
342    fn to_repr(&self) -> Repr {
343        let output = toml_writer::TomlStringBuilder::new(self.as_str())
344            .as_default()
345            .to_toml_value();
346        Repr::new_unchecked(output)
347    }
348}
349
350impl ValueRepr for i64 {
351    fn to_repr(&self) -> Repr {
352        let repr = self.to_toml_value();
353        Repr::new_unchecked(repr)
354    }
355}
356
357impl ValueRepr for f64 {
358    fn to_repr(&self) -> Repr {
359        let repr = self.to_toml_value();
360        Repr::new_unchecked(repr)
361    }
362}
363
364impl ValueRepr for bool {
365    fn to_repr(&self) -> Repr {
366        let repr = self.to_toml_value();
367        Repr::new_unchecked(repr)
368    }
369}
370
371impl ValueRepr for Datetime {
372    fn to_repr(&self) -> Repr {
373        Repr::new_unchecked(self.to_string())
374    }
375}
376
377#[cfg(test)]
378mod test {
379    use super::*;
380    use proptest::prelude::*;
381
382    proptest! {
383        #[test]
384        #[cfg(feature = "parse")]
385        fn parseable_string(string in "\\PC*") {
386            let value = Value::from(string.clone());
387            let encoded = value.to_string();
388            let _: Value = encoded.parse().unwrap_or_else(|err| {
389                panic!("error: {err}
390
391string:
392```
393{string}
394```
395value:
396```
397{value}
398```
399")
400            });
401        }
402    }
403
404    proptest! {
405        #[test]
406        #[cfg(feature = "parse")]
407        fn parseable_key(string in "\\PC*") {
408            let key = Key::new(string.clone());
409            let encoded = key.to_string();
410            let _: Key = encoded.parse().unwrap_or_else(|err| {
411                panic!("error: {err}
412
413string:
414```
415{string}
416```
417key:
418```
419{key}
420```
421")
422            });
423        }
424    }
425}