Finished project
This commit is contained in:
parent
6849f1afae
commit
31179d64e5
12 changed files with 2207 additions and 3 deletions
141
src/theme.rs
Normal file
141
src/theme.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue