use std::path::{Path, PathBuf};
use crate::{PluginFramework, PluginRequest, PluginRequestKind};
pub trait ZshPluginFramework: Sync {
fn framework(&self) -> PluginFramework;
fn root_keys(&self) -> &'static [&'static str];
fn resolve_plugin_entrypoint(&self, root: &Path, name: &str) -> Option<PathBuf>;
fn resolve_theme_entrypoint(&self, root: &Path, name: &str) -> Option<PathBuf>;
fn resolve_source_suffix(
&self,
root: &Path,
source_path: &Path,
candidate: &str,
) -> Option<PathBuf>;
fn dependent_plugin_requests(&self, _request: &PluginRequest) -> Vec<PluginRequest> {
Vec::new()
}
fn resolve_entrypoint(
&self,
root: &Path,
kind: PluginRequestKind,
name: &str,
) -> Option<PathBuf> {
match kind {
PluginRequestKind::Plugin => self.resolve_plugin_entrypoint(root, name),
PluginRequestKind::Theme => self.resolve_theme_entrypoint(root, name),
PluginRequestKind::Entrypoint => Some(PathBuf::from(name)),
}
}
}
pub fn zsh_plugin_framework_from_name(name: &str) -> PluginFramework {
match name {
"oh-my-zsh" => PluginFramework::OhMyZsh,
"prezto" => PluginFramework::Prezto,
"zdot" => PluginFramework::Zdot,
"zinit" | "zi" => PluginFramework::Zinit,
other => PluginFramework::Other(other.to_owned()),
}
}
pub fn resolve_zsh_plugin_entrypoint(root: &Path, request: &PluginRequest) -> Option<PathBuf> {
if let Some(layout) = crate::layout_for_plugin_framework(&request.framework) {
return layout.resolve_entrypoint(root, request.kind, &request.name);
}
match (&request.framework, request.kind) {
(PluginFramework::Other(_), PluginRequestKind::Plugin) => {
let standalone = root.join(format!("{}.plugin.zsh", request.name));
if standalone.is_file() {
Some(standalone)
} else {
Some(
root.join("plugins")
.join(&request.name)
.join(format!("{}.plugin.zsh", request.name)),
)
}
}
(PluginFramework::Other(_), PluginRequestKind::Theme) => Some(
root.join("themes")
.join(format!("{}.zsh-theme", request.name)),
),
_ => None,
}
}
pub fn zsh_plugin_root_keys(framework: &PluginFramework) -> Option<&'static [&'static str]> {
crate::layout_for_plugin_framework(framework).map(ZshPluginFramework::root_keys)
}
pub fn resolve_zsh_plugin_source_paths<'a, I>(
roots: I,
source_path: &Path,
candidate: &str,
) -> Vec<PathBuf>
where
I: IntoIterator<Item = (&'a str, &'a Path)>,
{
let roots = roots.into_iter().collect::<Vec<_>>();
let mut paths = Vec::new();
for layout in crate::zsh_plugin_frameworks() {
for root_key in layout.root_keys() {
for (configured_key, root) in &roots {
if configured_key != root_key {
continue;
}
if let Some(suffix) = layout.resolve_source_suffix(root, source_path, candidate) {
paths.push(root.join(suffix));
}
}
}
}
paths
}
#[cfg(test)]
mod tests {
use super::*;
use crate::PluginRequest;
use tempfile::tempdir;
#[test]
fn custom_plugin_requests_use_default_nested_layout() {
let root = Path::new("/workspace/custom");
let request = PluginRequest {
framework: PluginFramework::Other("custom".to_owned()),
kind: PluginRequestKind::Plugin,
name: "prompt-tools".to_owned(),
span: shuck_ast::Span::new(),
explicit: true,
root_hint: None,
};
assert_eq!(
resolve_zsh_plugin_entrypoint(root, &request),
Some(PathBuf::from(
"/workspace/custom/plugins/prompt-tools/prompt-tools.plugin.zsh"
))
);
}
#[test]
fn custom_plugin_requests_prefer_standalone_entrypoint_when_present() {
let temp = tempdir().unwrap();
std::fs::write(
temp.path().join("zsh-autosuggestions.plugin.zsh"),
"source zsh-autosuggestions.zsh\n",
)
.unwrap();
let request = PluginRequest {
framework: PluginFramework::Other("zsh-autosuggestions".to_owned()),
kind: PluginRequestKind::Plugin,
name: "zsh-autosuggestions".to_owned(),
span: shuck_ast::Span::new(),
explicit: false,
root_hint: None,
};
assert_eq!(
resolve_zsh_plugin_entrypoint(temp.path(), &request),
Some(temp.path().join("zsh-autosuggestions.plugin.zsh"))
);
}
#[test]
fn custom_theme_requests_use_default_theme_layout() {
let root = Path::new("/workspace/custom");
let request = PluginRequest {
framework: PluginFramework::Other("custom".to_owned()),
kind: PluginRequestKind::Theme,
name: "minimal".to_owned(),
span: shuck_ast::Span::new(),
explicit: true,
root_hint: None,
};
assert_eq!(
resolve_zsh_plugin_entrypoint(root, &request),
Some(PathBuf::from("/workspace/custom/themes/minimal.zsh-theme"))
);
}
#[test]
fn source_paths_resolve_through_framework_roots() {
let root = Path::new("/workspace/oh-my-zsh");
assert_eq!(
resolve_zsh_plugin_source_paths(
[("oh-my-zsh", root)].into_iter(),
Path::new("/workspace/app/.zshrc"),
"$ZSH/oh-my-zsh.sh",
),
vec![PathBuf::from("/workspace/oh-my-zsh/oh-my-zsh.sh")]
);
}
}