// `mctl template` — the bundled template library, described.
//
// A template is a directory of files with `{{.Name}}` holes in them, plus the rules
// mctl needs to fill them: which spec each kind reads, what it writes, and what a
// user has to rename. The catalogue lives here so `mctl template list` and `mctl
// template show` describe the same thing the generators use, and so a remote
// library (--remote, --branch) can be compared entry by entry.
///|
/// One template: the name a user types, the kind of spec it reads, one sentence on
/// what it produces, and the directory under the template home it lives in.
pub(all) struct TemplateEntry {
name : String
kind : String
description : String
path : String
}
///|
/// The template library a run reads from. Empty `home` and `remote` mean the
/// bundled library — the scaffolds in code; `--home` names a directory laid out
/// like it, `--remote` a git repository to check out at `branch`.
pub(all) struct Library {
home : String
remote : String
branch : String
}
///|
/// The branch `--remote` reads when `--branch` names none.
pub let default_branch : String = "main"
///|
/// The library the three flags describe. `--home` wins over `--remote` when both are
/// given: a local directory is already checked out.
pub fn library(home? : String, remote? : String, branch? : String) -> Library {
{
home: home.unwrap_or(""),
remote: remote.unwrap_or(""),
branch: match branch {
Some(b) if b != "" => b
_ => default_branch
},
}
}
///|
/// Whether this is the bundled library, which is code rather than files.
pub fn is_bundled(lib : Library) -> Bool {
lib.home == "" && lib.remote == ""
}
///|
/// The directory `t` is read from, given the root the library resolves to. The root
/// is the `--home` directory, or where `--remote` was checked out.
pub fn template_dir(root : String, t : TemplateEntry) -> String {
if root == "" {
t.path
} else if @text.ends_with(root, "/") {
root + t.path
} else {
root + "/" + t.path
}
}
///|
/// The bundled library, in the order it is listed.
pub fn templates() -> Array[TemplateEntry] {
[
{
name: "api-service",
kind: "api",
description: "a moonapi service: routes, handlers, logic, config and a Dockerfile",
path: "api/service",
},
{
name: "api-flat",
kind: "api",
description: "the same routes and types in one .mbt file, for a small service",
path: "api/flat",
},
{
name: "rpc-service",
kind: "proto",
description: "a moonrpc service: messages, codecs, handler table and a client",
path: "rpc/service",
},
{
name: "gql-service",
kind: "api",
description: "a moongql schema and resolver over the spec's types",
path: "gql/service",
},
{
name: "mcp-server",
kind: "api",
description: "an MCP server whose tools are the spec's annotated handlers",
path: "mcp/server",
},
{
name: "model-mysql",
kind: "ddl",
description: "moonorm models and CRUD for a MySQL schema, reflected or from DDL",
path: "model/mysql",
},
{
name: "model-postgres",
kind: "ddl",
description: "moonorm models and CRUD for a PostgreSQL schema",
path: "model/postgres",
},
{
name: "model-mongo",
kind: "api",
description: "document models over a Mongo collection, with a typed collection port",
path: "model/mongo",
},
{
name: "gateway",
kind: "proto",
description: "an HTTP gateway that routes to a moonrpc client",
path: "gateway/http",
},
]
}
///|
/// The template called `name`, or `None`.
pub fn find_template(name : String) -> TemplateEntry? {
for t in templates() {
if t.name == name {
return Some(t)
}
}
None
}
///|
/// The templates of one kind — the ones a `gen` of that kind may pick.
pub fn templates_of(kind : String) -> Array[TemplateEntry] {
let out : Array[TemplateEntry] = []
for t in templates() {
if t.kind == kind {
out.push(t)
}
}
out
}
///|
/// `mctl template list`: a table a reader scans rather than a paragraph they read.
pub fn template_table() -> String {
let out : Array[String] = []
out.push("| Template | Spec | What it produces |")
out.push("| --- | --- | --- |")
for t in templates() {
out.push("| `" + t.name + "` | " + t.kind + " | " + t.description + " |")
}
@text.join_lines(out)
}
///|
/// `mctl template show `: one entry, and where it sits in the library.
pub fn template_detail(t : TemplateEntry) -> String {
let out : Array[String] = []
out.push("# " + t.name)
out.push("")
out.push(t.description + ".")
out.push("")
out.push("- reads: `." + t.kind + "`")
out.push("- lives in: `" + t.path + "` under the template home")
out.push("- override: put your own `" + t.path + "` under `--home `")
@text.join_lines(out)
}
///|
/// The files `mctl template init` writes: one entry under `api/service`, small
/// enough to read in a minute and complete enough to serve the spec it is given.
/// Both the file names and the text are templates, so a library is edited as
/// templates and read back the same way.
pub fn template_starter(pins : Map[String, String]) -> Array[(String, String)] {
let deps = [
"moonbitstack/moonapi", "moonbitstack/moonasgi", "moonbitstack/mooncat",
]
let mut pinned = "import {\n"
let mut plain = "import {\n"
for dep in deps {
let version = match pins.get(dep) {
Some(v) => "@" + v
None => ""
}
pinned = pinned + " \"" + dep + version + "\",\n"
plain = plain + " \"" + dep + "\",\n"
}
pinned = pinned + "}\n"
plain = plain + "}\n"
// `moon.mod` names the release, `moon.pkg` only the package.
let mod = "name = \"{{.service}}\"\n\nversion = \"0.1.0\"\n\n" + pinned
let pkg = plain +
"\nsupported_targets = \"native\"\n\npkgtype(kind: \"executable\")\n"
let main_src = "///|\n/// The {{.service}} service: one handler per route, and a server on 12000.\n" +
"{{range .routes}}pub fn {{.handler}}(_ctx : @moonapi.Context) -> @moonasgi.Response {\n @moonapi.text(200, \"{{.handler}}\")\n}\n\n{{end}}" +
"///|\nfn build_app() -> @moonapi.App {\n let app = @moonapi.App::new()\n{{range .routes}} app.{{.verb}}(\"{{.route}}\", ctx => {{.handler}}(ctx))\n{{end}} app\n}\n\n///|\nasync fn main {\n let app = build_app()\n @mooncat.serve(app, port=12000)\n}\n"
let readme = "# {{.service}} templates\n\n`moon.mod`, `moon.pkg` and `{{.service}}.mbt`, written by `mctl template init`.\nEvery file is a template over the parsed spec: the service name, a loop over its\nroutes, and each route's verb, path, handler, summary, request type and reply type.\nGenerate with://n//n```bash\nmctl gen api --home --dir