Guide

Documentation

Metago runs before the Go compiler. You annotate ordinary Go declarations, render reusable Go templates, and get ordinary, formatted Go back. There is no runtime framework and no reflection requirement.

Metago is early-stage software. APIs and directives may evolve.

Your first generator

Create stringer.metago anywhere beneath the directory you plan to scan. By default you can just run metago and it will scan all your project recursively for go templates.

{{ define "stringer" }}
func (v {{ name . }}) String() string {
    switch v {
    {{- range .Values }}
    case {{ .Name }}:
        return {{ quote .Name }}
    {{- end }}
    default:
        return "unknown"
    }
}
{{ end }}

Attach it to a Go type:

package example

//mgo:gen stringer
type Status int

const (
    StatusPending Status = iota
    StatusRunning
    StatusDone
)

Run Metago from the project root:

metago .

Metago finds the template and directive, resolves Status, and writes meta.go beside the source:

// Code generated by metago; DO NOT EDIT.

package example

func (v Status) String() string {
    switch v {
    case StatusPending:
        return "StatusPending"
    case StatusRunning:
        return "StatusRunning"
    case StatusDone:
        return "StatusDone"
    default:
        return "unknown"
    }
}

Commit generated code if that fits your project. It is normal Go: inspect it, test it, and compile it with the usual toolchain.

The workflow

A Metago generator has three parts:

  1. A //mgo:gen or //mgo:inline directive selects a template.
  2. Metago resolves the directive’s target and exposes its package, type, field, method, value, and property metadata.
  3. A named text/template definition renders Go source.

Template files may live anywhere under the scan root. Hidden directories, vendor, and testdata are skipped. Template names are global to the scan, so every {{ define "name" }} must be unique. Names beginning with std. belong to Metago’s embedded standard templates.

Target declarations

The clearest form puts a directive in the declaration’s doc comment. Metago infers the target:

//mgo:gen validator
type User struct {
    Name string
}

//mgo:gen trace
func LoadUser(id string) (User, error) { /* ... */ }

//mgo:gen observe
func (s *Store) Save(user User) error { /* ... */ }

You can also place a directive separately and name a local or imported target explicitly:

type User struct{ Name string }

//mgo:gen validator User

Methods use Type.Method; imported targets use a package qualifier or import path:

//mgo:gen wrapper Server.Serve
//mgo:gen wrapper http.Client.Do
//mgo:gen wrapper net/http.Client.Do

Types, methods, functions, package-level constants, and package-level variables can all be targets. A directive attached to the package declaration creates a package-scoped invocation:

//mgo:gen std.serde.jsonruntime
package jsonruntime

Sidecar or inline output

Use //mgo:gen for a generated sidecar. Ordinary source directives write meta.go; internal tests write meta_test.go; external test packages write meta_<package>_test.go.

Use //mgo:inline when generated code belongs next to its declaration:

//mgo:inline stringer
type Status string

Metago inserts and later replaces a managed region:

//mgo:inline stringer
type Status string

func (s Status) String() string { return string(s) }

//mgo:end

Never edit inside that region. Inline templates can register imports; Metago updates the source import block.

Generation is atomic across the scan root. Metago prepares every package first and changes no files if scanning, target resolution, template execution, or generation fails. Successful runs remove stale Metago-owned sidecars while preserving similarly named files that do not carry Metago’s exact generated header.

Pass arguments

Everything after a template and explicit target is either a positional argument, a bare flag, or key=value:

//mgo:gen endpoint /users/{userID} auth=required cache
func GetUser() {}

Inside the template, read them with .Argv, .Args, or arg:

path: {{ arg 0 }}
auth: {{ default "public" (arg "auth") }}

A token beginning with / or containing { is always treated as a positional argument rather than a target.

Project-wide named defaults live in metago.toml at the exact scan root:

[templates."std.serde".args]
runtime = "example.com/project/internal/jsonruntime"
strict = "true"

Directive-local key=value arguments override configured defaults.

Attach properties

A custom directive namespace attaches metadata without rendering code:

//mgo:api owner=identity
//mgo:gen schema
type User struct {
    //mgo:validate required max=100
    Name string `json:"name"`

    Age int `json:"age"` //mgo:validate min=0 max=150
}

Read properties in a template:

{{ range .Fields }}
{{ if propHas . "validate" "required" }}
// {{ .Name }} is required
{{ end }}
{{ with prop . "validate" "max" }}
// maximum: {{ . }}
{{ end }}
{{ end }}

Properties must be attached to a declaration. Repeating a namespace merges flags and named values; later named values win. Put generation lines before property lines when stacking directives in one comment block.

Work with metadata

The invocation itself is the template’s dot value. Common fields include:

{{ .Package.Name }}  {{/* current package */}}
{{ .Name }}          {{/* target name */}}
{{ .Kind }}          {{/* struct, method, function, const, ... */}}
{{ .Fields }}        {{/* target struct fields */}}
{{ .Methods }}       {{/* target methods */}}
{{ .Values }}        {{/* typed constants for the target type */}}

Helpers keep templates focused on generated code:

{{ define "json-fields" }}
{{ imports "encoding/json" }}

{{ range exportedFields . }}
// {{ name . }}: {{ typeof . }}{{ tagName . "json" }}
{{ end }}
{{ end }}

Use fail to reject an unsupported invocation. Metago discards that invocation’s output and imports, continues collecting failures, and writes nothing if any invocation fails:

{{ if not (isInt .) }}
    {{ fail "requires an integer-backed type" }}
{{ end }}

The reference lists every metadata field and helper.

Aggregate a package

Every generation annotation is available through .Package.Metas in deterministic file and line order. This lets one invocation build a registry from many marker annotations:

{{ define "routes" }}
func Routes() []string {
    return []string{
    {{- range .Package.Metas }}
    {{- if eq .Template "route" }}
        {{ quote (index .Argv 0) }},
    {{- end }}
    {{- end }}
    }
}
{{ end }}

{{ define "route" }}{{ end }}
//mgo:gen routes
package api

//mgo:gen route /users
func Users() {}

//mgo:gen route /teams
func Teams() {}

Empty templates are valid markers. Property annotations do not appear in .Package.Metas because they live on their attached symbols.

Reuse standard templates

Metago embeds generators for common jobs:

TemplateGenerates
std.stringerString() for a primitive-backed type.
std.enumString, parse, validation, values, and JSON behavior for enums.
std.mockFunction-field mocks for interfaces.
std.mapstructMap decoding and encoding for structs.
std.serde.jsonruntimeA project-owned JSON runtime.
std.serdeReflection-free JSON codecs.

They require no copied .metago files. See examples for complete usage and the reference for supported arguments and behavior.

Run in a project

The command accepts one optional scan root:

metago              # current directory
metago ./path       # another root
metago -v           # verbose output
metago --verbose

With Go 1.24 or later, pin Metago as a project tool:

go get -tool github.com/guillemus/metago@latest

Call it from a single go:generate directive because Metago scans the supplied root recursively:

//go:generate go tool metago .

Then use the normal Go workflow:

go generate ./...
go test ./...

Contribute

Run the complete test and analysis suite from the repository root:

go test ./...
staticcheck ./...

Golden fixtures live under testdata/. Update them only when intentionally accepting generated-output changes:

UPDATE_GOLDEN=1 go test ./...

Install the documentation dependencies once with npm install --prefix docs, then preview this site at localhost:3000:

mise run docs

Build the production site with:

hugo --source docs --destination ../dist --minify

Where to go next

  • Use the reference when writing a template or looking up exact behavior.
  • Copy a complete pattern from examples.
  • Read the source or report an issue on GitHub.