go-chainer-gen transforms independent methods into structured execution chains. By using semantic analysis of your
function signatures, it automates variable handoffs, error propagation, and context management - turning discrete logic
units into a clean, idiomatic execute function.
- Smart Variable Handoff: Automatically maps return values from one step to the input parameters of subsequent steps.
- Zero-Value Returns: Automatically resolves and returns the correct zero-values for complex types during error propagation.
- Pkl-Powered: Uses pkl-lang for type-safe, programmable configuration of your generation rules.
go-chainer-gen can be used as a standalone CLI tool or integrated into your own custom generator as a library. This is
a component of my code generation toolbox.
When running as a standalone generator, go-chainer-gen requires the pkl binary to be available on your PATH. It is
used at runtime to evaluate .pkl configuration files. You can install it via:
# macOS
brew install pklFor other platforms, see pkl-lang installation docs.
First let's set up a golang module
module github.com/gen/project
go 1.24using the minimum configuration
// file: chainer.pkl
amends "package://nhatp.com/go/chainer-gen/pkl@0.3.0#/Config.pkl"
import "package://nhatp.com/go/chainer-gen/pkl@0.3.0#/match.pkl"
packages {
["github.com/gen/project"] {
struct_name = match.with("createUserOp")
}
}source code
// file: main.go
package main
import "context"
type CreateUserInput struct{}
type createUserOp struct {
}
func (op *createUserOp) validate(ctx context.Context, input CreateUserInput) error {
return nil
}
func (op *createUserOp) authorize(ctx context.Context, input CreateUserInput) error {
return nil
}
func (op *createUserOp) handle(ctx context.Context, input CreateUserInput) error {
return nil
}run command in your terminal
go run nhatp.com/go/chainer-gen/cmd/go-chainer-gen generateexpected generated code
// golden-file: gen_chainer.go
// Code generated by go-chainer-gen - dev. DO NOT EDIT.
package main
import (
"context"
"fmt"
)
// execute runs createUserOp.validate, createUserOp.authorize, and
// createUserOp.handle in sequence.
func (op *createUserOp) execute(ctx context.Context, input CreateUserInput) error {
v0 := op.validate(ctx, input)
if v0 != nil {
return fmt.Errorf("validate: %w", v0)
}
v1 := op.authorize(ctx, input)
if v1 != nil {
return fmt.Errorf("authorize: %w", v1)
}
v2 := op.handle(ctx, input)
if v2 != nil {
return fmt.Errorf("handle: %w", v2)
}
return nil
}Example code
package yourgenerator
import (
"fmt"
"nhatp.com/go/chainer-gen"
)
func UseAsLibrary(sourceDir string) {
// fileManager instance can be shared between commands in the toolbox
fileManager := chainergen.NewFileManager(sourceDir, chainergen.WithBinaryName("your-generator"))
var configs []chainergen.Config // build your configs here ...
generator := chainergen.New(fileManager)
pkgs, err := chainergen.LoadPackages(sourceDir)
if err != nil {
panic(err)
}
for _, pkg := range pkgs {
if err := generator.Generate(pkg, configs); err != nil {
panic(err)
}
}
fmt.Println(fileManager.Files())
}Explore the features directory to see how go-chainer-gen handles various real-world scenarios:
- Data handoff, unused variables, and zero-value returns
- Customizing method names and execution order
- Configuring multiple structs with distinct options
PRs are welcome! See the CONTRIBUTING. Distributed under the Apache License 2.0.
If you like the project, feel free to buy me a coffee. Thank you!