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 | |
| 8 | use oxedyne_fe2o3_core::prelude::*; |
| 9 | |
| 10 | |
| 11 | /// How to break a parameter or argument list. |
| 12 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| 13 | pub 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 | |
| 22 | impl 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)] |
| 28 | pub 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 | |
| 37 | impl 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)] |
| 43 | pub enum BinopBreak { |
| 44 | /// Break before the operator. |
| 45 | Before, |
| 46 | /// Break after the operator. |
| 47 | After, |
| 48 | } |
| 49 | |
| 50 | impl 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)] |
| 56 | pub 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 | |
| 63 | impl Default for ReturnTypePlacement { |
| 64 | fn default() -> Self { Self::OwnLine } |
| 65 | } |
| 66 | |
| 67 | /// Complete formatting specification. |
| 68 | #[derive(Clone, Debug)] |
| 69 | pub 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 | |
| 130 | impl 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 | |
| 155 | impl 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 | } |