ralph/template/loader/
load.rs1use std::path::Path;
21
22use anyhow::{Result, bail};
23
24use crate::contracts::Task;
25use crate::template::builtin::get_builtin_template;
26use crate::template::variables::{
27 TemplateContext, detect_context_with_warnings, substitute_variables_in_task,
28 validate_task_template,
29};
30
31use super::types::{LoadedTemplate, TemplateError, TemplateSource};
32
33pub fn load_template(name: &str, project_root: &Path) -> Result<(Task, TemplateSource)> {
37 let custom_path = project_root
38 .join(".ralph/templates")
39 .join(format!("{}.json", name));
40 if custom_path.exists() {
41 let content = std::fs::read_to_string(&custom_path)
42 .map_err(|e| TemplateError::ReadError(e.to_string()))?;
43 let task: Task = serde_json::from_str(&content)
44 .map_err(|e| TemplateError::InvalidJson(e.to_string()))?;
45
46 let validation = validate_task_template(&task);
47 if validation.has_unknown_variables() {
48 let unknowns = validation.unknown_variable_names();
49 log::warn!(
50 "Template '{}' contains unknown variables: {}",
51 name,
52 unknowns.join(", ")
53 );
54 }
55
56 return Ok((task, TemplateSource::Custom(custom_path)));
57 }
58
59 if let Some(template_json) = get_builtin_template(name) {
60 let task: Task = serde_json::from_str(template_json)
61 .map_err(|e| TemplateError::InvalidJson(e.to_string()))?;
62 return Ok((task, TemplateSource::Builtin(name.to_string())));
63 }
64
65 Err(TemplateError::NotFound(name.to_string()).into())
66}
67
68pub fn load_template_with_context(
76 name: &str,
77 project_root: &Path,
78 target: Option<&str>,
79 strict: bool,
80) -> Result<LoadedTemplate> {
81 let (mut task, source) = load_template(name, project_root)?;
82 let validation = validate_task_template(&task);
83
84 if strict && validation.has_unknown_variables() {
85 let unknowns = validation.unknown_variable_names();
86 bail!(TemplateError::ValidationError(format!(
87 "Template '{}' contains unknown variables: {}",
88 name,
89 unknowns.join(", ")
90 )));
91 }
92
93 let (context, mut warnings) =
94 detect_context_with_warnings(target, project_root, validation.uses_branch);
95 warnings.extend(validation.warnings);
96 substitute_variables_in_task(&mut task, &context);
97
98 Ok(LoadedTemplate {
99 task,
100 source,
101 warnings,
102 })
103}
104
105pub fn load_template_with_context_legacy(
110 name: &str,
111 project_root: &Path,
112 target: Option<&str>,
113) -> Result<(Task, TemplateSource)> {
114 let loaded = load_template_with_context(name, project_root, target, false)?;
115 Ok((loaded.task, loaded.source))
116}
117
118pub fn get_template_context(target: Option<&str>, project_root: &Path) -> TemplateContext {
120 let (context, _) = detect_context_with_warnings(target, project_root, true);
121 context
122}