feat: can load project files

This commit is contained in:
Kasper Juul Hermansen 2025-03-23 20:36:40 +01:00
parent dca625af31
commit 28a1d09974
Signed by: kjuulh
SSH Key Fingerprint: SHA256:RjXh0p7U6opxnfd3ga/Y9TCo18FYlHFdSpRIV72S/QM
7 changed files with 89 additions and 10 deletions

View File

@ -1,12 +1,13 @@
use std::{net::SocketAddr, path::PathBuf};
use anyhow::Context as AnyContext;
use clap::{FromArgMatches, Parser, Subcommand, crate_authors, crate_description, crate_version};
use colored_json::ToColoredJson;
use kdl::KdlDocument;
use rusty_s3::{Bucket, Credentials, S3Action};
use crate::{
model::{Context, ForestFile, Plan},
model::{Context, ForestFile, Plan, Project, WorkspaceProject},
plan_reconciler::PlanReconciler,
state::SharedState,
};
@ -86,11 +87,36 @@ pub async fn execute() -> anyhow::Result<()> {
tracing::trace!("running as workspace");
// 1. For each member load the project
let output = serde_json::to_string_pretty(&workspace)?;
let mut workspace_members = Vec::new();
for member in workspace.members {
let workspace_member_path = project_path.join(&member.path);
let project_file_path = workspace_member_path.join("forest.kdl");
if !project_file_path.exists() {
anyhow::bail!(
"no 'forest.kdl' file was found at: {}",
workspace_member_path.display().to_string()
);
}
let project_file = tokio::fs::read_to_string(&project_file_path).await?;
let doc: KdlDocument = project_file.parse()?;
let project: WorkspaceProject = doc.try_into().context(format!(
"workspace member: {} failed to parse",
&member.path
))?;
workspace_members.push(project);
}
let output = serde_json::to_string_pretty(&workspace_members)?;
println!("{}", output.to_colored_json_auto().unwrap_or(output));
// TODO: 1a (optional). Resolve dependencies
// 2. Reconcile plans
// 3. Provide context and aggregated commands for projects
}
ForestFile::Project(project) => {

View File

@ -13,7 +13,9 @@ pub struct Context {
#[derive(Debug, Clone, Serialize)]
pub struct Plan {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Templates>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scripts: Option<Scripts>,
}
@ -164,6 +166,12 @@ pub struct Global {
items: BTreeMap<String, GlobalVariable>,
}
impl Global {
fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl From<&Global> for minijinja::Value {
fn from(value: &Global) -> Self {
Self::from_serialize(&value.items)
@ -312,10 +320,16 @@ impl TryFrom<&KdlNode> for Scripts {
#[derive(Debug, Clone, Serialize)]
pub struct Project {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan: Option<ProjectPlan>,
#[serde(skip_serializing_if = "Global::is_empty")]
pub global: Global,
#[serde(skip_serializing_if = "Option::is_none")]
pub templates: Option<Templates>,
#[serde(skip_serializing_if = "Option::is_none")]
pub scripts: Option<Scripts>,
}
@ -375,7 +389,7 @@ impl TryFrom<KdlDocument> for Project {
#[derive(Debug, Clone, Serialize)]
pub struct WorkspaceMember {
pub name: String,
pub path: String,
}
impl TryFrom<&kdl::KdlNode> for WorkspaceMember {
@ -383,7 +397,7 @@ impl TryFrom<&kdl::KdlNode> for WorkspaceMember {
fn try_from(value: &kdl::KdlNode) -> Result<Self, Self::Error> {
Ok(Self {
name: value
path: value
.entries()
.first()
.ok_or(anyhow::anyhow!(
@ -399,7 +413,7 @@ impl TryFrom<&kdl::KdlNode> for WorkspaceMember {
#[derive(Debug, Clone, Serialize)]
pub struct Workspace {
members: Vec<WorkspaceMember>,
pub members: Vec<WorkspaceMember>,
}
impl TryFrom<KdlDocument> for Workspace {
@ -453,3 +467,30 @@ impl TryFrom<KdlDocument> for ForestFile {
anyhow::bail!("a forest.kdl file must be either a project, workspace or plan")
}
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "type")]
pub enum WorkspaceProject {
Plan(Plan),
Project(Project),
}
impl TryFrom<KdlDocument> for WorkspaceProject {
type Error = anyhow::Error;
fn try_from(value: KdlDocument) -> Result<Self, Self::Error> {
if value.get("plan").is_some() && value.get("project").is_some() {
anyhow::bail!("a forest.kdl file cannot contain both a plan and project")
}
if value.get("project").is_some() {
return Ok(Self::Project(value.try_into()?));
}
if value.get("plan").is_some() {
return Ok(Self::Plan(value.try_into()?));
}
anyhow::bail!("a forest.kdl file must be either a project, workspace or plan")
}
}

View File

@ -1,9 +1,9 @@
workspace {
members {
member "./projects/a"
member "./projects/b"
member "./plan/a"
member "./plan/b"
member "./components/*"
member "projects/a"
member "projects/b"
member "plan/a"
member "plan/b"
// member "components/*"
}
}

View File

@ -0,0 +1,3 @@
plan {
name a
}

View File

@ -0,0 +1,3 @@
plan {
name b
}

View File

@ -0,0 +1,3 @@
project {
name a
}

View File

@ -0,0 +1,3 @@
project {
name b
}