1use std::fmt::Write as _;
2
3#[non_exhaustive]
6#[derive(Debug)]
7pub struct FormatConfig<'a> {
8 pub indent_level: usize,
12
13 pub indent: &'a str,
15
16 pub no_comments: bool,
18
19 pub entry_autoformate_keep: bool,
21}
22
23impl Default for FormatConfig<'_> {
25 fn default() -> Self {
26 Self::builder().build()
27 }
28}
29
30impl FormatConfig<'_> {
31 pub const fn builder() -> FormatConfigBuilder<'static> {
33 FormatConfigBuilder::new()
34 }
35}
36
37#[derive(Debug, Default)]
41pub struct FormatConfigBuilder<'a>(FormatConfig<'a>);
42
43impl<'a> FormatConfigBuilder<'a> {
44 pub const fn new() -> Self {
46 Self(FormatConfig {
47 indent_level: 0,
48 indent: " ",
49 no_comments: false,
50 entry_autoformate_keep: false,
51 })
52 }
53
54 pub const fn maybe_indent_level(mut self, indent_level: Option<usize>) -> Self {
58 if let Some(indent_level) = indent_level {
59 self.0.indent_level = indent_level;
60 }
61 self
62 }
63
64 pub const fn indent_level(mut self, indent_level: usize) -> Self {
68 self.0.indent_level = indent_level;
69 self
70 }
71
72 pub const fn maybe_indent<'b, 'c>(self, indent: Option<&'b str>) -> FormatConfigBuilder<'c>
75 where
76 'a: 'b,
77 'b: 'c,
78 {
79 if let Some(indent) = indent {
80 self.indent(indent)
81 } else {
82 self
83 }
84 }
85
86 pub const fn indent(self, indent: &str) -> FormatConfigBuilder<'_> {
89 FormatConfigBuilder(FormatConfig { indent, ..self.0 })
90 }
91
92 pub const fn maybe_no_comments(mut self, no_comments: Option<bool>) -> Self {
95 if let Some(no_comments) = no_comments {
96 self.0.no_comments = no_comments;
97 }
98 self
99 }
100
101 pub const fn no_comments(mut self, no_comments: bool) -> Self {
104 self.0.no_comments = no_comments;
105 self
106 }
107
108 pub const fn build(self) -> FormatConfig<'a> {
110 self.0
111 }
112}
113
114pub(crate) fn autoformat_leading(leading: &mut String, config: &FormatConfig<'_>) {
115 let mut result = String::new();
116 if !config.no_comments {
117 let input = leading.trim();
118 if !input.is_empty() {
119 for line in input.lines() {
120 let trimmed = line.trim();
121 if !trimmed.is_empty() {
122 for _ in 0..config.indent_level {
123 result.push_str(config.indent);
124 }
125 writeln!(result, "{trimmed}").unwrap();
126 }
127 }
128 }
129 }
130 for _ in 0..config.indent_level {
131 result.push_str(config.indent);
132 }
133 *leading = result;
134}
135
136pub(crate) fn autoformat_trailing(decor: &mut String, no_comments: bool) {
137 if decor.is_empty() {
138 return;
139 }
140 *decor = decor.trim().to_string();
141 let mut result = String::new();
142 if !decor.is_empty() && !no_comments {
143 if decor.trim_start() == &decor[..] {
144 write!(result, " ").unwrap();
145 }
146 for comment in decor.lines() {
147 writeln!(result, "{comment}").unwrap();
148 }
149 }
150 *decor = result;
151}
152
153#[cfg(test)]
154mod test {
155 use super::*;
156
157 #[test]
158 fn builder() -> miette::Result<()> {
159 let built = FormatConfig::builder()
160 .indent_level(12)
161 .indent(" \t")
162 .no_comments(true)
163 .build();
164 assert!(matches!(
165 built,
166 FormatConfig {
167 indent_level: 12,
168 indent: " \t",
169 no_comments: true,
170 entry_autoformate_keep: false,
171 }
172 ));
173 Ok(())
174 }
175}