Skip to main content

leekscript_tooling/formatter/
config.rs

1//! Load formatter options from config files (.leekfmt.toml or leekscript.toml [format] section).
2
3use std::path::Path;
4
5use super::options::{BraceStyle, FormatterOptions, IndentStyle, SemicolonStyle};
6
7/// Try to load formatter options from a directory: first `.leekfmt.toml`, then `leekscript.toml` with `[format]` section.
8/// Returns `None` if no file is found or parsing fails; caller falls back to `FormatterOptions::default()`.
9#[must_use]
10pub fn load_formatter_options_from_dir(dir: &Path) -> Option<FormatterOptions> {
11    let leekfmt = dir.join(".leekfmt.toml");
12    if leekfmt.exists() {
13        return load_from_file(&leekfmt);
14    }
15    let manifest = dir.join("leekscript.toml");
16    if manifest.exists() {
17        if let Ok(content) = std::fs::read_to_string(&manifest) {
18            if let Ok(t) = content.parse::<toml::Table>() {
19                if let Some(format_table) = t.get("format").and_then(|v| v.as_table()) {
20                    return parse_format_table(format_table);
21                }
22            }
23        }
24    }
25    None
26}
27
28/// Load formatter options from a single config file (e.g. `.leekfmt.toml`) with a top-level `[format]` section.
29#[must_use]
30pub fn load_formatter_options_from_file(path: &Path) -> Option<FormatterOptions> {
31    load_from_file(path)
32}
33
34fn load_from_file(path: &Path) -> Option<FormatterOptions> {
35    let content = std::fs::read_to_string(path).ok()?;
36    let t = content.parse::<toml::Table>().ok()?;
37    let format_table = t.get("format").and_then(|v| v.as_table())?;
38    parse_format_table(format_table)
39}
40
41fn parse_format_table(t: &toml::map::Map<String, toml::Value>) -> Option<FormatterOptions> {
42    let indent_style = t
43        .get("indent")
44        .and_then(|v| v.as_str())
45        .map_or(IndentStyle::Tabs, parse_indent_style);
46
47    let brace_style = t
48        .get("brace_style")
49        .and_then(|v| v.as_str())
50        .map_or(BraceStyle::SameLine, parse_brace_style);
51
52    let semicolon_style = t
53        .get("semicolon_style")
54        .and_then(|v| v.as_str())
55        .map_or(SemicolonStyle::Always, parse_semicolon_style);
56
57    let canonical_format = t
58        .get("canonical")
59        .and_then(toml::Value::as_bool)
60        .unwrap_or(false);
61
62    Some(FormatterOptions {
63        preserve_comments: t
64            .get("preserve_comments")
65            .and_then(toml::Value::as_bool)
66            .unwrap_or(true),
67        parenthesize_expressions: t
68            .get("parenthesize_expressions")
69            .and_then(toml::Value::as_bool)
70            .unwrap_or(false),
71        annotate_types: false,
72        signature_roots: None,
73        canonical_format,
74        indent_style,
75        brace_style,
76        semicolon_style,
77    })
78}
79
80fn parse_indent_style(s: &str) -> IndentStyle {
81    let s = s.trim().to_lowercase();
82    if s == "tabs" {
83        return IndentStyle::Tabs;
84    }
85    if s.starts_with("spaces") {
86        let n = s
87            .trim_start_matches("spaces")
88            .trim()
89            .parse::<u32>()
90            .unwrap_or(4);
91        return IndentStyle::Spaces(n);
92    }
93    IndentStyle::Tabs
94}
95
96fn parse_brace_style(s: &str) -> BraceStyle {
97    match s.trim().to_lowercase().as_str() {
98        "next-line" | "nextline" => BraceStyle::NextLine,
99        _ => BraceStyle::SameLine,
100    }
101}
102
103fn parse_semicolon_style(s: &str) -> SemicolonStyle {
104    match s.trim().to_lowercase().as_str() {
105        "omit" => SemicolonStyle::Omit,
106        _ => SemicolonStyle::Always,
107    }
108}