toml_parse/toml_fmt/
mod.rs

1use std::fmt;
2
3pub(self) use super::tkn_tree::{self, walk::walk_tokens_non_ws, SyntaxNode, TomlKind};
4
5mod block;
6mod rules;
7mod ws;
8
9use block::Block;
10use rules::{
11    indent_after_comma, indent_after_open_brace, lf_after_heading, lf_after_table, none_around_dot,
12    space_around_eq, space_lf_after_array_open, space_lf_after_comma,
13    space_lf_after_inline_table_open, space_lf_before_array_close,
14    space_lf_before_inline_table_close,
15};
16use ws::WhiteSpace;
17
18type RuleFn = Box<dyn for<'a> Fn(&'a Block, &'a Block) -> Option<WhiteSpace>>;
19
20/// Formatter impl's `Display` so once `format()` has been called, the resulting
21/// text can be retrieved.
22pub struct Formatter {
23    blocks: Vec<Block>,
24    rules: Vec<(TomlKind, RuleFn)>,
25    formatted: String,
26}
27
28impl Formatter {
29    /// Creates new instance of `Formatter` for formatting a tree of `rowan::SyntaxNode`s.
30    pub fn new(root: &SyntaxNode) -> Formatter {
31        Self {
32            blocks: walk_tokens_non_ws(root).map(Block::new).collect(),
33            rules: formatter(),
34            formatted: String::default(),
35        }
36    }
37    /// `format` the tree of `rowan::SyntaxElements` according to valid toml.
38    pub fn format(mut self) -> Self {
39        let zipped = self.blocks.iter().zip(self.blocks.iter().skip(1));
40
41        for (l_blk, r_blk) in zipped {
42            let rules = self
43                .rules
44                .iter()
45                .filter(|(kind, _)| *kind == l_blk.kind())
46                .chain(self.rules.iter().filter(|(kind, _)| *kind == r_blk.kind()))
47                .map(|(_, func)| func);
48
49            for rule in rules {
50                if let Some(fixed) = rule(l_blk, r_blk) {
51                    r_blk.whitespace.set(fixed);
52                }
53            }
54        }
55        self.formatted = self
56            .blocks
57            .clone()
58            .into_iter()
59            .map(|b| b.to_string())
60            .collect();
61
62        if !self.formatted.ends_with('\n') {
63            self.formatted.push('\n')
64        }
65        self
66    }
67}
68impl fmt::Debug for Formatter {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.debug_struct("Formatter")
71            .field("blocks", &self.blocks)
72            .field(
73                "rules",
74                &self.rules.iter().map(|(k, _fn)| k).collect::<Vec<_>>(),
75            )
76            .field("formatted", &self.formatted)
77            .finish()
78    }
79}
80
81impl fmt::Display for Formatter {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self.formatted)
84    }
85}
86
87pub fn formatter() -> Vec<(TomlKind, RuleFn)> {
88    vec![
89        // WHITESPACE
90        // empty line between tables
91        (TomlKind::OpenBrace, Box::new(lf_after_table) as RuleFn),
92        // newline after heading before key values
93        (TomlKind::CloseBrace, Box::new(lf_after_heading) as RuleFn),
94        // nothing around dot in heading
95        (TomlKind::Dot, Box::new(none_around_dot) as RuleFn),
96        // space around equal sign
97        (TomlKind::Equal, Box::new(space_around_eq) as RuleFn),
98        // space or newline after comma in array or inline table
99        (TomlKind::Comma, Box::new(space_lf_after_comma) as RuleFn),
100        // space or newline after array open brace
101        (
102            TomlKind::OpenCurly,
103            Box::new(space_lf_after_inline_table_open) as RuleFn,
104        ),
105        // space or newline before closing curly brace of inline table
106        (
107            TomlKind::CloseCurly,
108            Box::new(space_lf_before_inline_table_close) as RuleFn,
109        ),
110        // space or newline after open brace of array
111        (
112            TomlKind::OpenBrace,
113            Box::new(space_lf_after_array_open) as RuleFn,
114        ),
115        // space or newline before closing brace of array
116        (
117            TomlKind::CloseBrace,
118            Box::new(space_lf_before_array_close) as RuleFn,
119        ),
120        // INDENT
121        // indent after open brace if siblings are indented
122        (
123            TomlKind::OpenBrace,
124            Box::new(indent_after_open_brace) as RuleFn,
125        ),
126        // indent after comma if siblings are indented
127        (TomlKind::Comma, Box::new(indent_after_comma) as RuleFn),
128    ]
129}