//! Vue target — composable per context + composable per call. //! Output shape lives at `templates/vue/vue.ts.j2`. use std::path::PathBuf; use askama::Template; use crate::config::Config; use crate::emit::CodegenTarget; use crate::emit::EmittedFile; use crate::emit::casing::pascal_case; use crate::ir::{IsContext, MizanFunction, MizanIR}; pub struct VueAdapter; impl CodegenTarget for VueAdapter { fn name(&self) -> &'static str { "vue" } fn emit(&self, ir: &MizanIR, _config: &Config) -> Vec { let content = build_template(ir).render().expect("vue template renders"); vec![EmittedFile::new(PathBuf::from("vue.ts"), content)] } } #[derive(Template)] #[template(path = "vue/vue.ts.j2", escape = "none")] struct VueTemplate<'a> { stage1_imports: Vec, contexts: Vec>, calls: Vec, } struct CtxRender<'a> { pascal: String, name: &'a str, has_params: bool, params_arg: &'static str, fns: Vec>, } struct FnRender<'a> { camel_name: &'a str, name: &'a str, output_type: &'a str, } struct CallRender { pascal: String, has_input: bool, } fn build_template(ir: &MizanIR) -> VueTemplate<'_> { let contexts: Vec = ir.contexts.iter() .map(|(ctx_name, ctx_meta)| { let has_params = !ctx_meta.params.is_empty(); let ctx_fns: Vec = ir.functions.iter() .filter(|f| f.is_context.as_str() == Some(ctx_name.as_str())) .map(|f| FnRender { camel_name: &f.camel_name, name: &f.name, output_type: &f.output_type, }) .collect(); CtxRender { pascal: pascal_case(ctx_name), name: ctx_name, has_params, params_arg: if has_params { "params" } else { "{} as any" }, fns: ctx_fns, } }) .collect(); let mutations: Vec<&MizanFunction> = ir.functions.iter() .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && !f.affects.is_empty()) .collect(); let plain_fns: Vec<&MizanFunction> = ir.functions.iter() .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && f.affects.is_empty()) .collect(); let calls: Vec = mutations.iter().chain(plain_fns.iter()) .map(|f| CallRender { pascal: pascal_case(&f.camel_name), has_input: f.has_input, }) .collect(); let mut stage1: Vec = Vec::new(); for ctx_name in ir.contexts.keys() { let p = pascal_case(ctx_name); stage1.push(format!("fetch{p}Context")); stage1.push(format!("type {p}ContextData")); stage1.push(format!("type {p}ContextParams")); } for fn_meta in mutations.iter().chain(plain_fns.iter()) { stage1.push(format!("call{}", pascal_case(&fn_meta.camel_name))); } VueTemplate { stage1_imports: stage1, contexts, calls } }