use std::io;
use std::path::Path;
use crate::config;
use crate::edge::Edge;
use crate::hooks::Hooks;
use crate::layout::CloneDir;
use super::Key;
pub(super) struct Resolved {
pub(super) value: String,
pub(super) layer: String,
}
pub(super) fn resolve(edge: &Edge, clone: &CloneDir, key: &Key) -> io::Result<Resolved> {
let landing = clone.landing();
match key {
Key::TaskRemote => task_remote(edge, &landing, &clone.binding()),
Key::TaskBranch => scalar(edge, &landing, "tasks_branch", crate::DEFAULT_TASKS_BRANCH, None),
Key::LogLevel => scalar(edge, &landing, "log_level", "info", edge.log_level.as_deref()),
Key::Hook(k) => {
let hooks = Hooks::effective(&landing, &edge.xdg.user_config())?;
let names = hooks
.entries()
.find(|(key, _)| *key == k)
.map(|(_, names)| names.join(", "))
.filter(|joined| !joined.is_empty());
Ok(Resolved {
value: names.unwrap_or_else(|| "(none)".into()),
layer: hook_layer(edge, clone, k)?,
})
}
}
}
fn task_remote(edge: &Edge, landing: &Path, binding: &Path) -> io::Result<Resolved> {
let (remote, declared) = config::remote_ladder(None, landing, binding, &edge.xdg.user_config())?;
if declared {
return Ok(Resolved { value: "(none)".into(), layer: "landing".into() });
}
if let Some(url) = remote {
let layer = if config::binding_remote(binding).is_some() { "binding" } else { "xdg (global)" };
return Ok(Resolved { value: url, layer: layer.into() });
}
Ok(match origin(&edge.invocation_path) {
Some(url) => Resolved { value: url, layer: "origin".into() },
None => Resolved { value: "(none)".into(), layer: "stealth".into() },
})
}
fn origin(project: &Path) -> Option<String> {
let url = crate::git::run(project, &["remote", "get-url", "origin"], None).ok()?;
Some(url.trim().to_string())
}
fn scalar(edge: &Edge, landing: &Path, field: &str, default: &str, cli: Option<&str>) -> io::Result<Resolved> {
if let Some(v) = cli {
return Ok(Resolved { value: v.to_string(), layer: "cli".into() });
}
let layers = [
(config::read_layer(&edge.xdg.user_config())?, "xdg"),
(config::read_layer(&landing.join("config").join("balls.toml"))?, "landing"),
];
for (table, layer) in layers {
if let Some(v) = table.as_ref().and_then(|t| t.get(field)).and_then(|v| v.as_str()) {
return Ok(Resolved { value: v.to_string(), layer: layer.into() });
}
}
Ok(Resolved { value: default.to_string(), layer: "default".into() })
}
pub(super) fn hook_layer(edge: &Edge, clone: &CloneDir, key: &str) -> io::Result<String> {
let landing = hooks_mentions(&clone.landing().join("config").join("plugins.toml"), key)?;
let xdg = hooks_mentions(&edge.xdg.user_config().with_file_name("plugins.toml"), key)?;
Ok(match (landing, xdg) {
(true, true) => "landing+xdg".into(),
(true, false) => "landing".into(),
(false, true) => "xdg".into(),
(false, false) => "default".into(),
})
}
fn hooks_mentions(path: &Path, key: &str) -> io::Result<bool> {
let Some(root) = config::read_layer(path)? else {
return Ok(false);
};
let Some(toml::Value::Table(hooks)) = root.get("hooks") else {
return Ok(false);
};
Ok(hooks.contains_key(key)
|| ["_prepend", "_append", "_ban"].iter().any(|s| hooks.contains_key(&format!("{key}{s}"))))
}