ferrite_config/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
use anyhow::{Context, Result};
use config::{Config, File};
use directories::ProjectDirs;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use tracing::instrument;

#[derive(Debug, Serialize, Deserialize)]
pub struct WindowConfig {
    pub width: Option<u32>,
    pub height: Option<u32>,
    pub borderless: bool,
    pub hide_menu: bool,
}

impl Default for WindowConfig {
    fn default() -> Self {
        Self {
            width: None,
            height: None,
            borderless: false,
            hide_menu: false,
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct FeriteConfig {
    pub cache_size: usize,
    pub default_zoom: f32,
    pub show_performance: bool,
    pub recent_files: Vec<PathBuf>,
    pub max_recent_files: usize,
    pub zoom: ZoomConfig,
    pub window: WindowConfig,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Corner {
    TopLeft,
    TopRight,
    BottomLeft,
    BottomRight,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ZoomConfig {
    pub require_ctrl_for_zoom: bool,
    pub zoom_display_corner: Corner,
    pub show_zoom_level: bool,
}

impl Default for FeriteConfig {
    fn default() -> Self {
        Self {
            cache_size: 5,
            default_zoom: 1.0,
            show_performance: false,
            recent_files: Vec::new(),
            max_recent_files: 10,
            zoom: ZoomConfig::default(),
            window: WindowConfig::default(),
        }
    }
}

impl Default for Corner {
    fn default() -> Self {
        Corner::TopLeft
    }
}

impl Default for ZoomConfig {
    fn default() -> Self {
        Self {
            require_ctrl_for_zoom: false,
            zoom_display_corner: Corner::default(),
            show_zoom_level: true,
        }
    }
}

impl FeriteConfig {
    #[instrument]
    pub fn load() -> Result<Self> {
        let config_path = Self::get_config_path()?;

        if !config_path.exists() {
            return Err(anyhow::anyhow!(
                "No configuration file found at {:?}. Run with --generate-config to create one.",
                config_path
            ));
        }

        let config = Config::builder()
            .add_source(config::File::from_str(
                toml::to_string(&Self::default())?.as_str(),
                config::FileFormat::Toml,
            ))
            .add_source(File::from(config_path))
            .build()?;

        config
            .try_deserialize()
            .context("Failed to deserialize configuration")
    }

    #[instrument]
    pub fn save(&self) -> Result<()> {
        let config_path = Self::get_config_path()?;

        if let Some(parent) = config_path.parent() {
            std::fs::create_dir_all(parent).context("Failed to create config directory")?;
        }

        let toml = toml::to_string_pretty(self).context("Failed to serialize configuration")?;
        std::fs::write(&config_path, toml).context("Failed to write configuration file")?;

        Ok(())
    }

    fn get_config_path() -> Result<PathBuf> {
        let proj_dirs = ProjectDirs::from("com", "ferrite", "ferrite")
            .context("Failed to determine project directories")?;

        Ok(proj_dirs.config_dir().join("config.toml"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = FeriteConfig::default();
        assert_eq!(config.cache_size, 5);
        assert_eq!(config.default_zoom, 1.0);
        assert!(!config.show_performance);
        assert!(config.recent_files.is_empty());
        assert_eq!(config.max_recent_files, 10);
    }

    #[test]
    fn test_config_serialization() {
        let config = FeriteConfig::default();
        let serialized = toml::to_string(&config).unwrap();
        let deserialized: FeriteConfig = toml::from_str(&serialized).unwrap();
        assert_eq!(deserialized.cache_size, config.cache_size);
    }
}