Compare commits

..

1 Commits

Author SHA1 Message Date
19fdfcdfa5 chore(deps): update rust crate clap to v4.5.29 2025-02-15 01:34:38 +00:00
6 changed files with 38 additions and 234 deletions

17
Cargo.lock generated
View File

@ -236,9 +236,7 @@ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
"dotenvy", "dotenvy",
"glob",
"kdl", "kdl",
"minijinja",
"rusty-s3", "rusty-s3",
"serde", "serde",
"tokio", "tokio",
@ -286,12 +284,6 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]] [[package]]
name = "heck" name = "heck"
version = "0.5.0" version = "0.5.0"
@ -543,15 +535,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "minijinja"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cff7b8df5e85e30b87c2b0b3f58ba3a87b68e133738bf512a7713769326dbca9"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.3" version = "0.8.3"

View File

@ -17,5 +17,3 @@ rusty-s3 = "0.7.0"
url = "2.5.4" url = "2.5.4"
kdl = "6.3.3" kdl = "6.3.3"
walkdir = "2.5.0" walkdir = "2.5.0"
minijinja = "2.7.0"
glob = "0.3.2"

View File

@ -3,10 +3,9 @@ use std::{net::SocketAddr, path::PathBuf};
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use kdl::KdlDocument; use kdl::KdlDocument;
use rusty_s3::{Bucket, Credentials, S3Action}; use rusty_s3::{Bucket, Credentials, S3Action};
use tokio::io::AsyncWriteExt;
use crate::{ use crate::{
model::{Context, Plan, Project, TemplateType}, model::{Context, Plan, Project},
plan_reconciler::PlanReconciler, plan_reconciler::PlanReconciler,
state::SharedState, state::SharedState,
}; };
@ -16,20 +15,18 @@ use crate::{
struct Command { struct Command {
#[command(subcommand)] #[command(subcommand)]
command: Option<Commands>, command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Init {
#[arg( #[arg(
env = "FOREST_PROJECT_PATH", env = "FOREST_PROJECT_PATH",
long = "project-path", long = "project-path",
default_value = "." default_value = "."
)] )]
project_path: PathBuf, project_path: PathBuf,
} },
#[derive(Subcommand)]
enum Commands {
Init {},
Template {},
Serve { Serve {
#[arg(env = "FOREST_HOST", long, default_value = "127.0.0.1:3000")] #[arg(env = "FOREST_HOST", long, default_value = "127.0.0.1:3000")]
@ -55,7 +52,10 @@ enum Commands {
pub async fn execute() -> anyhow::Result<()> { pub async fn execute() -> anyhow::Result<()> {
let cli = Command::parse(); let cli = Command::parse();
let project_path = &cli.project_path.canonicalize()?; match cli.command.unwrap() {
Commands::Init { project_path } => {
tracing::info!("initializing project");
let project_file_path = project_path.join("forest.kdl"); let project_file_path = project_path.join("forest.kdl");
if !project_file_path.exists() { if !project_file_path.exists() {
anyhow::bail!( anyhow::bail!(
@ -71,7 +71,7 @@ pub async fn execute() -> anyhow::Result<()> {
tracing::trace!("found a project name: {}", project.name); tracing::trace!("found a project name: {}", project.name);
let plan = if let Some(plan_file_path) = PlanReconciler::new() let plan = if let Some(plan_file_path) = PlanReconciler::new()
.reconcile(&project, project_path) .reconcile(&project, &project_path)
.await? .await?
{ {
let plan_file = tokio::fs::read_to_string(&plan_file_path).await?; let plan_file = tokio::fs::read_to_string(&plan_file_path).await?;
@ -87,106 +87,7 @@ pub async fn execute() -> anyhow::Result<()> {
let context = Context { project, plan }; let context = Context { project, plan };
match cli.command.unwrap() { tracing::info!("context: {:+?}", context);
Commands::Init {} => {
tracing::info!("initializing project");
tracing::trace!("found context: {:?}", context);
}
Commands::Template {} => {
tracing::info!("templating");
let Some(template) = context.project.templates else {
return Ok(());
};
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();
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 { Commands::Serve {

View File

@ -153,79 +153,12 @@ impl TryFrom<&KdlNode> for Global {
} }
} }
#[derive(Debug, Clone, Default)]
pub enum TemplateType {
#[default]
Jinja2,
}
#[derive(Debug, Clone)]
pub struct Templates {
pub ty: TemplateType,
pub path: String,
pub output: PathBuf,
}
impl Default for Templates {
fn default() -> Self {
Self {
ty: TemplateType::default(),
path: "./templates/*.jinja2".into(),
output: "output/".into(),
}
}
}
impl TryFrom<&KdlNode> for Templates {
type Error = anyhow::Error;
fn try_from(value: &KdlNode) -> Result<Self, Self::Error> {
let mut templates = Templates::default();
for entry in value.entries() {
let Some(name) = entry.name() else { continue };
match name.value() {
"type" => {
let Some(val) = entry.value().as_string() else {
anyhow::bail!("type is not a valid string")
};
match val.to_lowercase().as_str() {
"jinja2" => templates.ty = TemplateType::Jinja2,
e => {
anyhow::bail!("failed to find a template matching the required type: {}, only 'jinja2' is supported", e);
}
}
}
"path" => {
let Some(val) = entry.value().as_string() else {
anyhow::bail!("failed to parse path as a valid string")
};
templates.path = val.to_string();
}
"output" => {
let Some(val) = entry.value().as_string() else {
anyhow::bail!("failed to parse val as a valid string")
};
templates.output = PathBuf::from(val);
}
_ => continue,
}
}
Ok(templates)
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Project { pub struct Project {
pub name: String, pub name: String,
pub description: Option<String>, pub description: Option<String>,
pub plan: Option<ProjectPlan>, pub plan: Option<ProjectPlan>,
pub global: Global, pub global: Global,
pub templates: Option<Templates>,
} }
impl TryFrom<KdlDocument> for Project { impl TryFrom<KdlDocument> for Project {
@ -270,10 +203,6 @@ impl TryFrom<KdlDocument> for Project {
}), }),
plan: project_plan, plan: project_plan,
global: global.unwrap_or_default(), global: global.unwrap_or_default(),
templates: project_children
.get("templates")
.map(|t| t.try_into())
.transpose()?,
}) })
} }
} }

View File

@ -20,10 +20,4 @@ project {
} }
} }
} }
templates type=jinja2 {
path "templates/*.jinja2"
output "output/"
} }
}

View File

@ -1 +0,0 @@
something