feat: create support for templates
This commit is contained in:
@@ -3,9 +3,10 @@ use std::{net::SocketAddr, path::PathBuf};
|
||||
use clap::{Parser, Subcommand};
|
||||
use kdl::KdlDocument;
|
||||
use rusty_s3::{Bucket, Credentials, S3Action};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
use crate::{
|
||||
model::{Context, Plan, Project},
|
||||
model::{Context, Plan, Project, TemplateType},
|
||||
plan_reconciler::PlanReconciler,
|
||||
state::SharedState,
|
||||
};
|
||||
@@ -15,18 +16,20 @@ use crate::{
|
||||
struct Command {
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
|
||||
#[arg(
|
||||
env = "FOREST_PROJECT_PATH",
|
||||
long = "project-path",
|
||||
default_value = "."
|
||||
)]
|
||||
project_path: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Init {
|
||||
#[arg(
|
||||
env = "FOREST_PROJECT_PATH",
|
||||
long = "project-path",
|
||||
default_value = "."
|
||||
)]
|
||||
project_path: PathBuf,
|
||||
},
|
||||
Init {},
|
||||
|
||||
Template {},
|
||||
|
||||
Serve {
|
||||
#[arg(env = "FOREST_HOST", long, default_value = "127.0.0.1:3000")]
|
||||
@@ -52,42 +55,138 @@ enum Commands {
|
||||
pub async fn execute() -> anyhow::Result<()> {
|
||||
let cli = Command::parse();
|
||||
|
||||
let project_path = &cli.project_path.canonicalize()?;
|
||||
let project_file_path = project_path.join("forest.kdl");
|
||||
if !project_file_path.exists() {
|
||||
anyhow::bail!(
|
||||
"no 'forest.kdl' file was found at: {}",
|
||||
project_file_path.display().to_string()
|
||||
);
|
||||
}
|
||||
|
||||
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
|
||||
let project_doc: KdlDocument = project_file.parse()?;
|
||||
|
||||
let project: Project = project_doc.try_into()?;
|
||||
tracing::trace!("found a project name: {}", project.name);
|
||||
|
||||
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
||||
.reconcile(&project, project_path)
|
||||
.await?
|
||||
{
|
||||
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
|
||||
let plan_doc: KdlDocument = plan_file.parse()?;
|
||||
|
||||
let plan: Plan = plan_doc.try_into()?;
|
||||
tracing::trace!("found a plan name: {}", project.name);
|
||||
|
||||
Some(plan)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let context = Context { project, plan };
|
||||
|
||||
match cli.command.unwrap() {
|
||||
Commands::Init { project_path } => {
|
||||
Commands::Init {} => {
|
||||
tracing::info!("initializing project");
|
||||
tracing::trace!("found context: {:?}", context);
|
||||
}
|
||||
|
||||
let project_file_path = project_path.join("forest.kdl");
|
||||
if !project_file_path.exists() {
|
||||
anyhow::bail!(
|
||||
"no 'forest.kdl' file was found at: {}",
|
||||
project_file_path.display().to_string()
|
||||
);
|
||||
}
|
||||
Commands::Template {} => {
|
||||
tracing::info!("templating");
|
||||
|
||||
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
|
||||
let project_doc: KdlDocument = project_file.parse()?;
|
||||
|
||||
let project: Project = project_doc.try_into()?;
|
||||
tracing::trace!("found a project name: {}", project.name);
|
||||
|
||||
let plan = if let Some(plan_file_path) = PlanReconciler::new()
|
||||
.reconcile(&project, &project_path)
|
||||
.await?
|
||||
{
|
||||
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
|
||||
let plan_doc: KdlDocument = plan_file.parse()?;
|
||||
|
||||
let plan: Plan = plan_doc.try_into()?;
|
||||
tracing::trace!("found a plan name: {}", project.name);
|
||||
|
||||
Some(plan)
|
||||
} else {
|
||||
None
|
||||
let Some(template) = context.project.templates else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let context = Context { project, plan };
|
||||
match template.ty {
|
||||
TemplateType::Jinja2 => {
|
||||
for entry in glob::glob(&format!(
|
||||
"{}/{}",
|
||||
project_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();
|
||||
|
||||
tracing::info!("context: {:+?}", context);
|
||||
let entry_rel = if entry.is_absolute() {
|
||||
entry.strip_prefix(project_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,
|
||||
project_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)?;
|
||||
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?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Commands::Serve {
|
||||
|
||||
Reference in New Issue
Block a user