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