Guide

Documentation

Metago generates formatted Go source from directives and reusable templates. Compile and test generated files with the normal Go toolchain.

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

Getting started

Install Metago with Go:

go install github.com/guillemus/metago@latest

Ready-to-use binaries are also available on GitHub Releases.

From the project root, run metago. This is the normal invocation: Metago scans the current directory recursively and automatically finds every .metago template under it.

metago              # current directory and all its templates
metago ./path       # another root and all its templates
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

Add one go:generate directive at the project root:

//go:generate go tool metago .

Metago scans that root recursively. Then use the normal Go workflow:

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

Your first generator

Create stringer.metago anywhere beneath the directory you plan to scan.

{{ 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.

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. Place declaration documentation before Metago directives:

// User represents an account.
//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. If any package fails, Metago changes no files. Successful runs remove stale Metago-generated sidecars and preserve other files.

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.

metago.toml configures default named arguments for your templates and must live at the project root:

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

Explicit arguments on //mgo:gen and //mgo:inline 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:

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

Metago reports all template failures and changes no generated files if any invocation fails.

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() {}

The empty route template produces no code. Each //mgo:gen route ... directive still appears in .Package.Metas, where routes collects it into the package registry.

.Package.Metas contains generation directives only. Read property annotations from their attached symbols with prop, props, propHas, or propExists.

Reuse standard templates

Metago embeds generators for common jobs:

TemplateGenerates
std.stringerString() for a primitive-backed enum or ordinary value type; types without constants return their underlying text directly.
std.enumString, parse, validation, values, and JSON behavior for enums.
std.mockFunction-field mocks for interfaces.
std.serde.jsonruntimeThe shared runtime used by std.serde.
std.serdeReflection-free JSON codecs.

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

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.