moonctl (mctl) — a spec-driven code generator for MoonBit (← goctl): parse a .api service spec and emit compilable moonapi scaffolding (routes + handler stubs).
Moved on mooncakes from Lfan-ke/moonctl to moonbitstack/moonctl.
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 out/internal/types/types.mbt
out/moon.pkg 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"| Package | What | goctl |
|---|---|---|
| api | the .api language: types, routes, tags, imports | api/parser, api/spec |
| gen | the moonapi service a spec describes, and the validation its tags ask for | api/gogen |
| template | the template engine, Go text/template syntax and semantics | text/template |
| style | --style naming templates | config |
| proto | moonrpc messages, codecs and handler tables from .proto | rpc |
| doc | OpenAPI and Swagger documents | api/docgen |
| gql | a moongql schema | — |
| model | moonorm models from a spec, from DDL, or from a live database | model |
| agent | a moonkoog agent | — |
| plugin | the plugin protocol | plugin |
| scaffold | project skeletons and the layered trees | api new, rpc new, docker, kube |
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 rpc greet.proto # -> a moonrpc project tree (pb/, internal/…, client/)
$ mctl gen rpc greet.proto --flat # -> greet_grpc.mbt (messages, codecs, handler tables)
$ mctl gen gql greet.api # -> a moongql project tree (internal/schema, …)
$ 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 gql new books # scaffold a moongql service project under books/
$ 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 # moonapi on mooncat
$ mctl rpc new echo # moonrpc, with a typed client
$ mctl gql new books # moongql on mooncat
$ mctl model new account # a moonorm data layer
$ mctl docker greet # Dockerfile (two-stage native build) + .dockerignore
$ mctl kube greet # deploy/deployment.yaml + deploy/service.yaml| Path | api | rpc | gql |
|---|---|---|---|
| <name>.mbt | serve the app on mooncat | serve the handler tables on GrpcServer | serve graphql_app on mooncat |
| etc/, internal/config, internal/svc | ✓ | ✓ | ✓ |
| generated | internal/types, internal/handler/routes.mbt | pb/, internal/server, client/<name>client.mbt | internal/schema |
| the author's | internal/handler/*, internal/logic/* | internal/logic/* | internal/logic/resolvers.mbt |
| test | the routes through TestClient | Ping over a real socket | a { ping } query |
let files = @moonctl.scaffold_rpc("echo") // -> Array[GenFile] to write
let tree = @moonctl.generate_rpc_tree(proto) // -> Array[TreeFile], each marked Always or Oncelet (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_withfn 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; }let port : Intfn 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).