Finished project

This commit is contained in:
ManOfGoldForever 2026-04-01 00:46:54 -04:00
parent 6849f1afae
commit 31179d64e5
12 changed files with 2207 additions and 3 deletions

141
src/theme.rs Normal file
View file

@ -0,0 +1,141 @@
use anyhow::{Context, Result};
use dirs::home_dir;
use std::fs;
use std::path::{Path, PathBuf};
pub fn themes_root() -> Result<PathBuf> {
if let Ok(root) = std::env::var("DM_THEMES_ROOT") {
return Ok(PathBuf::from(root));
}
let home = home_dir().context("failed to find home directory")?;
Ok(home.join("dotfiles").join("themes"))
}
pub fn discover_themes() -> Result<Vec<String>> {
let root = themes_root()?;
let entries = fs::read_dir(&root)
.with_context(|| format!("failed to read themes directory: {}", root.display()))?;
let mut themes = Vec::new();
for entry in entries {
let entry = entry.with_context(|| {
format!(
"failed to read an entry from themes directory: {}",
root.display()
)
})?;
let path = entry.path();
if path.is_dir() {
let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
continue;
};
themes.push(name.to_string());
}
}
themes.sort();
Ok(themes)
}
/// Recursively enumerate all files under `theme_dir`.
/// Returns `(theme_relative_path, absolute_source_path)` pairs sorted by path.
pub fn enumerate_theme_files(theme_dir: &Path) -> Result<Vec<(PathBuf, PathBuf)>> {
let mut files = Vec::new();
enumerate_recursive(theme_dir, theme_dir, &mut files)?;
files.sort_by(|a, b| a.0.cmp(&b.0));
Ok(files)
}
fn enumerate_recursive(
root: &Path,
current: &Path,
files: &mut Vec<(PathBuf, PathBuf)>,
) -> Result<()> {
let entries = fs::read_dir(current)
.with_context(|| format!("failed to read directory: {}", current.display()))?;
for entry in entries {
let entry =
entry.with_context(|| format!("failed to read entry from: {}", current.display()))?;
let path = entry.path();
// Skip symlinks inside a theme source tree
if path
.symlink_metadata()
.map(|m| m.file_type().is_symlink())
.unwrap_or(false)
{
continue;
}
if path.is_dir() {
enumerate_recursive(root, &path, files)?;
} else if path.is_file() {
let relative = path
.strip_prefix(root)
.with_context(|| format!("failed to strip prefix from: {}", path.display()))?;
files.push((relative.to_path_buf(), path));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::ENV_MUTEX;
use std::fs;
use tempfile::TempDir;
fn make_theme(root: &Path, theme: &str, files: &[&str]) {
for rel in files {
let p = root.join(theme).join(rel);
fs::create_dir_all(p.parent().unwrap()).unwrap();
fs::write(&p, "").unwrap();
}
}
#[test]
fn discover_themes_returns_directory_names() {
let _guard = ENV_MUTEX.lock().unwrap();
let tmp = TempDir::new().unwrap();
fs::create_dir(tmp.path().join("dracula")).unwrap();
fs::create_dir(tmp.path().join("catppuccin")).unwrap();
fs::write(tmp.path().join("not-a-dir.txt"), "").unwrap();
unsafe { std::env::set_var("DM_THEMES_ROOT", tmp.path()) };
let themes = discover_themes().unwrap();
unsafe { std::env::remove_var("DM_THEMES_ROOT") };
assert_eq!(themes, vec!["catppuccin", "dracula"]);
}
#[test]
fn enumerate_theme_files_returns_relative_paths() {
let tmp = TempDir::new().unwrap();
make_theme(tmp.path(), "dracula", &[".config/nvim/init.lua", ".zshrc"]);
let theme_dir = tmp.path().join("dracula");
let files = enumerate_theme_files(&theme_dir).unwrap();
let relatives: Vec<PathBuf> = files.into_iter().map(|(r, _)| r).collect();
assert!(relatives.contains(&PathBuf::from(".config/nvim/init.lua")));
assert!(relatives.contains(&PathBuf::from(".zshrc")));
}
#[test]
fn enumerate_theme_files_is_sorted() {
let tmp = TempDir::new().unwrap();
make_theme(tmp.path(), "t", &["b.txt", "a.txt", ".config/z.txt"]);
let theme_dir = tmp.path().join("t");
let files = enumerate_theme_files(&theme_dir).unwrap();
let relatives: Vec<PathBuf> = files.into_iter().map(|(r, _)| r).collect();
let mut sorted = relatives.clone();
sorted.sort();
assert_eq!(relatives, sorted);
}
}