Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/src/fmt/spec.rs

10.8 KiB, 1 run

created by r1870400018:11636, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1//! Format specification.
2//!
3//! Defines the formatting rules that control how each syntactic
4//! construct is laid out. The spec is a plain Rust struct that can
5//! be loaded from a JDAT configuration file.
6//!
7
8use oxedyne_fe2o3_core::prelude::*;
9
10
11/// How to break a parameter or argument list.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum ListLayout {
14 /// Keep everything on one line if it fits.
15 Compressed,
16 /// If any item doesn't fit, put all items on separate lines.
17 Tall,
18 /// Always put each item on its own line.
19 Vertical,
20}
21
22impl Default for ListLayout {
23 fn default() -> Self { Self::Tall }
24}
25
26/// Where to place the opening brace.
27#[derive(Clone, Copy, Debug, Eq, PartialEq)]
28pub enum BraceStyle {
29 /// Always on the same line as the declaration.
30 SameLine,
31 /// On its own line when a where clause or generics are present.
32 SameLineUnlessWhere,
33 /// Always on its own line.
34 NextLine,
35}
36
37impl Default for BraceStyle {
38 fn default() -> Self { Self::SameLineUnlessWhere }
39}
40
41/// Where to break a binary operator continuation.
42#[derive(Clone, Copy, Debug, Eq, PartialEq)]
43pub enum BinopBreak {
44 /// Break before the operator.
45 Before,
46 /// Break after the operator.
47 After,
48}
49
50impl Default for BinopBreak {
51 fn default() -> Self { Self::Before }
52}
53
54/// Where to place the return type when params go vertical.
55#[derive(Clone, Copy, Debug, Eq, PartialEq)]
56pub enum ReturnTypePlacement {
57 /// On the same line as the closing parenthesis.
58 SameLine,
59 /// On its own indented line (the fe2o3 style).
60 OwnLine,
61}
62
63impl Default for ReturnTypePlacement {
64 fn default() -> Self { Self::OwnLine }
65}
66
67/// Complete formatting specification.
68#[derive(Clone, Debug)]
69pub struct FormatSpec {
70 // ── Global ───────────────────────────────────────────────
71 /// Indentation unit.
72 pub indent_width: u16,
73 /// Use tabs for indentation.
74 pub use_tabs: bool,
75 /// Maximum line width (soft limit).
76 pub max_width: usize,
77 /// Maximum consecutive blank lines allowed.
78 pub max_blank_lines: usize,
79
80 // ── Functions ────────────────────────────────────────────
81 /// How to lay out function parameters.
82 pub fn_params_layout: ListLayout,
83 /// Where to place the return type when params are vertical.
84 pub fn_return_type: ReturnTypePlacement,
85 /// Allow single-expression functions on one line.
86 pub fn_single_line: bool,
87 /// Maximum width for a single-line function.
88 pub fn_single_line_max: usize,
89
90 // ── Braces ───────────────────────────────────────────────
91 /// Opening brace placement for items (fn, struct, impl, ...).
92 pub brace_style: BraceStyle,
93
94 // ── Imports ──────────────────────────────────────────────
95 /// How to lay out items within a use-tree.
96 pub import_layout: ListLayout,
97 /// Reorder import items alphabetically.
98 pub import_reorder: bool,
99
100 // ── Match ────────────────────────────────────────────────
101 /// Trailing comma after block-based match arms.
102 pub match_trailing_comma: bool,
103
104 // ── Alignment ────────────────────────────────────────────
105 /// Align struct/enum fields whose name lengths differ by up
106 /// to this threshold. 0 disables alignment.
107 pub field_align_threshold: usize,
108
109 // ── Where clauses ───────────────────────────────────────
110 /// Indent predicates inside a where clause.
111 pub where_indent: bool,
112
113 // ── Binary expressions ───────────────────────────────────
114 /// Where to break binary operators.
115 pub binop_break: BinopBreak,
116
117 // ── Method chains ────────────────────────────────────────
118 /// Maximum chain width before breaking.
119 pub chain_max_width: usize,
120
121 // ── Trailing commas ──────────────────────────────────────
122 /// Add trailing commas in vertical lists.
123 pub trailing_comma: bool,
124
125 // ── Comments ─────────────────────────────────────────────
126 /// Reflow comment text to fit max_width.
127 pub reflow_comments: bool,
128}
129
130impl Default for FormatSpec {
131 fn default() -> Self {
132 Self {
133 indent_width: 4,
134 use_tabs: false,
135 max_width: 100,
136 max_blank_lines: 2,
137 fn_params_layout: ListLayout::Tall,
138 fn_return_type: ReturnTypePlacement::OwnLine,
139 fn_single_line: true,
140 fn_single_line_max: 80,
141 brace_style: BraceStyle::SameLineUnlessWhere,
142 import_layout: ListLayout::Vertical,
143 import_reorder: false,
144 match_trailing_comma: true,
145 field_align_threshold: 40,
146 where_indent: true,
147 binop_break: BinopBreak::Before,
148 chain_max_width: 80,
149 trailing_comma: true,
150 reflow_comments: false,
151 }
152 }
153}
154
155impl FormatSpec {
156 /// Specification that matches the Oxedyne coding style.
157 pub fn fe2o3() -> Self {
158 Self::default()
159 }
160
161 /// The indentation string for one level.
162 pub fn indent_str(&self) -> String {
163 if self.use_tabs {
164 "\t".to_string()
165 } else {
166 " ".repeat(self.indent_width as usize)
167 }
168 }
169
170 /// Parse a format specification from a simple text config.
171 ///
172 /// Each line is `key = value`. Blank lines and lines starting
173 /// with `#` are ignored. Unknown keys produce an error.
174 ///
175 /// ```text
176 /// # Oxedyne style
177 /// indent_width = 4
178 /// max_width = 100
179 /// where_indent = true
180 /// fn_return_type = own_line
181 /// brace_style = same_line_unless_where
182 /// ```
183 pub fn from_config_str(s: &str) -> Outcome<Self> {
184 let mut spec = Self::default();
185 for (lineno, line) in s.lines().enumerate() {
186 let line = line.trim();
187 if line.is_empty() || line.starts_with('#') {
188 continue;
189 }
190 let (key, val) = match line.split_once('=') {
191 Some((k, v)) => (k.trim(), v.trim()),
192 None => return Err(err!(
193 "Line {}: expected 'key = value', got '{}'", lineno + 1, line;
194 Invalid, Input, Configuration)),
195 };
196 match key {
197 "indent_width" => spec.indent_width = res!(val.parse::<u16>()),
198 "use_tabs" => spec.use_tabs = val == "true",
199 "max_width" => spec.max_width = res!(val.parse::<usize>()),
200 "max_blank_lines" => spec.max_blank_lines = res!(val.parse::<usize>()),
201 "fn_single_line" => spec.fn_single_line = val == "true",
202 "fn_single_line_max" => spec.fn_single_line_max = res!(val.parse::<usize>()),
203 "import_reorder" => spec.import_reorder = val == "true",
204 "match_trailing_comma" => spec.match_trailing_comma = val == "true",
205 "field_align_threshold" => spec.field_align_threshold = res!(val.parse::<usize>()),
206 "where_indent" => spec.where_indent = val == "true",
207 "chain_max_width" => spec.chain_max_width = res!(val.parse::<usize>()),
208 "trailing_comma" => spec.trailing_comma = val == "true",
209 "reflow_comments" => spec.reflow_comments = val == "true",
210 "fn_params_layout" => {
211 spec.fn_params_layout = match val {
212 "compressed" => ListLayout::Compressed,
213 "tall" => ListLayout::Tall,
214 "vertical" => ListLayout::Vertical,
215 _ => return Err(err!(
216 "Unknown fn_params_layout: '{}'", val;
217 Invalid, Input, Configuration)),
218 };
219 }
220 "fn_return_type" => {
221 spec.fn_return_type = match val {
222 "same_line" => ReturnTypePlacement::SameLine,
223 "own_line" => ReturnTypePlacement::OwnLine,
224 _ => return Err(err!(
225 "Unknown fn_return_type: '{}'", val;
226 Invalid, Input, Configuration)),
227 };
228 }
229 "brace_style" => {
230 spec.brace_style = match val {
231 "same_line" => BraceStyle::SameLine,
232 "same_line_unless_where" => BraceStyle::SameLineUnlessWhere,
233 "next_line" => BraceStyle::NextLine,
234 _ => return Err(err!(
235 "Unknown brace_style: '{}'", val;
236 Invalid, Input, Configuration)),
237 };
238 }
239 "import_layout" => {
240 spec.import_layout = match val {
241 "compressed" => ListLayout::Compressed,
242 "tall" => ListLayout::Tall,
243 "vertical" => ListLayout::Vertical,
244 _ => return Err(err!(
245 "Unknown import_layout: '{}'", val;
246 Invalid, Input, Configuration)),
247 };
248 }
249 "binop_break" => {
250 spec.binop_break = match val {
251 "before" => BinopBreak::Before,
252 "after" => BinopBreak::After,
253 _ => return Err(err!(
254 "Unknown binop_break: '{}'", val;
255 Invalid, Input, Configuration)),
256 };
257 }
258 _ => return Err(err!(
259 "Unknown config key: '{}'", key;
260 Invalid, Input, Configuration)),
261 }
262 }
263 Ok(spec)
264 }
265}