pub async fn get_template_content<T: TemplateServerTrait>(
server: &T,
uri: &str,
) -> Result<String, TemplateError> {
server
.get_template_content(uri)
.await
.map(|arc_str| arc_str.to_string())
.map_err(|_| TemplateError::NotFound(format!("Template content not found: {uri}")))
}
pub async fn generate_template<T: TemplateServerTrait>(
server: &T,
uri: &str,
parameters: Map<String, serde_json::Value>,
) -> Result<GeneratedTemplate, TemplateError> {
let (category, toolchain, _variant) = parse_template_uri(uri)?;
if category == "context" {
return generate_context(toolchain, parameters).await;
}
let metadata =
server
.get_template_metadata(uri)
.await
.map_err(|_| TemplateError::TemplateNotFound {
uri: uri.to_string(),
})?;
validate_parameters(&metadata.parameters, ¶meters)?;
let template_content = server
.get_template_content(uri)
.await
.map_err(|_| TemplateError::NotFound(format!("Template content not found: {uri}")))?;
let rendered =
renderer::render_template(server.get_renderer(), &template_content, parameters.clone())?;
let mut hasher = Sha256::new();
hasher.update(rendered.as_bytes());
let checksum = hasher
.finalize()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>();
let project_name = parameters
.get("project_name")
.and_then(|v| v.as_str())
.unwrap_or("project");
Ok(GeneratedTemplate {
content: rendered,
filename: format!("{}/{}", project_name, extract_filename(category)),
checksum,
toolchain: metadata.toolchain.clone(),
})
}
async fn generate_context(
toolchain: &str,
parameters: Map<String, serde_json::Value>,
) -> Result<GeneratedTemplate, TemplateError> {
let project_path = parameters
.get("project_path")
.and_then(|v| v.as_str())
.unwrap_or(".");
let context = analyze_project(Path::new(project_path), toolchain).await?;
let content = format_context_as_markdown(&context);
let mut hasher = Sha256::new();
hasher.update(content.as_bytes());
let checksum = hasher
.finalize()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>();
let toolchain_enum = match toolchain {
"rust" => crate::models::template::Toolchain::RustCli {
cargo_features: vec![],
},
"deno" => crate::models::template::Toolchain::DenoTypescript {
deno_version: "1.46".to_string(),
},
"python-uv" => crate::models::template::Toolchain::PythonUv {
python_version: "3.12".to_string(),
},
_ => {
return Err(TemplateError::InvalidUri {
uri: format!("template://context/{toolchain}/ast"),
})
}
};
Ok(GeneratedTemplate {
content,
filename: "CONTEXT.md".to_string(),
checksum,
toolchain: toolchain_enum,
})
}
pub async fn scaffold_project<T: TemplateServerTrait>(
server: Arc<T>,
toolchain: &str,
templates: Vec<String>,
parameters: serde_json::Value,
) -> Result<ScaffoldResult, TemplateError> {
let mut files = Vec::new();
let mut errors = Vec::new();
let params_map = if let serde_json::Value::Object(map) = parameters {
map
} else {
return Err(TemplateError::ValidationError {
parameter: "parameters".to_string(),
reason: "Parameters must be an object".to_string(),
});
};
for template_type in &templates {
let variant = match template_type.as_str() {
"makefile" | "readme" | "gitignore" => match toolchain {
"rust" | "deno" | "python-uv" => "cli",
_ => continue,
},
_ => continue,
};
let uri = format!("template://{template_type}/{toolchain}/{variant}");
match generate_template(server.as_ref(), &uri, params_map.clone()).await {
Ok(generated) => {
files.push(GeneratedFile {
path: generated.filename,
content: generated.content,
checksum: generated.checksum,
});
}
Err(e) => {
errors.push(ScaffoldError {
template: template_type.clone(),
error: e.to_string(),
});
}
}
}
Ok(ScaffoldResult { files, errors })
}