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