toml_edit/
encode.rs

1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result, Write};
3
4use toml_datetime::Datetime;
5
6use crate::inline_table::DEFAULT_INLINE_KEY_DECOR;
7use crate::key::Key;
8use crate::repr::{Formatted, Repr, ValueRepr};
9use crate::table::{
10    DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_ROOT_DECOR, DEFAULT_TABLE_DECOR,
11};
12use crate::value::{
13    DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR,
14};
15use crate::DocumentMut;
16use crate::{Array, InlineTable, Item, Table, Value};
17
18pub(crate) fn encode_key(this: &Key, buf: &mut dyn Write, input: Option<&str>) -> Result {
19    if let Some(input) = input {
20        let repr = this
21            .as_repr()
22            .map(Cow::Borrowed)
23            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
24        repr.encode(buf, input)?;
25    } else {
26        let repr = this.display_repr();
27        write!(buf, "{repr}")?;
28    };
29
30    Ok(())
31}
32
33fn encode_key_path(
34    this: &[Key],
35    buf: &mut dyn Write,
36    input: Option<&str>,
37    default_decor: (&str, &str),
38) -> Result {
39    let leaf_decor = this.last().expect("always at least one key").leaf_decor();
40    for (i, key) in this.iter().enumerate() {
41        let dotted_decor = key.dotted_decor();
42
43        let first = i == 0;
44        let last = i + 1 == this.len();
45
46        if first {
47            leaf_decor.prefix_encode(buf, input, default_decor.0)?;
48        } else {
49            write!(buf, ".")?;
50            dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
51        }
52
53        encode_key(key, buf, input)?;
54
55        if last {
56            leaf_decor.suffix_encode(buf, input, default_decor.1)?;
57        } else {
58            dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
59        }
60    }
61    Ok(())
62}
63
64pub(crate) fn encode_key_path_ref(
65    this: &[&Key],
66    buf: &mut dyn Write,
67    input: Option<&str>,
68    default_decor: (&str, &str),
69) -> Result {
70    let leaf_decor = this.last().expect("always at least one key").leaf_decor();
71    for (i, key) in this.iter().enumerate() {
72        let dotted_decor = key.dotted_decor();
73
74        let first = i == 0;
75        let last = i + 1 == this.len();
76
77        if first {
78            leaf_decor.prefix_encode(buf, input, default_decor.0)?;
79        } else {
80            write!(buf, ".")?;
81            dotted_decor.prefix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.0)?;
82        }
83
84        encode_key(key, buf, input)?;
85
86        if last {
87            leaf_decor.suffix_encode(buf, input, default_decor.1)?;
88        } else {
89            dotted_decor.suffix_encode(buf, input, DEFAULT_KEY_PATH_DECOR.1)?;
90        }
91    }
92    Ok(())
93}
94
95pub(crate) fn encode_formatted<T: ValueRepr>(
96    this: &Formatted<T>,
97    buf: &mut dyn Write,
98    input: Option<&str>,
99    default_decor: (&str, &str),
100) -> Result {
101    let decor = this.decor();
102    decor.prefix_encode(buf, input, default_decor.0)?;
103
104    if let Some(input) = input {
105        let repr = this
106            .as_repr()
107            .map(Cow::Borrowed)
108            .unwrap_or_else(|| Cow::Owned(this.default_repr()));
109        repr.encode(buf, input)?;
110    } else {
111        let repr = this.display_repr();
112        write!(buf, "{repr}")?;
113    };
114
115    decor.suffix_encode(buf, input, default_decor.1)?;
116    Ok(())
117}
118
119pub(crate) fn encode_array(
120    this: &Array,
121    buf: &mut dyn Write,
122    input: Option<&str>,
123    default_decor: (&str, &str),
124) -> Result {
125    let decor = this.decor();
126    decor.prefix_encode(buf, input, default_decor.0)?;
127    write!(buf, "[")?;
128
129    for (i, elem) in this.iter().enumerate() {
130        let inner_decor;
131        if i == 0 {
132            inner_decor = DEFAULT_LEADING_VALUE_DECOR;
133        } else {
134            inner_decor = DEFAULT_VALUE_DECOR;
135            write!(buf, ",")?;
136        }
137        encode_value(elem, buf, input, inner_decor)?;
138    }
139    if this.trailing_comma() && !this.is_empty() {
140        write!(buf, ",")?;
141    }
142
143    this.trailing().encode_with_default(buf, input, "")?;
144    write!(buf, "]")?;
145    decor.suffix_encode(buf, input, default_decor.1)?;
146
147    Ok(())
148}
149
150pub(crate) fn encode_table(
151    this: &InlineTable,
152    buf: &mut dyn Write,
153    input: Option<&str>,
154    default_decor: (&str, &str),
155) -> Result {
156    let decor = this.decor();
157    decor.prefix_encode(buf, input, default_decor.0)?;
158    write!(buf, "{{")?;
159    this.preamble().encode_with_default(buf, input, "")?;
160
161    let children = this.get_values();
162    let len = children.len();
163    for (i, (key_path, value)) in children.into_iter().enumerate() {
164        if i != 0 {
165            write!(buf, ",")?;
166        }
167        let inner_decor = if i == len - 1 {
168            DEFAULT_TRAILING_VALUE_DECOR
169        } else {
170            DEFAULT_VALUE_DECOR
171        };
172        encode_key_path_ref(&key_path, buf, input, DEFAULT_INLINE_KEY_DECOR)?;
173        write!(buf, "=")?;
174        encode_value(value, buf, input, inner_decor)?;
175    }
176
177    write!(buf, "}}")?;
178    decor.suffix_encode(buf, input, default_decor.1)?;
179
180    Ok(())
181}
182
183pub(crate) fn encode_value(
184    this: &Value,
185    buf: &mut dyn Write,
186    input: Option<&str>,
187    default_decor: (&str, &str),
188) -> Result {
189    match this {
190        Value::String(repr) => encode_formatted(repr, buf, input, default_decor),
191        Value::Integer(repr) => encode_formatted(repr, buf, input, default_decor),
192        Value::Float(repr) => encode_formatted(repr, buf, input, default_decor),
193        Value::Boolean(repr) => encode_formatted(repr, buf, input, default_decor),
194        Value::Datetime(repr) => encode_formatted(repr, buf, input, default_decor),
195        Value::Array(array) => encode_array(array, buf, input, default_decor),
196        Value::InlineTable(table) => encode_table(table, buf, input, default_decor),
197    }
198}
199
200impl Display for DocumentMut {
201    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
202        let decor = self.decor();
203        decor.prefix_encode(f, None, DEFAULT_ROOT_DECOR.0)?;
204
205        let mut path = Vec::new();
206        let mut last_position = 0;
207        let mut tables = Vec::new();
208        visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| {
209            if let Some(pos) = t.position() {
210                last_position = pos;
211            }
212            tables.push((last_position, t, p.clone(), is_array));
213            Ok(())
214        })
215        .unwrap();
216
217        tables.sort_by_key(|&(id, _, _, _)| id);
218        let mut first_table = true;
219        for (_, table, path, is_array) in tables {
220            visit_table(f, None, table, &path, is_array, &mut first_table)?;
221        }
222        decor.suffix_encode(f, None, DEFAULT_ROOT_DECOR.1)?;
223        self.trailing().encode_with_default(f, None, "")
224    }
225}
226
227fn visit_nested_tables<'t, F>(
228    table: &'t Table,
229    path: &mut Vec<Key>,
230    is_array_of_tables: bool,
231    callback: &mut F,
232) -> Result
233where
234    F: FnMut(&'t Table, &Vec<Key>, bool) -> Result,
235{
236    if !table.is_dotted() {
237        callback(table, path, is_array_of_tables)?;
238    }
239
240    for (key, value) in table.items.iter() {
241        match value {
242            Item::Table(ref t) => {
243                let key = key.clone();
244                path.push(key);
245                visit_nested_tables(t, path, false, callback)?;
246                path.pop();
247            }
248            Item::ArrayOfTables(ref a) => {
249                for t in a.iter() {
250                    let key = key.clone();
251                    path.push(key);
252                    visit_nested_tables(t, path, true, callback)?;
253                    path.pop();
254                }
255            }
256            _ => {}
257        }
258    }
259    Ok(())
260}
261
262fn visit_table(
263    buf: &mut dyn Write,
264    input: Option<&str>,
265    table: &Table,
266    path: &[Key],
267    is_array_of_tables: bool,
268    first_table: &mut bool,
269) -> Result {
270    let children = table.get_values();
271    // We are intentionally hiding implicit tables without any tables nested under them (ie
272    // `table.is_empty()` which is in contrast to `table.get_values().is_empty()`).  We are
273    // trusting the user that an empty implicit table is not semantically meaningful
274    //
275    // This allows a user to delete all tables under this implicit table and the implicit table
276    // will disappear.
277    //
278    // However, this means that users need to take care in deciding what tables get marked as
279    // implicit.
280    let is_visible_std_table = !(table.implicit && children.is_empty());
281
282    if path.is_empty() {
283        // don't print header for the root node
284        if !children.is_empty() {
285            *first_table = false;
286        }
287    } else if is_array_of_tables {
288        let default_decor = if *first_table {
289            *first_table = false;
290            ("", DEFAULT_TABLE_DECOR.1)
291        } else {
292            DEFAULT_TABLE_DECOR
293        };
294        table.decor.prefix_encode(buf, input, default_decor.0)?;
295        write!(buf, "[[")?;
296        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?;
297        write!(buf, "]]")?;
298        table.decor.suffix_encode(buf, input, default_decor.1)?;
299        writeln!(buf)?;
300    } else if is_visible_std_table {
301        let default_decor = if *first_table {
302            *first_table = false;
303            ("", DEFAULT_TABLE_DECOR.1)
304        } else {
305            DEFAULT_TABLE_DECOR
306        };
307        table.decor.prefix_encode(buf, input, default_decor.0)?;
308        write!(buf, "[")?;
309        encode_key_path(path, buf, input, DEFAULT_KEY_PATH_DECOR)?;
310        write!(buf, "]")?;
311        table.decor.suffix_encode(buf, input, default_decor.1)?;
312        writeln!(buf)?;
313    }
314    // print table body
315    for (key_path, value) in children {
316        encode_key_path_ref(&key_path, buf, input, DEFAULT_KEY_DECOR)?;
317        write!(buf, "=")?;
318        encode_value(value, buf, input, DEFAULT_VALUE_DECOR)?;
319        writeln!(buf)?;
320    }
321    Ok(())
322}
323
324impl ValueRepr for String {
325    fn to_repr(&self) -> Repr {
326        to_string_repr(self, None, None)
327    }
328}
329
330pub(crate) fn to_string_repr(
331    value: &str,
332    style: Option<StringStyle>,
333    literal: Option<bool>,
334) -> Repr {
335    let (style, literal) = infer_style(value, style, literal);
336
337    let mut output = String::with_capacity(value.len() * 2);
338    if literal {
339        output.push_str(style.literal_start());
340        output.push_str(value);
341        output.push_str(style.literal_end());
342    } else {
343        output.push_str(style.standard_start());
344        for ch in value.chars() {
345            match ch {
346                '\u{8}' => output.push_str("\\b"),
347                '\u{9}' => output.push_str("\\t"),
348                '\u{a}' => match style {
349                    StringStyle::NewlineTriple => output.push('\n'),
350                    StringStyle::OnelineSingle => output.push_str("\\n"),
351                    StringStyle::OnelineTriple => unreachable!(),
352                },
353                '\u{c}' => output.push_str("\\f"),
354                '\u{d}' => output.push_str("\\r"),
355                '\u{22}' => output.push_str("\\\""),
356                '\u{5c}' => output.push_str("\\\\"),
357                c if c <= '\u{1f}' || c == '\u{7f}' => {
358                    write!(output, "\\u{:04X}", ch as u32).unwrap();
359                }
360                ch => output.push(ch),
361            }
362        }
363        output.push_str(style.standard_end());
364    }
365
366    Repr::new_unchecked(output)
367}
368
369#[derive(Copy, Clone, Debug, PartialEq, Eq)]
370pub(crate) enum StringStyle {
371    NewlineTriple,
372    OnelineTriple,
373    OnelineSingle,
374}
375
376impl StringStyle {
377    fn literal_start(self) -> &'static str {
378        match self {
379            Self::NewlineTriple => "'''\n",
380            Self::OnelineTriple => "'''",
381            Self::OnelineSingle => "'",
382        }
383    }
384    fn literal_end(self) -> &'static str {
385        match self {
386            Self::NewlineTriple => "'''",
387            Self::OnelineTriple => "'''",
388            Self::OnelineSingle => "'",
389        }
390    }
391
392    fn standard_start(self) -> &'static str {
393        match self {
394            Self::NewlineTriple => "\"\"\"\n",
395            // note: OnelineTriple can happen if do_pretty wants to do
396            // '''it's one line'''
397            // but literal == false
398            Self::OnelineTriple | Self::OnelineSingle => "\"",
399        }
400    }
401
402    fn standard_end(self) -> &'static str {
403        match self {
404            Self::NewlineTriple => "\"\"\"",
405            // note: OnelineTriple can happen if do_pretty wants to do
406            // '''it's one line'''
407            // but literal == false
408            Self::OnelineTriple | Self::OnelineSingle => "\"",
409        }
410    }
411}
412
413fn infer_style(
414    value: &str,
415    style: Option<StringStyle>,
416    literal: Option<bool>,
417) -> (StringStyle, bool) {
418    match (style, literal) {
419        (Some(style), Some(literal)) => (style, literal),
420        (None, Some(literal)) => (infer_all_style(value).0, literal),
421        (Some(style), None) => {
422            let literal = infer_literal(value);
423            (style, literal)
424        }
425        (None, None) => infer_all_style(value),
426    }
427}
428
429fn infer_literal(value: &str) -> bool {
430    #[cfg(feature = "parse")]
431    {
432        use winnow::stream::ContainsToken as _;
433        (value.contains('"') | value.contains('\\'))
434            && value
435                .chars()
436                .all(|c| crate::parser::strings::LITERAL_CHAR.contains_token(c))
437    }
438    #[cfg(not(feature = "parse"))]
439    {
440        false
441    }
442}
443
444fn infer_all_style(value: &str) -> (StringStyle, bool) {
445    // We need to determine:
446    // - if we are a "multi-line" pretty (if there are \n)
447    // - if ['''] appears if multi or ['] if single
448    // - if there are any invalid control characters
449    //
450    // Doing it any other way would require multiple passes
451    // to determine if a pretty string works or not.
452    let mut ty = StringStyle::OnelineSingle;
453    // found consecutive single quotes
454    let mut max_found_singles = 0;
455    let mut found_singles = 0;
456    let mut prefer_literal = false;
457    let mut can_be_pretty = true;
458
459    for ch in value.chars() {
460        if can_be_pretty {
461            if ch == '\'' {
462                found_singles += 1;
463                if found_singles >= 3 {
464                    can_be_pretty = false;
465                }
466            } else {
467                if found_singles > max_found_singles {
468                    max_found_singles = found_singles;
469                }
470                found_singles = 0;
471            }
472            match ch {
473                '\t' => {}
474                '"' => {
475                    prefer_literal = true;
476                }
477                '\\' => {
478                    prefer_literal = true;
479                }
480                '\n' => ty = StringStyle::NewlineTriple,
481                // Escape codes are needed if any ascii control
482                // characters are present, including \b \f \r.
483                c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
484                _ => {}
485            }
486        } else {
487            // the string cannot be represented as pretty,
488            // still check if it should be multiline
489            if ch == '\n' {
490                ty = StringStyle::NewlineTriple;
491            }
492        }
493    }
494    if found_singles > 0 && value.ends_with('\'') {
495        // We cannot escape the ending quote so we must use """
496        can_be_pretty = false;
497    }
498    if !prefer_literal {
499        can_be_pretty = false;
500    }
501    if !can_be_pretty {
502        debug_assert!(ty != StringStyle::OnelineTriple);
503        return (ty, false);
504    }
505    if found_singles > max_found_singles {
506        max_found_singles = found_singles;
507    }
508    debug_assert!(max_found_singles < 3);
509    if ty == StringStyle::OnelineSingle && max_found_singles >= 1 {
510        // no newlines, but must use ''' because it has ' in it
511        ty = StringStyle::OnelineTriple;
512    }
513    (ty, true)
514}
515
516impl ValueRepr for i64 {
517    fn to_repr(&self) -> Repr {
518        Repr::new_unchecked(self.to_string())
519    }
520}
521
522impl ValueRepr for f64 {
523    fn to_repr(&self) -> Repr {
524        to_f64_repr(*self)
525    }
526}
527
528fn to_f64_repr(f: f64) -> Repr {
529    let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) {
530        (true, true, _) => "-nan".to_owned(),
531        (false, true, _) => "nan".to_owned(),
532        (true, false, true) => "-0.0".to_owned(),
533        (false, false, true) => "0.0".to_owned(),
534        (_, false, false) => {
535            if f % 1.0 == 0.0 {
536                format!("{f}.0")
537            } else {
538                format!("{f}")
539            }
540        }
541    };
542    Repr::new_unchecked(repr)
543}
544
545impl ValueRepr for bool {
546    fn to_repr(&self) -> Repr {
547        Repr::new_unchecked(self.to_string())
548    }
549}
550
551impl ValueRepr for Datetime {
552    fn to_repr(&self) -> Repr {
553        Repr::new_unchecked(self.to_string())
554    }
555}
556
557#[cfg(test)]
558mod test {
559    use super::*;
560    use proptest::prelude::*;
561
562    proptest! {
563        #[test]
564        #[cfg(feature = "parse")]
565        fn parseable_string(string in "\\PC*") {
566            let string = Value::from(string);
567            let encoded = string.to_string();
568            let _: Value = encoded.parse().unwrap_or_else(|err| {
569                panic!("error: {err}
570
571string:
572```
573{string}
574```
575")
576            });
577        }
578    }
579
580    proptest! {
581        #[test]
582        #[cfg(feature = "parse")]
583        fn parseable_key(string in "\\PC*") {
584            let string = Key::new(string);
585            let encoded = string.to_string();
586            let _: Key = encoded.parse().unwrap_or_else(|err| {
587                panic!("error: {err}
588
589string:
590```
591{string}
592```
593")
594            });
595        }
596    }
597}