moonctl (mctl) — a spec-driven code generator for MoonBit (← goctl): parse a .api service spec and emit compilable moonapi scaffolding (routes + handler stubs).
Dependencies
service greet {
get /ping ping "health check"
get /users/:id get_user
post /users create_user "create a user"
}// Code generated by moonctl. DO NOT EDIT.
///|
/// Build the greet application with its routes wired to handlers.
pub fn build_app() -> @moonapi.App {
let app = @moonapi.App::new()
app.get("/ping", ctx => ping(ctx), summary="health check")
app.get("/users/:id", ctx => get_user(ctx))
app.post("/users", ctx => create_user(ctx), summary="create a user")
app
}
///|
/// `get /ping`.
pub fn ping(_ctx : @moonapi.Context) -> @moonasgi.Response {
@moonapi.text(200, "TODO: ping")
}
// ... one stub per handler@server (
group: user
prefix: /api/v1
jwt: Auth
middleware: Log,Trace
)
service greet {
@doc "log a user in"
@handler login
post /login (LoginReq) returns (LoginResp)
}pub fn build_app() -> @moonapi.App {
let app = @moonapi.App::new()
// @server group: user, prefix: /api/v1, jwt: Auth, middleware: Log,Trace
app.post("/api/v1/login", ctx => login(ctx), summary="log a user in")
app
}
///|
/// `post /api/v1/login` of group user.
pub fn login(_ctx : @moonapi.Context) -> @moonasgi.Response {
@moonapi.text(200, "TODO: login")
}type ListReq {
Region string `path:"region"`
Page int `form:"page,default=1,range=[1:100]"`
Size int `form:"size,optional"`
Trace string `header:"X-Trace"`
Sort string `json:"sort,options=asc|desc"`
}pub fn ListReq::with_defaults(self : ListReq) -> ListReq { … } // fills in every default=
pub fn ListReq::check(self : ListReq) -> String? { … } // the first field that fails$ mctl gen api greet.api --dir out --style go_zero
out/moon.mod.json out/internal/types/types.mbt
out/moon.pkg.json out/internal/handler/routes.mbt
out/greet.mbt out/internal/handler/user/login_handler.mbt
out/etc/greet.yaml out/internal/logic/user/login_logic.mbt
out/internal/config/config.mbt out/internal/middleware/log_middleware.mbt
out/internal/svc/service_context.mbtlet tree = @moonctl.generate_tree(spec, style~, dir~) // -> [TreeFile { path, content, regen }]
let write = @moonctl.tree_plan(tree, p => on_disk(p)) // -> the files to actually write| template | welcome_to_go_zero becomes |
|---|---|
| gozero (default) | welcometogozero |
| goZero | welcomeToGoZero |
| go_zero | welcome_to_go_zero |
| Go#zero | Welcome#to#go#zero |
import "user.api"
import (
"shared/base.api"
"../common.api"
)let spec = @moonctl.parse_all(source, files, from="spec/greet.api")@moonctl.parse("service greet {\n gett /ping ping\n}")
// SpecError — line 2: unknown verb "gett"let spec = @moonctl.parse(source) // -> Spec { service, routes, types, info, groups, imports }
let code = @moonctl.generate(spec) // -> compilable moonapi scaffold (String)
let tree = @moonctl.generate_tree(spec) // -> the layered project, file by file$ mctl gen api greet.api # -> a moonapi project tree (etc/, internal/…)
$ mctl gen api greet.api --style go_zero --dir out
$ mctl gen api greet.api --flat # -> greet.mbt (one file, as before)
$ mctl gen proto greet.proto # -> greet_grpc.mbt (moonrpc service stub)
$ mctl gen model user.api # -> user_model.mbt (moonorm model + migration)
$ mctl gen crud schema.sql # -> schema_model.mbt (moonorm model + typed CRUD)
$ mctl gen doc greet.api # -> greet_openapi.json + greet_swagger.html
$ mctl model datasource sqlite:shop.db # -> datasource_model.mbt (read a live DB's schema)
$ mctl api new blog # scaffold a moonapi service project under blog/
$ mctl rpc new echo # scaffold a moonrpc service project under echo/
$ mctl model new account # scaffold a moonorm data-layer project under account/
$ mctl docker greet # -> Dockerfile + .dockerignore
$ mctl kube greet # -> deploy/deployment.yaml + deploy/service.yaml
$ mctl plugin ./my-plugin greet.api # run an external plugin over the parsed speclet stub = @moonctl.generate_grpc(@moonctl.parse_proto(proto_src)) // -> moonrpc stub
let model = @moonctl.generate_model(@moonctl.parse(api_src)) // -> moonorm model + migrationlet code = @moonctl.generate_crud_from_ddl(sql_src) // -> moonorm model + CRUD (String)$ mctl model datasource sqlite:shop.db
$ mctl model datasource postgres://user:pass@localhost:5432/shop$ mctl api new blog # blog/{moon.mod.json, blog.api, src/{moon.pkg.json, app.mbt}, README.md}
$ mctl rpc new echo # echo/{moon.mod.json, echo.proto, src/{moon.pkg.json, service.mbt}, README.md}
$ mctl model new account # account/{moon.mod.json, schema.sql, src/{moon.pkg.json, model.mbt}, README.md}
$ mctl docker greet # Dockerfile (two-stage native build) + .dockerignore
$ mctl kube greet # deploy/deployment.yaml + deploy/service.yamllet files = @moonctl.scaffold_api("blog") // -> Array[GenFile] to writelet (bin, argv) = @moonctl.plugin_argv("my-plugin -flag value") // what to run
let req = @moonctl.plugin_request(spec, api_file_path~, style~, dir~) // its stdin
let files = @moonctl.parse_gen_files(@json.parse(out)) // the plugin's stdout -> files to writelet spec = @moonctl.parse(api_src)
let json = @moonctl.generate_doc(spec, version=OpenApi31) // or Swagger20 / OpenApi30
let html = @moonctl.swagger_ui_stub(spec_url="/openapi.json")let out = @moonctl.render(
"{{range .routes}}{{.verb}} {{.path}} -> {{.handler}}\n{{end}}",
@moonctl.spec_to_value(spec),
)
// or drive codegen from a caller-supplied template:
let code = @moonctl.generate(spec, template=my_template) // == generate_withpub suberror DataSourceError {
BadDsn(String)
}pub suberror SpecError {
Syntax(line~ : Int, msg~ : String)
Missing(file~ : String, path~ : String)
}pub suberror StyleError {
BadStyle(String)
}pub suberror TemplateError {
ParseError(String)
ExecError(String)
}pub(all) struct DdlColumn {
name : String
type_ : String
primary_key : Bool
nullable : Bool
default_ : String?
}pub(all) struct Field {
name : String
type_ : String
tag : String
}pub(all) struct GenFile {
path : String
content : String
}pub(all) struct PgTarget {
host : String
port : Int
user : String
password : String
database : String
}pub(all) struct Proto {
package_ : String
services : Array[ProtoService]
messages : Array[ProtoMessage]
enums : Array[ProtoEnum]
}pub(all) struct ProtoEnumValue {
name : String
number : Int
}pub(all) struct ProtoField {
name : String
type_ : String
number : Int
}pub(all) struct ProtoMessage {
name : String
fields : Array[ProtoField]
oneofs : Array[ProtoOneof]
reserved_numbers : Array[Int]
reserved_names : Array[String]
}pub(all) struct ReflectedColumn {
table : String
name : String
sql_type : String
primary_key : Bool
nullable : Bool
}pub(all) struct Route {
verb : String
path : String
handler : String
summary : String
req : String
resp : String
group : Group?
}pub(all) struct Rpc {
name : String
request : String
response : String
client_streaming : Bool
server_streaming : Bool
}Style::parse("gozero").format("welcome_to_go_zero") // welcometogozero
Style::parse("goZero").format("welcome_to_go_zero") // welcomeToGoZero
Style::parse("go_zero").format("welcome_to_go_zero") // welcome_to_go_zero
Style::parse("Go#zero").format("welcome_to_go_zero") // Welcome#to#go#zeropub struct Template {
renderer : (Value) -> String raise TemplateError
funcs : Map[String, (Array[Value]) -> Value raise TemplateError]
}fn generate_crud_from_ddl(source : String) -> Stringfn generate_doc(spec : Spec, version? : DocVersion, title? : String, api_version? : String) -> Stringfn openapi_document(spec : Spec, version? : DocVersion, title? : String, api_version? : String) -> Jsonsyntax = "v1"
info (
title: "greet"
version: "v2"
)
type LoginReq {
name: string
}
@server (
group: user
prefix: /api/v1
middleware: Log
)
service greet {
@doc "health check"
@handler ping
get /ping
@handler login
post /login (LoginReq) returns (LoginResp)
get /legacy legacy_handler "the inline form"
}syntax = "proto3";
package greet;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }fn resolve_import(from : String, path : String) -> Stringfn swagger_ui_stub(spec_url? : String, title? : String) -> StringInstall
Download zipmoonctl (mctl) — a spec-driven code generator for MoonBit (← goctl): parse a .api service spec and emit compilable moonapi scaffolding (routes + handler stubs).
Dependencies