use regex::Regex;
use std::collections::HashMap;
use thiserror::Error;
use crate::config::TemplateConfig;
#[derive(Error, Debug, Clone, PartialEq)]
pub enum TemplateError {
#[error("Template validation failed: {0}")]
ValidationError(String),
#[error("Missing required placeholder: {0}")]
MissingPlaceholder(String),
#[error("Invalid template configuration: {0}")]
ConfigError(String),
#[error("Template processing error: {0}")]
ProcessingError(String),
}
pub type TemplateResult<T> = Result<T, TemplateError>;
#[derive(Debug, Clone, PartialEq)]
pub struct ValidationResult {
pub valid: bool,
pub message: Option<String>,
pub missing_placeholders: Vec<String>,
pub found_placeholders: Vec<String>,
}
impl ValidationResult {
pub fn success(found_placeholders: Vec<String>) -> Self {
Self {
valid: true,
message: None,
missing_placeholders: Vec::new(),
found_placeholders,
}
}
pub fn failure(message: String, missing: Vec<String>, found: Vec<String>) -> Self {
Self {
valid: false,
message: Some(message),
missing_placeholders: missing,
found_placeholders: found,
}
}
}
pub struct TemplateService;
impl TemplateService {
pub fn should_use_template(config: &TemplateConfig) -> bool {
config.enabled && !config.file.trim().is_empty()
}
pub fn validate_template(content: &str, config: &TemplateConfig) -> ValidationResult {
let found_placeholders = Self::extract_placeholders(content);
let mut missing_placeholders = Vec::new();
if config.require_title && !found_placeholders.contains(&"title".to_string()) {
missing_placeholders.push("title".to_string());
}
if config.require_link && !found_placeholders.contains(&"link".to_string()) {
missing_placeholders.push("link".to_string());
}
if missing_placeholders.is_empty() {
ValidationResult::success(found_placeholders)
} else {
let message = format!(
"Template missing required placeholder(s): {{{{{}}}}}",
missing_placeholders.join("}}, {{")
);
ValidationResult::failure(message, missing_placeholders, found_placeholders)
}
}
fn extract_placeholders(content: &str) -> Vec<String> {
let placeholder_regex = Regex::new(r"\{\{(\w+)\}\}").unwrap();
let mut placeholders = Vec::new();
for capture in placeholder_regex.captures_iter(content) {
if let Some(placeholder) = capture.get(1) {
let name = placeholder.as_str().to_string();
if !placeholders.contains(&name) {
placeholders.push(name);
}
}
}
placeholders
}
pub fn generate_content(
template_content: Option<&str>,
title: &str,
backlink_content: &str,
) -> String {
match template_content {
Some(template) => {
Self::substitute_placeholders(template, title, backlink_content)
}
None => {
Self::generate_builtin_content(title, backlink_content)
}
}
}
fn substitute_placeholders(template: &str, title: &str, backlink: &str) -> String {
template
.replace("{{title}}", title)
.replace("{{link}}", backlink)
}
fn generate_builtin_content(title: &str, backlink: &str) -> String {
let mut content = String::new();
if !title.trim().is_empty() {
content.push_str(&format!("# {}", title.trim_start()));
}
if !backlink.trim().is_empty() {
if !content.is_empty() {
content.push_str("\n\n");
}
content.push_str(backlink);
}
content
}
pub fn resolve_template_path(config: &TemplateConfig) -> TemplateResult<String> {
if !config.file.trim().is_empty() {
Ok(config.file.trim().to_string())
} else if !config.directory.trim().is_empty() {
if config.default_template.trim().is_empty() {
return Err(TemplateError::ConfigError(
"Template directory specified but no default template name provided"
.to_string(),
));
}
let directory = config.directory.trim();
let template_name = config.default_template.trim();
Ok(format!("{}/{}", directory, template_name))
} else {
Err(TemplateError::ConfigError(
"No template file or directory specified".to_string(),
))
}
}
#[allow(dead_code)]
pub fn create_template_context(title: &str, backlink: &str) -> HashMap<String, String> {
let mut context = HashMap::new();
context.insert("title".to_string(), title.to_string());
context.insert("link".to_string(), backlink.to_string());
context
}
}
#[allow(dead_code)]
pub struct TemplateManager {
templates: HashMap<String, String>,
}
#[allow(dead_code)]
impl TemplateManager {
pub fn new() -> Self {
Self {
templates: HashMap::new(),
}
}
pub fn register_template(&mut self, name: String, content: String) {
self.templates.insert(name, content);
}
pub fn get_template(&self, name: &str) -> Option<&String> {
self.templates.get(name)
}
pub fn list_templates(&self) -> Vec<&String> {
self.templates.keys().collect()
}
}