tergo_formatter/
config.rs

1use serde::Deserialize;
2
3pub trait FormattingConfig: std::fmt::Display + Clone {
4    fn line_length(&self) -> i32;
5    fn indent(&self) -> i32;
6    // Custom embracing behaviour: https://style.tidyverse.org/syntax.html#embracing
7    fn embracing_op_no_nl(&self) -> bool;
8    fn allow_nl_after_assignment(&self) -> bool;
9    fn space_before_complex_rhs_in_formulas(&self) -> bool;
10    fn strip_suffix_whitespace_in_function_defs(&self) -> bool;
11    fn function_line_breaks(&self) -> FunctionLineBreaks;
12    fn insert_newline_in_quote_call(&self) -> bool;
13}
14
15#[derive(Debug, Clone, Copy, Deserialize, Default, Eq, PartialEq)]
16#[serde(rename_all = "lowercase")]
17pub enum FunctionLineBreaks {
18    #[default]
19    Hanging,
20    Double,
21    Single,
22}
23
24/// The configuration for `tergo`.
25///
26/// This configuration can also read from a TOML file.
27#[derive(Debug, Clone, Deserialize, Default)]
28pub struct Config {
29    /// The number of characters to use for one level of indentation.
30    ///
31    /// Default: 2.
32    #[serde(default)]
33    pub indent: Indent,
34
35    /// Tha maximum number of characters in a line of the formatted
36    /// code. `tergo` will ensure lines do not exceed this number
37    /// if possible.
38    ///
39    /// Default: 120.
40    #[serde(default)]
41    pub line_length: LineLength,
42
43    /// A logical flag to determine whether to suppress line
44    /// breaks for embracing operator `{{}}`.
45    ///
46    /// If true, the formatter outputs the following code:
47    ///
48    /// ```R
49    ///  data |>
50    ///    group_by({{ by }})
51    /// ````
52    ///
53    /// instead of inserting a new line after each `{`.
54    ///
55    /// Default: true.
56    #[serde(default)]
57    pub embracing_op_no_nl: EmbracingOpNoNl,
58
59    /// A logical flag indicating whether to insert new lines after
60    /// the assignment operator `<-` in cases where the code
61    /// does not fit a single line (so a line break is needed somewhere).
62    ///
63    /// The formatter outputs the following:
64    ///
65    /// ```R
66    /// a <- TRUE # for allow_nl_after_assignment = false
67    /// # or
68    /// a <-
69    ///   TRUE # for allow_nl_after_assignment = true
70    /// ```
71    ///
72    /// in cases where the code does not fit in a single line.
73    ///
74    /// Default: false.
75    #[serde(default)]
76    pub allow_nl_after_assignment: AllowNlAfterAssignment,
77
78    /// A logical flag indicating whether to put a space before complex right hand sides of
79    /// the formula operator. Example:
80    ///
81    /// ```R
82    /// # If space_before_complex_rhs_in_formula = true
83    /// ~ a + b
84    /// ~a
85    ///
86    /// # If space_before_complex_rhs_in_formula = false
87    /// ~a + b
88    /// ~a
89    /// ```
90    ///
91    /// Default: true.
92    #[serde(default)]
93    pub space_before_complex_rhs_in_formula: SpaceBeforeComplexRhsInFormulas,
94
95    /// A logical flag indicating whether to keep the whitespace before the ending
96    /// bracket of a function definition in cases such as this:
97    ///
98    /// ```R
99    /// function() {
100    ///   TRUE
101    ///
102    /// }
103    /// ```
104    ///
105    /// If true, `tergo` will remove the whitespace:
106    ///
107    /// ```R
108    /// function() {
109    ///   TRUE
110    /// }
111    /// ```
112    ///
113    /// Default: true.
114    #[serde(default)]
115    pub strip_suffix_whitespace_in_function_defs: StripSuffixWhitespaceInFunctionDefs,
116
117    /// The type of line breaking inside function definitions'
118    /// arguments. Possible values are: `single`, `double`, `hanging`.
119    /// Single puts a single level of indent for the arguments, double
120    /// puts a double level of indent and hanging puts the arguments
121    /// in the same column as the first argument to the function.
122    ///
123    /// Examples:
124    ///
125    /// ```R
126    /// # Single:
127    /// function(
128    ///   a
129    /// ) {}
130    ///
131    /// # Double:
132    /// function(
133    ///     a
134    /// ) {}
135    ///
136    /// # Hanging:
137    /// function(a,
138    ///          b) {}
139    /// ```
140    ///
141    /// Default: `hanging`.
142    #[serde(default)]
143    pub function_line_breaks: FunctionLineBreaks,
144
145    /// A logical flag indicating whether to insert a new line after
146    /// the opening parenthesis of a call to quote for very long calls.
147    ///
148    /// Examples:
149    ///
150    /// ```R
151    /// # If insert_newline_in_quote_call = false
152    /// quote(a <- function(call) {
153    ///   TRUE
154    ///   TRUE
155    /// })
156    ///
157    /// # vs
158    ///
159    /// # If insert_newline_in_quote_call = true
160    /// quote(
161    ///   a <- function(call) {
162    ///     TRUE
163    ///     TRUE
164    ///   }
165    /// )
166    /// ```
167    ///
168    /// Default: true.
169    #[serde(default)]
170    pub insert_newline_in_quote_call: InsertNewlineInQuoteCall,
171
172    /// A list of file paths to exclude from formatting.
173    ///
174    /// The file paths are relative to the directory
175    /// in which `tergo` is run.
176    ///
177    /// Example values:
178    ///
179    /// exclusion_list = ["./balnea",
180    /// "./aqua",
181    /// "./scopa",
182    /// "./spongia",
183    /// "./tergo",
184    /// "./unguentum",
185    /// "./antidotum/tergo/R/extendr-wrappers.R",
186    /// "./target"]
187    #[serde(default)]
188    pub exclusion_list: ExclusionList,
189}
190
191#[derive(Debug, Deserialize, Clone, Copy)]
192pub struct Indent(pub i32);
193impl Default for Indent {
194    fn default() -> Self {
195        Self(2)
196    }
197}
198#[derive(Debug, Deserialize, Clone, Copy)]
199pub struct LineLength(pub i32);
200impl Default for LineLength {
201    fn default() -> Self {
202        Self(120)
203    }
204}
205
206#[derive(Debug, Deserialize, Clone, Copy)]
207pub struct EmbracingOpNoNl(pub bool);
208impl Default for EmbracingOpNoNl {
209    fn default() -> Self {
210        Self(true)
211    }
212}
213
214#[derive(Debug, Deserialize, Clone, Copy, Default)]
215pub struct AllowNlAfterAssignment(pub bool);
216
217#[derive(Debug, Deserialize, Clone, Copy)]
218pub struct SpaceBeforeComplexRhsInFormulas(pub bool);
219impl Default for SpaceBeforeComplexRhsInFormulas {
220    fn default() -> Self {
221        Self(true)
222    }
223}
224
225#[derive(Debug, Deserialize, Clone, Copy)]
226pub struct StripSuffixWhitespaceInFunctionDefs(pub bool);
227impl Default for StripSuffixWhitespaceInFunctionDefs {
228    fn default() -> Self {
229        Self(true)
230    }
231}
232
233#[derive(Debug, Deserialize, Clone, Copy)]
234pub struct InsertNewlineInQuoteCall(pub bool);
235impl Default for InsertNewlineInQuoteCall {
236    fn default() -> Self {
237        Self(true)
238    }
239}
240
241#[derive(Debug, Deserialize, Clone, Default)]
242pub struct ExclusionList(pub Vec<String>);
243
244impl FormattingConfig for Config {
245    fn line_length(&self) -> i32 {
246        self.line_length.0
247    }
248
249    fn indent(&self) -> i32 {
250        self.indent.0
251    }
252
253    fn embracing_op_no_nl(&self) -> bool {
254        self.embracing_op_no_nl.0
255    }
256
257    fn allow_nl_after_assignment(&self) -> bool {
258        self.allow_nl_after_assignment.0
259    }
260
261    fn space_before_complex_rhs_in_formulas(&self) -> bool {
262        self.space_before_complex_rhs_in_formula.0
263    }
264
265    fn strip_suffix_whitespace_in_function_defs(&self) -> bool {
266        self.strip_suffix_whitespace_in_function_defs.0
267    }
268
269    fn function_line_breaks(&self) -> FunctionLineBreaks {
270        self.function_line_breaks
271    }
272
273    fn insert_newline_in_quote_call(&self) -> bool {
274        self.insert_newline_in_quote_call.0
275    }
276}
277
278impl std::fmt::Display for Config {
279    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
280        f.write_fmt(format_args!(
281            "indent: {} line_length: {} allow_nl_after_assignment: {}",
282            self.indent.0, self.line_length.0, self.allow_nl_after_assignment.0
283        ))
284    }
285}
286
287#[allow(clippy::too_many_arguments)]
288impl Config {
289    pub fn new(
290        indent: i32,
291        line_length: i32,
292        embracing_op_no_nl: bool,
293        allow_nl_after_assignment: bool,
294        space_before_complex_rhs_in_formula: bool,
295        strip_suffix_whitespace_in_function_defs: bool,
296        function_line_breaks: FunctionLineBreaks,
297        insert_newline_in_quote_call: bool,
298        exclusion_list: Vec<String>,
299    ) -> Self {
300        Self {
301            indent: Indent(indent),
302            line_length: LineLength(line_length),
303            embracing_op_no_nl: EmbracingOpNoNl(embracing_op_no_nl),
304            allow_nl_after_assignment: AllowNlAfterAssignment(allow_nl_after_assignment),
305            space_before_complex_rhs_in_formula: SpaceBeforeComplexRhsInFormulas(
306                space_before_complex_rhs_in_formula,
307            ),
308            strip_suffix_whitespace_in_function_defs: StripSuffixWhitespaceInFunctionDefs(
309                strip_suffix_whitespace_in_function_defs,
310            ),
311            function_line_breaks,
312            insert_newline_in_quote_call: InsertNewlineInQuoteCall(insert_newline_in_quote_call),
313            exclusion_list: ExclusionList(exclusion_list),
314        }
315    }
316}