use super::theme::Theme;
use serde::{Deserialize, Serialize};
use std::{
env, fs,
path::{Path, PathBuf},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct Look {
pub theme: Theme,
pub icons: bool,
pub keep_going: bool,
pub snapshots: bool,
}
impl Default for Look {
fn default() -> Self {
Self {
theme: Theme::Solid,
icons: true,
keep_going: false,
snapshots: true,
}
}
}
pub fn path() -> Option<PathBuf> {
let base = env::var_os("XDG_CONFIG_HOME")
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.or_else(|| env::var_os("HOME").map(|home| PathBuf::from(home).join(".config")))?;
Some(base.join("helixforge/settings.toml"))
}
pub fn load(path: &Path) -> Look {
let Some(table) = fs::read_to_string(path)
.ok()
.and_then(|text| text.parse::<toml::Table>().ok())
else {
return Look::default();
};
let mut look = Look::default();
let get = |name: &str| table.get(name).cloned();
if let Some(theme) = get("theme").and_then(|value| value.try_into().ok()) {
look.theme = theme;
}
for (name, field) in [
("icons", &mut look.icons),
("keep_going", &mut look.keep_going),
("snapshots", &mut look.snapshots),
] {
if let Some(value) = get(name).and_then(|value| value.as_bool()) {
*field = value;
}
}
look
}
#[derive(Debug, Default, Deserialize, Serialize)]
#[serde(default)]
struct Favorites {
entries: Vec<String>,
}
pub fn favorites_path(settings: &Path) -> PathBuf {
settings.with_file_name("favourites.toml")
}
pub fn load_favorites(path: &Path) -> Vec<String> {
fs::read_to_string(path)
.ok()
.and_then(|text| toml::from_str::<Favorites>(&text).ok())
.map(|favorites| favorites.entries)
.unwrap_or_default()
}
pub fn save_favorites(path: &Path, entries: &[String]) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let text = toml::to_string(&Favorites {
entries: entries.to_vec(),
})
.map_err(std::io::Error::other)?;
let staging = path.with_extension("toml.new");
fs::write(
&staging,
format!("# Entries pinned in HelixForge with *.\n{text}"),
)?;
fs::rename(&staging, path)
}
pub fn save(path: &Path, look: &Look) -> std::io::Result<()> {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
let text = toml::to_string(look).map_err(std::io::Error::other)?;
let staging = path.with_extension("toml.new");
fs::write(
&staging,
format!("# HelixForge interface settings; Settings in the menu changes them.\n{text}"),
)?;
fs::rename(&staging, path)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_look_round_trips_and_falls_back_to_the_default() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("helixforge/settings.toml");
assert_eq!(load(&path), Look::default());
assert_eq!(Look::default().theme, Theme::Solid);
let look = Look {
theme: Theme::Transparent,
icons: false,
keep_going: true,
snapshots: false,
};
save(&path, &look).unwrap();
assert_eq!(load(&path), look);
assert!(
fs::read_to_string(&path)
.unwrap()
.contains("theme = \"transparent\"")
);
fs::write(&path, "theme = \"default\"\n").unwrap();
assert_eq!(load(&path).theme, Theme::Transparent);
fs::write(&path, "theme = 3\n").unwrap();
assert_eq!(load(&path), Look::default());
fs::write(
&path,
"theme = \"aurora\"\nicons = false\nanimations = true\n[future]\nx = 1\n",
)
.unwrap();
assert_eq!(
load(&path),
Look {
icons: false,
..Look::default()
}
);
}
}