qudy
qudy is a Go library and command for writing Go code generators. Keep loops,
conditions, and helper calls in Go, and write the source you want to produce in
comments next to that logic. qudy compiles those comments into calls to a
printf-style function.
If you have written a generator full of escaped strings, the difference looks
like this. Given names := []string{"Red", "Green", "Blue"}, a template can say:
for _, name := range names {
//`case ~name:
//` return "~name"
}
Running the resulting generator writes:
case Red:
return "Red"
case Green:
return "Green"
case Blue:
return "Blue"
The loop is ordinary Go. The comments describe the output, and ~name inserts
the current value.
Use it when your generator mixes Go logic with repetitive source code: methods
for a set of types, handlers for a list of routes, or boilerplate derived from a
schema. The template keeps the output visible beside the logic that produces it,
without a separate language for loops and expressions.
There are two steps: qudy compiles your template into a Go generator; you run
that generator to write the final source. You supply the data and the logic
that reads it. The generator contains ordinary Go calls and needs no qudy
runtime dependency.

The example below takes an enum's constant names as arguments. After that, read
about interpolation, integration with existing generators, and writing several
files.
A stringer in one file
Suppose you want fmt.Sprint(Green) to print Green rather than 1. In an
existing Go module, save this enum as color.go:
// color.go
package colors
type Color int
const (
Red Color = iota
Green
Blue
)
The template below generates its String method. It takes the package, type,
and constant names from the command line; it does not discover them in Go
source. Create a stringer directory and save this as stringer/stringer.go:
//go:build qudy
package main
import "os"
func main() {
pkg, typ, names := os.Args[1], os.Args[2], os.Args[3:]
//`package ~pkg
//`
//`import "fmt"
//`
//`func (v# ~typ) String() string {
//` switch v# {
for _, name := range names {
//` case ~name:
//` return "~name"
}
//` }
//` return fmt.Sprintf("~typ(%d)", v#)
//`}
}
The Go code runs in the generator. Each comment that starts with a backtick
writes an output line, ~name inserts a Go value, and the loop writes one
case for each constant. v# writes a generated name, something like v_qd1,
to reduce the chance of clashing with names in the generated code. See
generated names for the limits.
See examples/stringer for a slightly longer version.
Run the example
Install the command with Go 1.25 or later:
go install github.com/bilus/qudy/cmd/qudy@latest
First, compile the template into a Go program:
qudy -o stringer/stringer_gen.go stringer/stringer.go
Then run that program to generate the method:
go run ./stringer colors Color Red Green Blue > color_string_gen.go
color_string_gen.go now holds:
package colors
import "fmt"
func (v_qd1 Color) String() string {
switch v_qd1 {
case Red:
return "Red"
case Green:
return "Green"
case Blue:
return "Blue"
}
return fmt.Sprintf("Color(%d)", v_qd1)
}
Run go test ./... to check that the package builds with its new method.
How the template becomes a generator
qudy turns the template's two output lines inside the loop into a call
equivalent to:
fmt.Printf("\tcase %v:\n\t\treturn \"%v\"\n", name, name)
The -emit flag chooses that function. It defaults to fmt.Printf. and qudy
adds the fmt import to the generator when the template lacks it. You can use
an existing generator's method to write to a buffer or file. qudy combines a
run of consecutive output lines into one call, uses %v for inserted values,
and escapes literal percent signs, which is why the template can say %d.
Your emit function must accept a format string and its arguments and interpret
them like fmt.Printf.
A template parses as Go, so gofmt can format it and your editor can read it.
It may not build as Go: variables used only in output lines look unused to the
Go compiler. So start a template with //go:build qudy, and a normal build
ignores it. qudy writes //go:build !qudy into the generator, so the two never
build together. Name generated Go files with the _gen.go suffix. For a
compiled template, replace .go with _gen.go: stringer.go becomes
stringer_gen.go. The CLI writes to the path supplied with -o.
To get completion and navigation inside a template, give your editor the tag,
for example -tags=qudy in the buildFlags of gopls. It then loads the
templates in place of the generators, and it reports a name that only output
lines use as unused.
The quick start runs qudy by hand. To run it with go generate, see
examples/stringer.
Insert values into the output
Output lines combine literal text, values inserted with ~, and names generated
with #:

~name is an interpolation: qudy inserts the value of name there.
For example, inside a template function:
foo := "XXX"
count := 3
//`~foo
//`~count
Produces:
XXX
3
qudy formats each inserted value with %v, so numbers need no conversion.
You can also capture a selector:
p := struct{ Name string }{Name: "Color"}
//`type ~p.Name int
Produces:
type Color int
Without braces, ~ captures the next Go identifier and any selectors that
follow it, as in ~p.Name. Each identifier must use only ASCII letters, digits,
and underscores, and cannot start with a digit.
Use a braced interpolation, ~{expr}, to capture a call, index, or other Go
expression. This example uses strconv.Quote from the standard library:
label := "hello"
values := []int{10, 20}
//`var label = ~{strconv.Quote(label)}
//`var first = ~{values[0]}
Produces:
var label = "hello"
var first = 10
Without braces, capture stops before a bracket or parenthesis. Compare:
name := "items"
//`~name[0]
//`~{name[0]}
Produces:
items[0]
105
~name[0] captures only name, inserting its value followed by the literal
text [0]. ~{name[0]} evaluates the index in the generator: the first byte of
"items" is 'i', whose numeric value is 105. This distinction lets you write
indexing and calls in the generated code as well as evaluate them in the
generator.
An output line must be a comment on its own line inside a function body.
Both //`text and the spelling that gofmt produces, // `text, work.
qudy copies the template's other Go code into the generator, with three
exceptions: it negates the qudy build tag, it leaves out go:generate lines,
and it handles gensyms as described below. It formats the generator with
go/format.
Each output line writes a trailing newline. End it with a backslash to continue
on the same line, for example when a loop writes an argument list:
//`handle(ctx\
for _, arg := range args {
//`, ~arg\
}
//`)
Two trailing backslashes write one backslash and keep the newline.
To put a comment in the generated code, include it in an output line:
//`// Event represents a change to a user.
To annotate only the template, use an ordinary Go comment or ~// inside an
output line. qudy drops ~//, the rest of that line, and the spaces before it:
//`} ~// close the generated handler
Write ~~ for a literal tilde and ## for a literal hash. Reserve comments that
open with a backtick for output lines; qudy rejects them outside function bodies.
Generate names with fewer collisions
A generator often needs its own local names in the code it produces. Write
name# to request a name with a numbered suffix, often called a gensym:
//`value, errName# := parse(input)
//`if errName# != nil {
//` return errName#
//`}
The first use of errName# in a block declares a variable in the generator,
equivalent to errName_qd := GENSYM("errName"). Later uses in that block insert
the same name.
errName# never collides with a variable of the template. qudy keeps the
gensym in a generator variable of its own, errName_qd, so errName# and
~errName can stand in one output line and mean different things. For the same
reason a template may not declare a name that ends in _qd.
name# belongs in output lines. When the template's Go code needs the gensym,
call GENSYM, as shown below.
qudy supplies the symbol generator. It declares qudyGensym, a small function
value, at the start of each function that uses a gensym, so the generator stays
one self-contained file. Each gensym gets a numbered suffix, so errName# comes
out as errName_qd1, which is unlikely to collide with a name in your code.
The default generator does not inspect the output's scope or reserve existing
identifiers. A user-defined errName_qd1 can still collide with its result.
If your generator knows which names are taken, supply a custom symbol generator
that checks them.
To choose the names yourself, declare qudyGensym in the template, as a local
variable, a parameter, or a package-level variable. It is a
func(want string) string. qudy then uses yours and declares none:
qudyGensym := newSeededGensym(taken)
A function literal shares the symbol generator of the function around it.
Call GENSYM(want) directly inside a function body when you compute the desired
name or need to declare the variable yourself:
local := GENSYM(param.Name)
//`var ~local ~param.Type.Text
qudy rewrites GENSYM to qudyGensym.
Generate several files
Have your generator print a txtar archive: a line such as -- color_string_gen.go --
starts a new file. Inside a template function, file markers are ordinary output
lines:
//`-- color_string_gen.go --
//`package colors
//`// String methods go here.
//`-- color_parse_gen.go --
//`package colors
//`// Parse functions go here.
Pipe the generator's output into qudy -txtar -o ./generated. qudy splits the
archive into files under that directory and formats the .go files with
go/format. For a complete example, run this from the qudy repository:
go run ./examples/enums -package colors Color=Red,Green,Blue | qudy -txtar -o ./generated
See examples/enums for a generator that writes both String
methods and Parse functions. The generated methods expect the enum types and
constants to be defined in the same package.
Use qudy as a library
Call qudy.Compile when you want to compile templates from your own Go program:
generator, err := qudy.Compile("stringer/stringer.go", src, "fmt.Printf")
Import github.com/bilus/qudy and pass the template as src []byte.
Compile returns the generator's formatted Go source and an error. It does not
run the generator.
Further examples
- stringer: a complete enum generator and
go generate integration.
- enums: several output files from one template.
- sumtype: a larger generator that reads Go source.
The name stands for “quick and dirty quasiquoting”: quote the code you want to
produce, and insert Go values where it varies.