brewery
notashelf /
ccc30d1bbdf943a485a4a478f0f80165eaec3571

beer

public

A terminal worth pouring time into

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/beer.git
src/config.rsRust131 lines4.0 KB
1
//! User configuration: a TOML file at `$XDG_CONFIG_HOME/beer/beer.toml`
2
//! deserialized into a typed [`Config`]. A missing file uses defaults; a
3
//! malformed one warns and falls back to defaults rather than failing to start.
4
5
use std::path::{Path, PathBuf};
6
7
use serde::Deserialize;
8
9
/// Top-level configuration. Unknown keys are ignored so a config written for a
10
/// newer beer still loads.
11
#[derive(Debug, Clone, Default, Deserialize)]
12
#[serde(default, rename_all = "kebab-case")]
13
pub struct Config {
14
    pub main: Main,
15
    pub scrollback: Scrollback,
16
}
17
18
/// `[main]`: fonts, window geometry, padding, and the terminal name.
19
#[derive(Debug, Clone, Deserialize)]
20
#[serde(default, rename_all = "kebab-case")]
21
pub struct Main {
22
    /// Primary font family, resolved via fontconfig.
23
    pub font: String,
24
    /// Font size in pixels.
25
    pub font_size: u32,
26
    /// `TERM` value exported to the child shell.
27
    pub term: String,
28
    /// Initial size in character cells.
29
    pub initial_cols: u16,
30
    pub initial_rows: u16,
31
    /// Characters that break a word for double-click selection. Empty/unset
32
    /// keeps the built-in default.
33
    pub word_delimiters: Option<String>,
34
}
35
36
impl Default for Main {
37
    fn default() -> Self {
38
        Self {
39
            font: "monospace".to_string(),
40
            font_size: 16,
41
            term: "beer".to_string(),
42
            initial_cols: 80,
43
            initial_rows: 24,
44
            word_delimiters: None,
45
        }
46
    }
47
}
48
49
/// `[scrollback]`: history retention.
50
#[derive(Debug, Clone, Deserialize)]
51
#[serde(default, rename_all = "kebab-case")]
52
pub struct Scrollback {
53
    /// Lines of history retained for the main screen.
54
    pub lines: usize,
55
}
56
57
impl Default for Scrollback {
58
    fn default() -> Self {
59
        Self { lines: 10_000 }
60
    }
61
}
62
63
impl Config {
64
    /// Load configuration from `explicit` if given, else the default path.
65
    /// Any read/parse failure logs a warning and returns defaults.
66
    pub fn load(explicit: Option<&Path>) -> Self {
67
        let Some(path) = explicit.map(Path::to_path_buf).or_else(default_path) else {
68
            return Self::default();
69
        };
70
        if !path.exists() {
71
            return Self::default();
72
        }
73
        match std::fs::read_to_string(&path) {
74
            Ok(text) => match toml::from_str(&text) {
75
                Ok(config) => {
76
                    tracing::info!("loaded config from {}", path.display());
77
                    config
78
                }
79
                Err(err) => {
80
                    tracing::warn!("config {}: {err}; using defaults", path.display());
81
                    Self::default()
82
                }
83
            },
84
            Err(err) => {
85
                tracing::warn!("read config {}: {err}; using defaults", path.display());
86
                Self::default()
87
            }
88
        }
89
    }
90
}
91
92
/// `$XDG_CONFIG_HOME/beer/beer.toml`, or `~/.config/beer/beer.toml`.
93
fn default_path() -> Option<PathBuf> {
94
    if let Some(dir) = std::env::var_os("XDG_CONFIG_HOME").filter(|s| !s.is_empty()) {
95
        return Some(PathBuf::from(dir).join("beer/beer.toml"));
96
    }
97
    let home = std::env::var_os("HOME")?;
98
    Some(PathBuf::from(home).join(".config/beer/beer.toml"))
99
}
100
101
#[cfg(test)]
102
mod tests {
103
    use super::*;
104
105
    #[test]
106
    fn defaults_are_sane() {
107
        let c = Config::default();
108
        assert_eq!(c.main.font, "monospace");
109
        assert_eq!(c.main.font_size, 16);
110
        assert_eq!(c.scrollback.lines, 10_000);
111
    }
112
113
    #[test]
114
    fn parses_partial_config_and_ignores_unknown() {
115
        let toml = r##"
116
            [main]
117
            font = "JetBrains Mono"
118
            font-size = 14
119
            unknown-key = "tolerated"
120
121
            [colors]
122
            background = "#000000"
123
        "##;
124
        let c: Config = toml::from_str(toml).unwrap();
125
        assert_eq!(c.main.font, "JetBrains Mono");
126
        assert_eq!(c.main.font_size, 14);
127
        // Unset keys keep defaults; unknown tables/keys are ignored.
128
        assert_eq!(c.main.term, "beer");
129
        assert_eq!(c.scrollback.lines, 10_000);
130
    }
131
}