feat: implement template plan

This commit is contained in:
Kasper Juul Hermansen 2025-02-28 21:21:34 +01:00
parent ef8deadfd8
commit 9be64b74b2
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
7 changed files with 137 additions and 0 deletions

View File

@ -11,6 +11,107 @@ impl Template {
pub async fn execute(self, project_path: &Path, context: &Context) -> anyhow::Result<()> { pub async fn execute(self, project_path: &Path, context: &Context) -> anyhow::Result<()> {
tracing::info!("templating"); tracing::info!("templating");
self.execute_plan(project_path, context).await?;
self.execute_project(project_path, context).await?;
Ok(())
}
async fn execute_plan(&self, project_path: &Path, context: &Context) -> anyhow::Result<()> {
let plan_path = &project_path.join(".forest").join("plan");
let Some(Some(template)) = &context.plan.as_ref().map(|p| &p.templates) else {
return Ok(());
};
match template.ty {
TemplateType::Jinja2 => {
for entry in glob::glob(&format!(
"{}/{}",
plan_path.display().to_string().trim_end_matches("/"),
template.path.trim_start_matches("./"),
))
.map_err(|e| anyhow::anyhow!("failed to read glob pattern: {}", e))?
{
let entry = entry.map_err(|e| anyhow::anyhow!("failed to read path: {}", e))?;
let entry_name = entry.display().to_string();
let entry_rel = if entry.is_absolute() {
entry.strip_prefix(plan_path).map(|e| e.to_path_buf())
} else {
Ok(entry.clone())
};
let rel_file_path = entry_rel
.map(|p| {
if p.file_name()
.map(|f| f.to_string_lossy().ends_with(".jinja2"))
.unwrap_or(false)
{
p.with_file_name(
p.file_stem().expect("to be able to find a filename"),
)
} else {
p.to_path_buf()
}
})
.map_err(|e| {
anyhow::anyhow!(
"failed to find relative file: {}, project: {}, file: {}",
e,
plan_path.display(),
entry_name
)
})?;
let output_file_path = project_path
.join(".forest/temp")
.join(&template.output)
.join(rel_file_path);
let contents = tokio::fs::read_to_string(&entry).await.map_err(|e| {
anyhow::anyhow!("failed to read template: {}, err: {}", entry.display(), e)
})?;
let mut env = minijinja::Environment::new();
env.add_template(&entry_name, &contents)?;
env.add_global("global", &context.project.global);
let tmpl = env.get_template(&entry_name)?;
let output = tmpl
.render(minijinja::context! {})
.map_err(|e| anyhow::anyhow!("failed to render template: {}", e))?;
tracing::info!("rendered template: {}", output);
if let Some(parent) = output_file_path.parent() {
tokio::fs::create_dir_all(parent).await.map_err(|e| {
anyhow::anyhow!(
"failed to create directory (path: {}) for output: {}",
parent.display(),
e
)
})?;
}
let mut output_file = tokio::fs::File::create(&output_file_path)
.await
.map_err(|e| {
anyhow::anyhow!(
"failed to create file: {}, error: {}",
output_file_path.display(),
e
)
})?;
output_file.write_all(output.as_bytes()).await?;
}
}
}
Ok(())
}
async fn execute_project(&self, project_path: &Path, context: &Context) -> anyhow::Result<()> {
let Some(template) = &context.project.templates else { let Some(template) = &context.project.templates else {
return Ok(()); return Ok(());
}; };

View File

@ -12,6 +12,8 @@ pub struct Context {
#[derive(Debug, Clone, Serialize)] #[derive(Debug, Clone, Serialize)]
pub struct Plan { pub struct Plan {
pub name: String, pub name: String,
pub templates: Option<Templates>,
pub scripts: Option<Scripts>,
} }
impl TryFrom<KdlDocument> for Plan { impl TryFrom<KdlDocument> for Plan {
@ -35,6 +37,14 @@ impl TryFrom<KdlDocument> for Plan {
}) })
.cloned() .cloned()
.ok_or(anyhow::anyhow!("a forest kuddle plan must have a name"))?, .ok_or(anyhow::anyhow!("a forest kuddle plan must have a name"))?,
templates: plan_children
.get("templates")
.map(|t| t.try_into())
.transpose()?,
scripts: plan_children
.get("scripts")
.map(|m| m.try_into())
.transpose()?,
}) })
} }
} }

View File

@ -1,3 +1,13 @@
plan { plan {
name project name project
templates type=jinja2 {
path "templates/*.jinja2"
output "output/"
}
scripts {
world type=shell {}
hello type=shell {}
}
} }

5
examples/plan/scripts/hello.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env zsh
set -e
echo "hello plan"

5
examples/plan/scripts/world.sh Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env zsh
set -e
echo "hello plan world"

View File

@ -0,0 +1,3 @@
something plan
val is mapping: {{ global.someKey.some.key.val is mapping }}

View File

@ -0,0 +1,3 @@
something plan
val is mapping: {{ global.someKey.some.key.val is mapping }}