toml_edit/
encode.rs

1use std::borrow::Cow;
2use std::fmt::{Display, Formatter, Result, Write};
3
4use toml_datetime::*;
5
6use crate::document::Document;
7use crate::inline_table::DEFAULT_INLINE_KEY_DECOR;
8use crate::key::Key;
9use crate::repr::{Formatted, Repr, ValueRepr};
10use crate::table::{DEFAULT_KEY_DECOR, DEFAULT_KEY_PATH_DECOR, DEFAULT_TABLE_DECOR};
11use crate::value::{
12    DEFAULT_LEADING_VALUE_DECOR, DEFAULT_TRAILING_VALUE_DECOR, DEFAULT_VALUE_DECOR,
13};
14use crate::{Array, InlineTable, Item, Table, Value};
15
16pub(crate) trait Encode {
17    fn encode(
18        &self,
19        buf: &mut dyn Write,
20        input: Option<&str>,
21        default_decor: (&str, &str),
22    ) -> Result;
23}
24
25impl Encode for Key {
26    fn encode(
27        &self,
28        buf: &mut dyn Write,
29        input: Option<&str>,
30        default_decor: (&str, &str),
31    ) -> Result {
32        let decor = self.decor();
33        decor.prefix_encode(buf, input, default_decor.0)?;
34
35        if let Some(input) = input {
36            let repr = self
37                .as_repr()
38                .map(Cow::Borrowed)
39                .unwrap_or_else(|| Cow::Owned(self.default_repr()));
40            repr.encode(buf, input)?;
41        } else {
42            let repr = self.display_repr();
43            write!(buf, "{}", repr)?;
44        };
45
46        decor.suffix_encode(buf, input, default_decor.1)?;
47        Ok(())
48    }
49}
50
51impl<'k> Encode for &'k [&'k Key] {
52    fn encode(
53        &self,
54        buf: &mut dyn Write,
55        input: Option<&str>,
56        default_decor: (&str, &str),
57    ) -> Result {
58        for (i, key) in self.iter().enumerate() {
59            let first = i == 0;
60            let last = i + 1 == self.len();
61
62            let prefix = if first {
63                default_decor.0
64            } else {
65                DEFAULT_KEY_PATH_DECOR.0
66            };
67            let suffix = if last {
68                default_decor.1
69            } else {
70                DEFAULT_KEY_PATH_DECOR.1
71            };
72
73            if !first {
74                write!(buf, ".")?;
75            }
76            key.encode(buf, input, (prefix, suffix))?;
77        }
78        Ok(())
79    }
80}
81
82impl<T> Encode for Formatted<T>
83where
84    T: ValueRepr,
85{
86    fn encode(
87        &self,
88        buf: &mut dyn Write,
89        input: Option<&str>,
90        default_decor: (&str, &str),
91    ) -> Result {
92        let decor = self.decor();
93        decor.prefix_encode(buf, input, default_decor.0)?;
94
95        if let Some(input) = input {
96            let repr = self
97                .as_repr()
98                .map(Cow::Borrowed)
99                .unwrap_or_else(|| Cow::Owned(self.default_repr()));
100            repr.encode(buf, input)?;
101        } else {
102            let repr = self.display_repr();
103            write!(buf, "{}", repr)?;
104        };
105
106        decor.suffix_encode(buf, input, default_decor.1)?;
107        Ok(())
108    }
109}
110
111impl Encode for Array {
112    fn encode(
113        &self,
114        buf: &mut dyn Write,
115        input: Option<&str>,
116        default_decor: (&str, &str),
117    ) -> Result {
118        let decor = self.decor();
119        decor.prefix_encode(buf, input, default_decor.0)?;
120        write!(buf, "[")?;
121
122        for (i, elem) in self.iter().enumerate() {
123            let inner_decor;
124            if i == 0 {
125                inner_decor = DEFAULT_LEADING_VALUE_DECOR;
126            } else {
127                inner_decor = DEFAULT_VALUE_DECOR;
128                write!(buf, ",")?;
129            }
130            elem.encode(buf, input, inner_decor)?;
131        }
132        if self.trailing_comma() && !self.is_empty() {
133            write!(buf, ",")?;
134        }
135
136        self.trailing().encode_with_default(buf, input, "")?;
137        write!(buf, "]")?;
138        decor.suffix_encode(buf, input, default_decor.1)?;
139
140        Ok(())
141    }
142}
143
144impl Encode for InlineTable {
145    fn encode(
146        &self,
147        buf: &mut dyn Write,
148        input: Option<&str>,
149        default_decor: (&str, &str),
150    ) -> Result {
151        let decor = self.decor();
152        decor.prefix_encode(buf, input, default_decor.0)?;
153        write!(buf, "{{")?;
154        self.preamble().encode_with_default(buf, input, "")?;
155
156        let children = self.get_values();
157        let len = children.len();
158        for (i, (key_path, value)) in children.into_iter().enumerate() {
159            if i != 0 {
160                write!(buf, ",")?;
161            }
162            let inner_decor = if i == len - 1 {
163                DEFAULT_TRAILING_VALUE_DECOR
164            } else {
165                DEFAULT_VALUE_DECOR
166            };
167            key_path
168                .as_slice()
169                .encode(buf, input, DEFAULT_INLINE_KEY_DECOR)?;
170            write!(buf, "=")?;
171            value.encode(buf, input, inner_decor)?;
172        }
173
174        write!(buf, "}}")?;
175        decor.suffix_encode(buf, input, default_decor.1)?;
176
177        Ok(())
178    }
179}
180
181impl Encode for Value {
182    fn encode(
183        &self,
184        buf: &mut dyn Write,
185        input: Option<&str>,
186        default_decor: (&str, &str),
187    ) -> Result {
188        match self {
189            Value::String(repr) => repr.encode(buf, input, default_decor),
190            Value::Integer(repr) => repr.encode(buf, input, default_decor),
191            Value::Float(repr) => repr.encode(buf, input, default_decor),
192            Value::Boolean(repr) => repr.encode(buf, input, default_decor),
193            Value::Datetime(repr) => repr.encode(buf, input, default_decor),
194            Value::Array(array) => array.encode(buf, input, default_decor),
195            Value::InlineTable(table) => table.encode(buf, input, default_decor),
196        }
197    }
198}
199
200impl Display for Document {
201    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
202        let mut path = Vec::new();
203        let mut last_position = 0;
204        let mut tables = Vec::new();
205        visit_nested_tables(self.as_table(), &mut path, false, &mut |t, p, is_array| {
206            if let Some(pos) = t.position() {
207                last_position = pos;
208            }
209            tables.push((last_position, t, p.clone(), is_array));
210            Ok(())
211        })
212        .unwrap();
213
214        tables.sort_by_key(|&(id, _, _, _)| id);
215        let mut first_table = true;
216        for (_, table, path, is_array) in tables {
217            visit_table(
218                f,
219                self.original.as_deref(),
220                table,
221                &path,
222                is_array,
223                &mut first_table,
224            )?;
225        }
226        self.trailing()
227            .encode_with_default(f, self.original.as_deref(), "")
228    }
229}
230
231fn visit_nested_tables<'t, F>(
232    table: &'t Table,
233    path: &mut Vec<&'t Key>,
234    is_array_of_tables: bool,
235    callback: &mut F,
236) -> Result
237where
238    F: FnMut(&'t Table, &Vec<&'t Key>, bool) -> Result,
239{
240    callback(table, path, is_array_of_tables)?;
241
242    for kv in table.items.values() {
243        match kv.value {
244            Item::Table(ref t) if !t.is_dotted() => {
245                path.push(&kv.key);
246                visit_nested_tables(t, path, false, callback)?;
247                path.pop();
248            }
249            Item::ArrayOfTables(ref a) => {
250                for t in a.iter() {
251                    path.push(&kv.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        path.encode(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        path.encode(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        key_path.as_slice().encode(buf, input, DEFAULT_KEY_DECOR)?;
317        write!(buf, "=")?;
318        value.encode(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) = match (style, literal) {
336        (Some(style), Some(literal)) => (style, literal),
337        (_, Some(literal)) => (infer_style(value).0, literal),
338        (Some(style), _) => (style, infer_style(value).1),
339        (_, _) => infer_style(value),
340    };
341
342    let mut output = String::with_capacity(value.len() * 2);
343    if literal {
344        output.push_str(style.literal_start());
345        output.push_str(value);
346        output.push_str(style.literal_end());
347    } else {
348        output.push_str(style.standard_start());
349        for ch in value.chars() {
350            match ch {
351                '\u{8}' => output.push_str("\\b"),
352                '\u{9}' => output.push_str("\\t"),
353                '\u{a}' => match style {
354                    StringStyle::NewlineTripple => output.push('\n'),
355                    StringStyle::OnelineSingle => output.push_str("\\n"),
356                    _ => unreachable!(),
357                },
358                '\u{c}' => output.push_str("\\f"),
359                '\u{d}' => output.push_str("\\r"),
360                '\u{22}' => output.push_str("\\\""),
361                '\u{5c}' => output.push_str("\\\\"),
362                c if c <= '\u{1f}' || c == '\u{7f}' => {
363                    write!(output, "\\u{:04X}", ch as u32).unwrap();
364                }
365                ch => output.push(ch),
366            }
367        }
368        output.push_str(style.standard_end());
369    }
370
371    Repr::new_unchecked(output)
372}
373
374#[derive(Copy, Clone, Debug, PartialEq, Eq)]
375pub(crate) enum StringStyle {
376    NewlineTripple,
377    OnelineTripple,
378    OnelineSingle,
379}
380
381impl StringStyle {
382    fn literal_start(self) -> &'static str {
383        match self {
384            Self::NewlineTripple => "'''\n",
385            Self::OnelineTripple => "'''",
386            Self::OnelineSingle => "'",
387        }
388    }
389    fn literal_end(self) -> &'static str {
390        match self {
391            Self::NewlineTripple => "'''",
392            Self::OnelineTripple => "'''",
393            Self::OnelineSingle => "'",
394        }
395    }
396
397    fn standard_start(self) -> &'static str {
398        match self {
399            Self::NewlineTripple => "\"\"\"\n",
400            // note: OnelineTripple can happen if do_pretty wants to do
401            // '''it's one line'''
402            // but literal == false
403            Self::OnelineTripple | Self::OnelineSingle => "\"",
404        }
405    }
406
407    fn standard_end(self) -> &'static str {
408        match self {
409            Self::NewlineTripple => "\"\"\"",
410            // note: OnelineTripple can happen if do_pretty wants to do
411            // '''it's one line'''
412            // but literal == false
413            Self::OnelineTripple | Self::OnelineSingle => "\"",
414        }
415    }
416}
417
418fn infer_style(value: &str) -> (StringStyle, bool) {
419    // For doing pretty prints we store in a new String
420    // because there are too many cases where pretty cannot
421    // work. We need to determine:
422    // - if we are a "multi-line" pretty (if there are \n)
423    // - if ['''] appears if multi or ['] if single
424    // - if there are any invalid control characters
425    //
426    // Doing it any other way would require multiple passes
427    // to determine if a pretty string works or not.
428    let mut out = String::with_capacity(value.len() * 2);
429    let mut ty = StringStyle::OnelineSingle;
430    // found consecutive single quotes
431    let mut max_found_singles = 0;
432    let mut found_singles = 0;
433    let mut prefer_literal = false;
434    let mut can_be_pretty = true;
435
436    for ch in value.chars() {
437        if can_be_pretty {
438            if ch == '\'' {
439                found_singles += 1;
440                if found_singles >= 3 {
441                    can_be_pretty = false;
442                }
443            } else {
444                if found_singles > max_found_singles {
445                    max_found_singles = found_singles;
446                }
447                found_singles = 0
448            }
449            match ch {
450                '\t' => {}
451                '\\' => {
452                    prefer_literal = true;
453                }
454                '\n' => ty = StringStyle::NewlineTripple,
455                // Escape codes are needed if any ascii control
456                // characters are present, including \b \f \r.
457                c if c <= '\u{1f}' || c == '\u{7f}' => can_be_pretty = false,
458                _ => {}
459            }
460            out.push(ch);
461        } else {
462            // the string cannot be represented as pretty,
463            // still check if it should be multiline
464            if ch == '\n' {
465                ty = StringStyle::NewlineTripple;
466            }
467        }
468    }
469    if found_singles > 0 && value.ends_with('\'') {
470        // We cannot escape the ending quote so we must use """
471        can_be_pretty = false;
472    }
473    if !prefer_literal {
474        can_be_pretty = false;
475    }
476    if !can_be_pretty {
477        debug_assert!(ty != StringStyle::OnelineTripple);
478        return (ty, false);
479    }
480    if found_singles > max_found_singles {
481        max_found_singles = found_singles;
482    }
483    debug_assert!(max_found_singles < 3);
484    if ty == StringStyle::OnelineSingle && max_found_singles >= 1 {
485        // no newlines, but must use ''' because it has ' in it
486        ty = StringStyle::OnelineTripple;
487    }
488    (ty, true)
489}
490
491impl ValueRepr for i64 {
492    fn to_repr(&self) -> Repr {
493        Repr::new_unchecked(self.to_string())
494    }
495}
496
497impl ValueRepr for f64 {
498    fn to_repr(&self) -> Repr {
499        to_f64_repr(*self)
500    }
501}
502
503fn to_f64_repr(f: f64) -> Repr {
504    let repr = match (f.is_sign_negative(), f.is_nan(), f == 0.0) {
505        (true, true, _) => "-nan".to_owned(),
506        (false, true, _) => "nan".to_owned(),
507        (true, false, true) => "-0.0".to_owned(),
508        (false, false, true) => "0.0".to_owned(),
509        (_, false, false) => {
510            if f % 1.0 == 0.0 {
511                format!("{}.0", f)
512            } else {
513                format!("{}", f)
514            }
515        }
516    };
517    Repr::new_unchecked(repr)
518}
519
520impl ValueRepr for bool {
521    fn to_repr(&self) -> Repr {
522        Repr::new_unchecked(self.to_string())
523    }
524}
525
526impl ValueRepr for Datetime {
527    fn to_repr(&self) -> Repr {
528        Repr::new_unchecked(self.to_string())
529    }
530}