Added base theme for defaults
This commit is contained in:
parent
31179d64e5
commit
eb692b56e4
2 changed files with 101 additions and 3 deletions
100
src/apply.rs
100
src/apply.rs
|
|
@ -103,13 +103,33 @@ pub fn plan_apply(
|
|||
force: bool,
|
||||
current_state: Option<&State>,
|
||||
) -> Result<ApplyPlan> {
|
||||
let theme_dir = themes_root()?.join(theme_name);
|
||||
if theme_name == "base" {
|
||||
anyhow::bail!("\"base\" is a reserved layer name and cannot be applied as a theme");
|
||||
}
|
||||
|
||||
let root = themes_root()?;
|
||||
let theme_dir = root.join(theme_name);
|
||||
if !theme_dir.is_dir() {
|
||||
anyhow::bail!("theme not found: {}", theme_name);
|
||||
}
|
||||
|
||||
let home = effective_home()?;
|
||||
let files = enumerate_theme_files(&theme_dir)?;
|
||||
|
||||
// Build a merged file map: base files first, theme files overlay (and win on conflict).
|
||||
// Using BTreeMap keeps the iteration order deterministic.
|
||||
let mut file_map: std::collections::BTreeMap<PathBuf, PathBuf> =
|
||||
std::collections::BTreeMap::new();
|
||||
|
||||
let base_dir = root.join("base");
|
||||
if base_dir.is_dir() {
|
||||
for (rel, src) in enumerate_theme_files(&base_dir)? {
|
||||
file_map.insert(rel, src);
|
||||
}
|
||||
}
|
||||
|
||||
for (rel, src) in enumerate_theme_files(&theme_dir)? {
|
||||
file_map.insert(rel, src);
|
||||
}
|
||||
|
||||
let managed_targets: HashSet<PathBuf> = current_state
|
||||
.map(|s| s.entries.iter().map(|e| PathBuf::from(&e.target)).collect())
|
||||
|
|
@ -117,7 +137,7 @@ pub fn plan_apply(
|
|||
|
||||
let mut actions = Vec::new();
|
||||
|
||||
for (relative, source) in files {
|
||||
for (relative, source) in file_map {
|
||||
let target = home.join(&relative);
|
||||
|
||||
match target.symlink_metadata() {
|
||||
|
|
@ -724,4 +744,78 @@ mod tests {
|
|||
let link = fs::read_link(env.home.path().join(".zshrc")).unwrap();
|
||||
assert!(link.to_string_lossy().contains("catppuccin"));
|
||||
}
|
||||
|
||||
// ---- base layer tests -----------------------------------------------
|
||||
|
||||
fn make_base_file(env: &TestEnv, rel: &str, content: &str) -> PathBuf {
|
||||
make_theme_file(env, "base", rel, content)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_files_applied_alongside_theme() {
|
||||
let env = TestEnv::new();
|
||||
make_base_file(&env, ".config/hypr/keybinds.conf", "# base keybinds");
|
||||
make_theme_file(&env, "dracula", ".zshrc", "# dracula");
|
||||
|
||||
let plan = plan_apply("dracula", false, None).unwrap();
|
||||
assert_eq!(plan.actions.len(), 2);
|
||||
|
||||
let (_, summary) = execute_apply(&plan, false).unwrap();
|
||||
assert_eq!(summary.created, 2);
|
||||
|
||||
let keybinds = fs::read_link(env.home.path().join(".config/hypr/keybinds.conf")).unwrap();
|
||||
assert!(keybinds.to_string_lossy().contains("base"));
|
||||
|
||||
let zshrc = fs::read_link(env.home.path().join(".zshrc")).unwrap();
|
||||
assert!(zshrc.to_string_lossy().contains("dracula"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn theme_overrides_base_file() {
|
||||
let env = TestEnv::new();
|
||||
make_base_file(&env, ".zshrc", "# base zshrc");
|
||||
make_theme_file(&env, "dracula", ".zshrc", "# dracula zshrc");
|
||||
|
||||
let plan = plan_apply("dracula", false, None).unwrap();
|
||||
// Only one action — theme wins over base for same path.
|
||||
assert_eq!(plan.actions.len(), 1);
|
||||
|
||||
execute_apply(&plan, false).unwrap();
|
||||
let link = fs::read_link(env.home.path().join(".zshrc")).unwrap();
|
||||
assert!(link.to_string_lossy().contains("dracula"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_file_stays_when_theme_has_no_override() {
|
||||
let env = TestEnv::new();
|
||||
make_base_file(&env, ".config/hypr/keybinds.conf", "# base keybinds");
|
||||
make_theme_file(&env, "dracula", ".zshrc", "# dracula");
|
||||
make_theme_file(&env, "catppuccin", ".zshrc", "# catppuccin");
|
||||
|
||||
// Apply dracula
|
||||
let plan = plan_apply("dracula", false, None).unwrap();
|
||||
let (dracula_state, _) = execute_apply(&plan, false).unwrap();
|
||||
|
||||
// Switch to catppuccin — neither theme has keybinds.conf, so base symlink stays
|
||||
let plan2 = plan_apply("catppuccin", false, Some(&dracula_state)).unwrap();
|
||||
|
||||
let keybinds_action = plan2
|
||||
.actions
|
||||
.iter()
|
||||
.find(|a| match a {
|
||||
PlannedAction::Skip { target, .. } => {
|
||||
target.to_string_lossy().contains("keybinds")
|
||||
}
|
||||
_ => false,
|
||||
});
|
||||
assert!(keybinds_action.is_some(), "keybinds.conf should be skipped (already correct)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn base_is_rejected_as_theme_name() {
|
||||
let env = TestEnv::new();
|
||||
make_base_file(&env, ".zshrc", "# base");
|
||||
let result = plan_apply("base", false, None);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue