Rust · 7448 bytes Raw Blame History
1 //! Configuration loading
2 //!
3 //! Supports:
4 //! - Lua configuration via ~/.config/gar/init.lua (gar.calculator table)
5 //! - TOML fallback via ~/.config/garcalc/config.toml
6
7 use anyhow::Result;
8 use mlua::Lua;
9 use serde::{Deserialize, Serialize};
10 use std::path::PathBuf;
11
12 /// Calculator configuration
13 #[derive(Debug, Clone, Serialize, Deserialize)]
14 #[serde(default)]
15 pub struct Config {
16 pub general: GeneralConfig,
17 pub popup: PopupConfig,
18 pub graph: GraphConfig,
19 pub appearance: AppearanceConfig,
20 }
21
22 impl Default for Config {
23 fn default() -> Self {
24 Self {
25 general: GeneralConfig::default(),
26 popup: PopupConfig::default(),
27 graph: GraphConfig::default(),
28 appearance: AppearanceConfig::default(),
29 }
30 }
31 }
32
33 #[derive(Debug, Clone, Serialize, Deserialize)]
34 #[serde(default)]
35 pub struct GeneralConfig {
36 pub default_mode: String,
37 pub precision: u32,
38 pub angle_mode: String,
39 pub exact_mode: bool,
40 }
41
42 impl Default for GeneralConfig {
43 fn default() -> Self {
44 Self {
45 default_mode: "calculator".to_string(),
46 precision: 15,
47 angle_mode: "radians".to_string(),
48 exact_mode: false,
49 }
50 }
51 }
52
53 #[derive(Debug, Clone, Serialize, Deserialize)]
54 #[serde(default)]
55 pub struct PopupConfig {
56 pub width: u32,
57 pub height: u32,
58 pub position: String,
59 }
60
61 impl Default for PopupConfig {
62 fn default() -> Self {
63 Self {
64 width: 700,
65 height: 500,
66 position: "center".to_string(),
67 }
68 }
69 }
70
71 #[derive(Debug, Clone, Serialize, Deserialize)]
72 #[serde(default)]
73 pub struct GraphConfig {
74 pub default_x_range: (f64, f64),
75 pub default_y_range: (f64, f64),
76 pub grid_enabled: bool,
77 }
78
79 impl Default for GraphConfig {
80 fn default() -> Self {
81 Self {
82 default_x_range: (-10.0, 10.0),
83 default_y_range: (-10.0, 10.0),
84 grid_enabled: true,
85 }
86 }
87 }
88
89 #[derive(Debug, Clone, Serialize, Deserialize)]
90 #[serde(default)]
91 pub struct AppearanceConfig {
92 pub font_family: String,
93 pub font_size: u32,
94 pub button_panel_visible: bool,
95 }
96
97 impl Default for AppearanceConfig {
98 fn default() -> Self {
99 Self {
100 font_family: "monospace".to_string(),
101 font_size: 14,
102 button_panel_visible: false,
103 }
104 }
105 }
106
107 impl Config {
108 /// Load configuration
109 /// Tries Lua config first (~/.config/gar/init.lua), then falls back to TOML
110 pub fn load() -> Result<Self> {
111 // Try Lua config first (shared with other gar components)
112 if let Ok(config) = Self::load_from_lua() {
113 return Ok(config);
114 }
115
116 // Fall back to TOML config
117 let path = toml_config_path();
118 if path.exists() {
119 let content = std::fs::read_to_string(&path)?;
120 let config: Config = toml::from_str(&content)?;
121 Ok(config)
122 } else {
123 Ok(Config::default())
124 }
125 }
126
127 /// Load configuration from Lua (~/.config/gar/init.lua)
128 fn load_from_lua() -> Result<Self> {
129 let lua_path = lua_config_path();
130 if !lua_path.exists() {
131 return Err(anyhow::anyhow!("Lua config not found"));
132 }
133
134 let lua = Lua::new();
135 let content = std::fs::read_to_string(&lua_path)?;
136
137 // Create gar table if it doesn't exist
138 lua.scope(|_scope| {
139 let globals = lua.globals();
140
141 // Initialize gar table
142 let gar: mlua::Table = lua.create_table()?;
143 globals.set("gar", gar)?;
144
145 // Execute the config file
146 lua.load(&content).exec()?;
147
148 // Get gar.calculator table
149 let gar: mlua::Table = globals.get("gar")?;
150 let calc: Option<mlua::Table> = gar.get("calculator").ok();
151
152 if let Some(calc) = calc {
153 let config = Self::from_lua_table(&calc)?;
154 Ok(config)
155 } else {
156 Err(mlua::Error::RuntimeError(
157 "gar.calculator not found".to_string(),
158 ))
159 }
160 })
161 .map_err(|e| anyhow::anyhow!("Lua error: {}", e))
162 }
163
164 /// Parse Config from Lua table
165 fn from_lua_table(table: &mlua::Table) -> mlua::Result<Self> {
166 let mut config = Config::default();
167
168 // General settings
169 if let Ok(general) = table.get::<mlua::Table>("general") {
170 if let Ok(mode) = general.get::<String>("default_mode") {
171 config.general.default_mode = mode;
172 }
173 if let Ok(precision) = general.get::<u32>("precision") {
174 config.general.precision = precision;
175 }
176 if let Ok(angle) = general.get::<String>("angle_mode") {
177 config.general.angle_mode = angle;
178 }
179 if let Ok(exact) = general.get::<bool>("exact_mode") {
180 config.general.exact_mode = exact;
181 }
182 }
183
184 // Popup settings
185 if let Ok(popup) = table.get::<mlua::Table>("popup") {
186 if let Ok(w) = popup.get::<u32>("width") {
187 config.popup.width = w;
188 }
189 if let Ok(h) = popup.get::<u32>("height") {
190 config.popup.height = h;
191 }
192 if let Ok(pos) = popup.get::<String>("position") {
193 config.popup.position = pos;
194 }
195 }
196
197 // Graph settings
198 if let Ok(graph) = table.get::<mlua::Table>("graph") {
199 if let Ok(x_range) = graph.get::<mlua::Table>("x_range") {
200 if let (Ok(min), Ok(max)) = (x_range.get::<f64>(1), x_range.get::<f64>(2)) {
201 config.graph.default_x_range = (min, max);
202 }
203 }
204 if let Ok(y_range) = graph.get::<mlua::Table>("y_range") {
205 if let (Ok(min), Ok(max)) = (y_range.get::<f64>(1), y_range.get::<f64>(2)) {
206 config.graph.default_y_range = (min, max);
207 }
208 }
209 if let Ok(grid) = graph.get::<bool>("grid") {
210 config.graph.grid_enabled = grid;
211 }
212 }
213
214 // Appearance settings
215 if let Ok(appearance) = table.get::<mlua::Table>("appearance") {
216 if let Ok(font) = appearance.get::<String>("font_family") {
217 config.appearance.font_family = font;
218 }
219 if let Ok(size) = appearance.get::<u32>("font_size") {
220 config.appearance.font_size = size;
221 }
222 if let Ok(panel) = appearance.get::<bool>("button_panel") {
223 config.appearance.button_panel_visible = panel;
224 }
225 }
226
227 Ok(config)
228 }
229
230 /// Save configuration to TOML file
231 #[allow(dead_code)]
232 pub fn save(&self) -> Result<()> {
233 let path = toml_config_path();
234 if let Some(parent) = path.parent() {
235 std::fs::create_dir_all(parent)?;
236 }
237 let content = toml::to_string_pretty(self)?;
238 std::fs::write(path, content)?;
239 Ok(())
240 }
241 }
242
243 fn lua_config_path() -> PathBuf {
244 dirs::config_dir()
245 .unwrap_or_else(|| PathBuf::from("."))
246 .join("gar")
247 .join("init.lua")
248 }
249
250 fn toml_config_path() -> PathBuf {
251 dirs::config_dir()
252 .unwrap_or_else(|| PathBuf::from("."))
253 .join("garcalc")
254 .join("config.toml")
255 }
256