snippet

package
v0.3.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 14 Imported by: 4

Documentation

Overview

Package snippet provides a flexible and composable AST-aware code generation framework.

Unlike simple string concatenation, this package is designed to work closely with Go's syntax tree (AST) and type system. It automatically handles complex generation tasks like tracking package imports, resolving type identifiers, and safely exposing external objects.

The core concept is the `Snippet` interface, which represents a fragment of code that can be generated dynamically. Snippets can be composed, nested, and evaluated lazily.

Core Concepts

  • Snippet: The fundamental interface representing a piece of code. It provides `Fragments` to yield strings iteratively, and `IsNil` to check if it's empty.
  • Composition: Snippets can be combined using `Compose`, `Snippets` (with separators), or `Strings`.

Available Snippet Types

  1. Basic Blocks (`block.go`): - `Block(string)`: A raw string block. - `BlockF(format, args...)`: A formatted string block. - `BlockRaw(string)`: A string block that is automatically quoted.

  2. Identifiers (`ident.go`): Used to generate Go type identifiers, automatically handling imports. - `IdentFor[T]()`: Generates the identifier for a generic type `T`. - `IdentOf(v)`: Generates the identifier for the type of value `v`. - `Ident(...)`: Generates identifiers from typx, reflect, or types.

  3. Values (`value.go`): - `Value(v)`: Generates the Go code representation of a value (e.g., structs, maps, slices, primitives).

  4. Code Exposing (`expose.go`): Used to reference exported objects (functions, variables, constants, types) from other packages. - `Expose(path, name, targs...)`: References an object by path and name. - `ExposeObject(types.Object, targs...)`: References a `types.Object`.

  5. Templates (`template.go`): A text-based template engine that replaces macros (e.g., `#MacroName#`) with other Snippets. - `Template(io.Reader, args...)`: Parses a template file. - `Arg(name, snippet)`: Defines arguments for the template.

  6. Comments & Directives (`comment.go`): - `Document(name, lines...)`: Generates a standard GoDoc comment block. - `InlineComment(line)`: Generates an inline comment. - `Directive(name, args...)`: Generates a Go compiler directive.

  7. Imports (`imports.go`): - `Imports()`: Automatically collects and formats all package imports tracked during snippet evaluation.

  8. Utilities (`separator.go`, `poster.go`): - `Indent(n)` / `NewLine(n)`: Generates whitespace separators. - `Poster(pkg, gen, ver)`: Generates the `// Code generated` header.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func IsNil added in v0.1.1

func IsNil(s Snippet) bool

IsNil checks if a Snippet is nil or its underlying implementation reports as nil.

Types

type Placeholder added in v0.1.1

type Placeholder struct{}

Placeholder must be not nil and yield an empty string

func (Placeholder) Fragments added in v0.1.1

func (Placeholder) Fragments(ctx context.Context) iter.Seq[string]

func (Placeholder) IsNil added in v0.1.1

func (Placeholder) IsNil() bool

type Snippet

type Snippet interface {
	// IsNil returns true if the snippet has no content to generate.
	IsNil() bool
	// Fragments yields a sequence of string fragments that make up the snippet.
	Fragments(ctx context.Context) iter.Seq[string]
}

Snippet represents a reusable code fragment generator.

func Block

func Block(v string) Snippet

Block creates a Snippet from a raw string block.

Example
Print(
	context.Background(),
	Document("F", "this is a document for F."),
	NewLine(1),
	Block("func F() string {"),
	NewLine(1),
	Indent(1), BlockF("x := "), BlockRaw("abc"),
	NewLine(1),
	Indent(1), Block("return x"),
	NewLine(1),
	Block("}"), NewLine(1),
)
Output:
// F this is a document for F.
func F() string {
	x := "abc"
	return x
}

func BlockF

func BlockF(v string, args ...any) Snippet

BlockF creates a Snippet from a formatted string block.

func BlockRaw

func BlockRaw(v string) Snippet

BlockRaw creates a Snippet from a string, quoting it automatically.

func Comments

func Comments(lines ...string) Snippet

Comments creates a Snippet for a block of comments. It cleans up existing comment markers (//, /*, */) and trims whitespace.

Example
Print(
	context.Background(),
	Snippets(
		NewLine(1),
		Document("TypeName", "this is a document for TypeName.", " line1\n line2\n line3"),
		Comments("\n", ""), // empty skipped
		InlineComment("this is an inline comment"),
		Directive("generate", "arg1", "\n", "arg2", "\t", "--flag"),
	),
)
Output:
// TypeName this is a document for TypeName.
// line1
// line2
// line3
// this is an inline comment
//go:generate arg1 arg2 --flag

func Compose

func Compose(ss ...Snippet) Snippet

Compose combines multiple Snippets sequentially without any separator.

func Directive

func Directive(directive string, args ...string) Snippet

Directive creates a Snippet for a Go compiler directive (e.g., //go:directive args).

func Document

func Document(name string, lines ...string) Snippet

Document creates a Snippet for a documentation comment block. If name is provided, it prefixes the first line with the name.

func Expose

func Expose(ctx context.Context, path string, name string, targs ...Snippet) Snippet

Expose create an exposer in some package, it may be a types.Type or a types.Object here the name MUST BE exported case 1: a named/alias type. this case should handled by Ident eg: path/to/package.NamedType[TypeArguments...] case 2: an exported object. the package level MUST be a *types.Func, *types.Const or *types.Var eg: errors.New the underlying is a function: func() error, but we need the object

Example
ctx := dumper.WithEntry(context.Background(), "github.com/xoctopus/genx/testdata")

pkg := dumper.Load(ctx, "github.com/xoctopus/genx/testdata")

t1 := pkg.Types.Scope().Lookup("T").Type().(*types.Named)
t2 := pkg.Types.Scope().Lookup("TT").Type().(*types.Named)
t3 := pkg.Types.Scope().Lookup("DemoEnum").Type().(*types.Named)

body := Snippets(
	NewLine(1),
	// imported: project
	Expose(ctx, "github.com/xoctopus/genx/testdata", "FuncT", IdentFor[string](ctx)),
	ExposeUnsafe(ctx, "github.com/xoctopus/genx/testdata", "FuncT"),
	Expose(ctx, "github.com/xoctopus/genx/testdata", "DEMO_ENUM_A"),
	Expose(ctx, "github.com/xoctopus/genx/testdata", "Var"),
	Expose(ctx, "github.com/xoctopus/genx/testdata", "VarFuncT"),
	ExposeObject(ctx, t1.Obj(), IdentFor[int](ctx)),
	ExposeObject(ctx, t2.Obj(), IdentFor[int](ctx), IdentFor[string](ctx)),
	ExposeObjectUnsafe(ctx, t3.Obj()),
	// imported: std
	Expose(ctx, "fmt", "Sscanf"),
	Expose(ctx, "io", "ReadAll"),
	// imported: general
	Expose(ctx, "errors", "New"),
	Expose(ctx, "github.com/xoctopus/x/ptrx", "Ptr", IdentFor[testdata.DemoEnum](ctx)),
	// ExposeByID(ctx, "github.com/xoctopus/genx/testdata.TT[int,github.com/xoctopus/genx/testdata.T[float32]]"),
	// TT[int, T[float32]]
)

Print(
	ctx,
	Poster("demo", "genx:test_expose", "devel"),
	Imports(ctx),
	body,
)
Output:
// Code generated by genx:test_expose@devel DO NOT EDIT.
package demo

import (
	"errors"
	"fmt"
	"io"

	"github.com/xoctopus/x/ptrx"
)

FuncT[string]
FuncT
DEMO_ENUM_A
Var
VarFuncT
T[int]
TT[int, string]
DemoEnum
fmt.Sscanf
io.ReadAll
errors.New
ptrx.Ptr[DemoEnum]

func ExposeObject

func ExposeObject(ctx context.Context, o types.Object, targs ...Snippet) Snippet

ExposeObject creates an exposer Snippet from a types.Object, validating the package and type arguments.

func ExposeObjectUnsafe added in v0.1.9

func ExposeObjectUnsafe(ctx context.Context, o types.Object) Snippet

ExposeObjectUnsafe creates an exposer Snippet from a types.Object without validating the package or type arguments.

func ExposeUnsafe added in v0.1.6

func ExposeUnsafe(ctx context.Context, path, name string) Snippet

ExposeUnsafe like Expose but skip validation of package and type arguments

func Ident

func Ident(ctx context.Context, t typx.Type) Snippet

Ident returns a Snippet representing the identifier for the given typx.Type.

func IdentFor

func IdentFor[T any](ctx context.Context) Snippet

IdentFor returns a Snippet representing the identifier for the generic type T.

Example
ctx := dumper.WithEntry(context.Background(), "github.com/xoctopus/genx/testdata")

body := Snippets(
	NewLine(1),
	IdentFor[string](ctx),
	IdentOf(ctx, bytes.Reader{}),
	IdentFor[bytes.Reader](ctx),
	IdentFor[*bytes.Buffer](ctx),
	IdentFor[io.Reader](ctx),
	IdentFor[dumper.Import](ctx),
	IdentRT(ctx, reflect.TypeFor[float64]()),
	IdentTT(ctx, types.Typ[types.Int32]),
)

Print(
	ctx,
	Poster("demo", "genx:test_ident", "devel"),
	Imports(ctx),
	body,
)
Output:
// Code generated by genx:test_ident@devel DO NOT EDIT.
package demo

import (
	"bytes"
	"io"

	"github.com/xoctopus/genx/internal/dumper"
)

string
bytes.Reader
bytes.Reader
*bytes.Buffer
io.Reader
dumper.Import
float64
int32

func IdentOf

func IdentOf(ctx context.Context, v any) Snippet

IdentOf returns a Snippet representing the identifier for the type of the given value v.

func IdentRT

func IdentRT(ctx context.Context, t reflect.Type) Snippet

IdentRT returns a Snippet representing the identifier for the given reflect.Type.

func IdentTT

func IdentTT(ctx context.Context, t types.Type) Snippet

IdentTT returns a Snippet representing the identifier for the given types.Type.

func Imports

func Imports(ctx context.Context) Snippet

Imports returns a Snippet that generates the Go import declaration block. It categorizes imports into standard library, project-local, and general third-party modules.

func Indent

func Indent(n int) Snippet

Indent creates a Snippet that generates n tab characters.

func InlineComment

func InlineComment(line string) Snippet

InlineComment creates a Snippet for an inline comment.

func NewLine

func NewLine(n int) Snippet

NewLine creates a Snippet that generates n newline characters.

func Poster

func Poster(pkg string, generator, version string) Snippet

Poster creates a Snippet that generates the standard "Code generated" header and package declaration.

func Snippets

func Snippets(sep Snippet, ss ...Snippet) Snippet

Snippets joins multiple Snippets together, separated by the given sep Snippet.

Example (Eof)
Print(context.Background(), Compose(Block(eof)))
Print(context.Background(), &Placeholder{})

func Strings added in v0.1.1

func Strings(tail string, sep string, list ...string) Snippet

Strings creates a Snippet from a list of strings. Each string is appended with the specified tail, and the resulting blocks are joined together using the given separator.

Example
Print(context.Background(), Strings(",", "\n", "1", "2", "3", "4"))
Print(context.Background(), Strings(",", "\n"))
Output:
"1",
"2",
"3",
"4",

func Template

func Template(r io.Reader, args ...*TArg) Snippet

Template parses a text template from the reader and returns a Snippet. It replaces macros in the format `#name#` with the corresponding TArg snippets.

Example
ctx := dumper.WithEntry(
	context.Background(),
	"github.com/xoctopus/genx/testdata",
)

tpl := Template(
	bytes.NewReader(template),
	ArgFor[testdata.Gender](ctx, "Type"),
	ArgExpose(ctx, "fmt", "Sprintf"),
	Arg(ctx, "NameToValueCases", Snippets(
		NewLine(1),
		Compose(Indent(1), Block(`case "MALE":`)),
		Compose(Indent(2), Block(`return GENDER__MALE, nil`)),
		Compose(Indent(1), Block(`case "FEMALE":`)),
		Compose(Indent(2), Block(`return GENDER__FEMALE, nil`)),
	)),
	ArgExpose(ctx, "github.com/xoctopus/genx/testdata", "GENDER_UNKNOWN").
		WithName("UnknownValue"),
	ArgExpose(ctx, "errors", "New"),
	Arg(ctx, "Values", Snippets(
		NewLine(1),
		Compose(Indent(2), Expose(ctx, "github.com/xoctopus/genx/testdata", "GENDER__MALE"), Block(",")),
		Compose(Indent(2), Expose(ctx, "github.com/xoctopus/genx/testdata", "GENDER__FEMALE"), Block(",")),
	)),
	ArgExpose(ctx, "github.com/xoctopus/genx/testdata", "Scan").WithName("EnumScanBrick"),
	ArgExposeUnsafe(ctx, "bytes", "ToUpper"),
	ArgExpose(ctx, "fmt", "Sscanf"),
	ArgT[driver.Value](ctx),
	Arg(ctx, "ValueToDescCases", Snippets(
		NewLine(1),
		Compose(Indent(1), Block(`case GENDER__MALE:`)),
		Compose(Indent(2), Block(`return "男"`)),
		Compose(Indent(1), Block(`case GENDER__FEMALE:`)),
		Compose(Indent(2), Block(`return "女"`)),
	)),
	Arg(ctx, "ValueToNameCases", Snippets(
		NewLine(1),
		Compose(Indent(1), Block(`case GENDER__MALE:`)),
		Compose(Indent(2), Block(`return "MALE"`)),
		Compose(Indent(1), Block("case GENDER__FEMALE:")),
		Compose(Indent(2), Block(`return "FEMALE"`)),
	)),
	nil,
	Arg(ctx, "Nil", nil),
)

b := bytes.NewBuffer(nil)

Write(
	ctx,
	b,
	Poster("testdata", "genx:test_template", "devel"),
	Imports(ctx),
	tpl,
)

filename := filepath.Join(must.NoErrorV(os.Getwd()), "..", "..", "testdata", "gender_genx_tpl_test_enum.go")
must.NoError(os.RemoveAll(filename))
output, _ := os.OpenFile(
	filename,
	os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
	0o644,
)
_, _ = io.Copy(output, b)
_ = output.Sync()
_ = output.Close()

func Value

func Value(ctx context.Context, x any) Snippet

Value returns a Snippet that generates the Go code representation of the given value.

Example
ctx := dumper.WithEntry(
	context.Background(),
	"github.com/xoctopus/genx/pkg/snippet_test",
)

body := Snippets(
	NewLine(1),
	Value(ctx, 1),
	Value(ctx, int8(1)),
	Value(ctx, uintptr(100)),
	Value(ctx, 1.1),
	Value(ctx, float32(1.1)),
	Value(ctx, "10"),
	Value(ctx, testdata.String("10")),
	Value(ctx, func() {}),
	Value(ctx, testdata.Gender(1)),
	Value(ctx, testdata.Struct{
		A: 100,
		B: &testdata.Struct{A: 101},
	}),
	Value(ctx, struct {
		X int `json:"x,omitzero"`
	}{0}),
	Value(ctx, map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}),
	Value(ctx, map[any]int{
		nil:                  0,
		(*int)(nil):          1,
		"1":                  2,
		"3":                  3,
		"5":                  4,
		testdata.String("2"): 5,
		testdata.String("4"): 6,
		testdata.String("6"): 7,
	}),
	Value(ctx, []map[any]any{{
		testdata.String("0"):      3, // snippet_test.String  3
		"1":                       2, // string               2
		2:                         0, // int                  0
		new(testdata.String("3")): 1, // pointer              1
	}}),
	Value(ctx, []any{nil, true, false, complex64(1), complex128(1)}),
	Value(ctx, nil),
)

Print(
	ctx,
	Poster("demo", "genx:test_value", "devel"),
	Imports(ctx),
	body,
)
Output:
// Code generated by genx:test_value@devel DO NOT EDIT.
package demo

import (
	"github.com/xoctopus/x/ptrx"

	"github.com/xoctopus/genx/testdata"
)

1
1
100
1.1
1.1
"10"
testdata.String("10")
testdata.Gender(1)
testdata.Struct{A: 100, B: ptrx.Ptr[testdata.Struct](testdata.Struct{A: 101, B: nil})}
struct { X int "json:\"x,omitzero\"" }{X: 0}
map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
map[interface {}]int{nil: 0, nil: 1, "1": 2, "3": 3, "5": 4, testdata.String("2"): 5, testdata.String("4"): 6, testdata.String("6"): 7}
[]map[interface {}]interface {}{map[interface {}]interface {}{2: 0, ptrx.Ptr[testdata.String](testdata.String("3")): 1, "1": 2, testdata.String("0"): 3}}
[]interface {}{nil, true, false, (1+0i), (1+0i)}
nil

type TArg

type TArg struct {
	// contains filtered or unexported fields
}

func Arg

func Arg(ctx context.Context, name string, s Snippet) *TArg

Arg creates a template argument with the given name and snippet.

func ArgExpose

func ArgExpose(ctx context.Context, path string, name string, targs ...Snippet) *TArg

func ArgExposeUnsafe added in v0.1.7

func ArgExposeUnsafe(ctx context.Context, path string, name string) *TArg

func ArgFor

func ArgFor[T any](ctx context.Context, name string) *TArg

ArgFor creates a template argument using the type identifier of T.

func ArgT

func ArgT[T any](ctx context.Context) *TArg

ArgT creates a template argument using the type identifier of T, with the name defaulting to the type string.

func (*TArg) WithName

func (a *TArg) WithName(name string) *TArg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL