use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use toml_edit::de::from_str;
pub const EMBEDDED_TEMPLATES: &[(&str, &str)] = &[
("general", include_str!("./general.plz.toml")),
("vite", include_str!("./vite.plz.toml")),
("uv", include_str!("./uv.plz.toml")),
("rust", include_str!("./rust.plz.toml")),
];
pub const EMBEDDED_ENVIRONMENTS: &str = include_str!("../environments.toml");
pub const EMBEDDED_SNIPPETS: &[(&str, &str)] = &[
("general", include_str!("../snippets/general.snippets.toml")),
("rust", include_str!("../snippets/rust.snippets.toml")),
("pnpm", include_str!("../snippets/pnpm.snippets.toml")),
("uv", include_str!("../snippets/uv.snippets.toml")),
];
#[derive(Debug, Clone, Deserialize)]
pub struct Environment {
pub patterns: Vec<String>,
#[serde(default)]
pub alternative_to: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct TemplateMeta {
pub name: String,
pub description: String,
pub env: Option<String>,
pub content: String,
pub is_user: bool,
}
#[derive(Debug, Clone, Deserialize)]
struct TemplateHeader {
description: String,
#[serde(default)]
env: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
struct TemplateFile {
template: TemplateHeader,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Snippet {
pub name: String,
pub description: String,
pub content: String,
}
#[derive(Debug, Clone, Deserialize)]
struct SnippetFile {
snippets: Vec<Snippet>,
}
pub fn load_environments() -> HashMap<String, Environment> {
from_str(EMBEDDED_ENVIRONMENTS).unwrap_or_default()
}
pub fn detect_environments(cwd: &Path, environments: &HashMap<String, Environment>) -> Vec<String> {
let mut detected = Vec::new();
let mut env_names: Vec<&String> = environments.keys().collect();
env_names.sort();
for name in env_names {
let env = &environments[name];
if env.patterns.iter().any(|p| cwd.join(p).exists()) {
detected.push(name.clone());
}
}
detected
}
pub fn load_templates(config_dir: Option<&Path>) -> Vec<TemplateMeta> {
let mut templates = Vec::new();
let mut user_template: Option<TemplateMeta> = None;
if let Some(dir) = config_dir {
let user_path = dir.join("templates").join("user.plz.toml");
if let Ok(content) = std::fs::read_to_string(&user_path)
&& let Some(meta) = parse_template_meta("user", &content, true)
{
user_template = Some(meta);
}
}
for (name, content) in EMBEDDED_TEMPLATES {
if let Some(ref ut) = user_template
&& ut.env.as_deref() == Some(*name)
{
templates.push(ut.clone());
}
if let Some(meta) = parse_template_meta(name, content, false) {
templates.push(meta);
}
}
if let Some(vite) = templates.iter().find(|t| t.name == "vite") {
let npm_variant = TemplateMeta {
name: "vite-npm".to_string(),
description: vite.description.clone(),
env: Some("npm".to_string()),
content: vite.content.replace("env = \"pnpm\"", "env = \"npm\""),
is_user: false,
};
templates.push(npm_variant);
}
if let Some(ref ut) = user_template
&& !EMBEDDED_TEMPLATES
.iter()
.any(|(name, _)| ut.env.as_deref() == Some(*name))
{
templates.push(ut.clone());
}
templates
}
fn parse_template_meta(name: &str, content: &str, is_user: bool) -> Option<TemplateMeta> {
let header: TemplateFile = from_str(content).ok()?;
Some(TemplateMeta {
name: name.to_string(),
description: header.template.description,
env: header.template.env,
content: content.to_string(),
is_user,
})
}
pub fn strip_template_section(content: &str) -> String {
let mut result = String::new();
let mut in_template_section = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == "[template]" {
in_template_section = true;
continue;
}
if in_template_section {
if trimmed.starts_with('[') {
in_template_section = false;
} else if trimmed.is_empty() {
in_template_section = false;
continue;
} else {
continue;
}
}
result.push_str(line);
result.push('\n');
}
let trimmed = result.trim_start();
trimmed.to_string()
}
pub fn load_snippets() -> Vec<(String, Vec<Snippet>)> {
let mut all_snippets: Vec<(String, Vec<Snippet>)> = Vec::new();
for (name, content) in EMBEDDED_SNIPPETS {
if let Ok(file) = from_str::<SnippetFile>(content) {
all_snippets.push((name.to_string(), file.snippets));
}
}
if let Some((_, pnpm_snippets)) = all_snippets.iter().find(|(n, _)| n == "pnpm") {
let npm_snippets: Vec<Snippet> = pnpm_snippets
.iter()
.map(|s| Snippet {
name: s.name.clone(),
description: s.description.clone(),
content: s.content.replace("env = \"pnpm\"", "env = \"npm\""),
})
.collect();
all_snippets.push(("npm".to_string(), npm_snippets));
}
all_snippets
}