// A runtime template engine — moonctl's faithful equivalent of goctl's
// `text/template`. It parses a template into a node tree once, then renders it
// against a dynamic `Value` (MoonBit has no reflection, so template data is an
// explicit tagged value the way `encoding/json` models it). Supports `{{.Field}}`
// interpolation, `{{if}}/{{else}}/{{range}}/{{with}}/{{end}}`, `$variable`
// assignment, `|` pipelines, parenthesised sub-pipelines, `{{- -}}` whitespace
// trimming, `{{/* comments */}}`, and a library of pipeline functions. Pure and
// all-backend.
///|
/// One `@server` group as template data: `{name, prefix, jwt, middleware,
/// maxBytes, timeout, signature}` plus every annotation mctl does not name itself.
fn group_to_value(g : @api.Group) -> @template.Value {
let middleware : Array[@template.Value] = []
for m in g.middleware {
middleware.push(Str(m))
}
let fields : Array[(String, @template.Value)] = [
("name", Str(g.name)),
("prefix", Str(g.prefix)),
("jwt", Str(g.jwt)),
("middleware", List(middleware)),
("maxBytes", Int(g.max_bytes)),
("timeout", Str(g.timeout)),
("signature", Bool(g.signature)),
]
for kv in g.extra {
fields.push((kv.0, Str(kv.1)))
}
Dict(Map(fields))
}
///|
/// Expose a parsed `.api` `Spec` as template `Value` data: a `Dict` with
/// `service` (string), `services` (list of `{name, routes}`), `routes` (list of
/// `{verb, path, route, handler, summary, hasSummary, group, hasGroup, req, resp,
/// hasReq, hasResp}`, where `route` is `path` as the framework spells it), `types` (list of `{name, fields:[{name,
/// type}]}`), `groups` (list of the `@server` blocks) and `hasTypes` (bool). This
/// is what the template-driven generator ranges over.
pub fn spec_to_value(spec : @api.Spec) -> @template.Value {
let routes : Array[@template.Value] = []
for r in spec.routes {
let name = match r.group {
Some(g) => g.name
None => ""
}
routes.push(
Dict(
Map([
("verb", @template.Str(r.verb)),
("path", @template.Str(r.path)),
("route", @template.Str(route_path(r.path))),
("handler", @template.Str(r.handler)),
("summary", @template.Str(r.summary)),
("hasSummary", @template.Bool(r.summary != "")),
("group", @template.Str(name)),
("hasGroup", @template.Bool(name != "")),
("req", @template.Str(r.req)),
("resp", @template.Str(r.resp)),
("hasReq", @template.Bool(r.req != "")),
("hasResp", @template.Bool(r.resp != "")),
]),
),
)
}
let groups : Array[@template.Value] = []
for g in spec.groups {
groups.push(group_to_value(g))
}
let types : Array[@template.Value] = []
for t in spec.types {
let fields : Array[@template.Value] = []
for f in t.fields {
fields.push(
Dict(
Map([
("name", @template.Str(f.name)),
("type", @template.Str(f.type_)),
]),
),
)
}
types.push(
Dict(
Map([
("name", @template.Str(t.name)),
("fields", @template.List(fields)),
]),
),
)
}
let services : Array[@template.Value] = []
for s in spec.services {
let handlers : Array[@template.Value] = []
for r in s.routes {
handlers.push(@template.Str(r.handler))
}
services.push(
Dict(
Map([
("name", @template.Str(s.name)),
("routes", @template.List(handlers)),
]),
),
)
}
Dict(
Map([
("service", @template.Str(spec.service)),
("services", @template.List(services)),
("routes", @template.List(routes)),
("types", @template.List(types)),
("groups", @template.List(groups)),
("hasTypes", @template.Bool(spec.types.length() != 0)),
]),
)
}
///|
/// Generate code from a `Spec` with a caller-supplied template instead of the
/// built-in generator — the runtime-template path goctl exposes for custom
/// scaffolds. Equivalent to `render(template_source, spec_to_value(spec))`.
pub fn generate_with(
spec : @api.Spec,
template_source : String,
) -> String raise @template.Refused {
@template.render(template_source, spec_to_value(spec))
}