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 ¶
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.
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.
Values (`value.go`): - `Value(v)`: Generates the Go code representation of a value (e.g., structs, maps, slices, primitives).
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`.
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.
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.
Imports (`imports.go`): - `Imports()`: Automatically collects and formats all package imports tracked during snippet evaluation.
Utilities (`separator.go`, `poster.go`): - `Indent(n)` / `NewLine(n)`: Generates whitespace separators. - `Poster(pkg, gen, ver)`: Generates the `// Code generated` header.
Index ¶
- func IsNil(s Snippet) bool
- type Placeholder
- type Snippet
- func Block(v string) Snippet
- func BlockF(v string, args ...any) Snippet
- func BlockRaw(v string) Snippet
- func Comments(lines ...string) Snippet
- func Compose(ss ...Snippet) Snippet
- func Directive(directive string, args ...string) Snippet
- func Document(name string, lines ...string) Snippet
- func Expose(ctx context.Context, path string, name string, targs ...Snippet) Snippet
- func ExposeObject(ctx context.Context, o types.Object, targs ...Snippet) Snippet
- func ExposeObjectUnsafe(ctx context.Context, o types.Object) Snippet
- func ExposeUnsafe(ctx context.Context, path, name string) Snippet
- func Ident(ctx context.Context, t typx.Type) Snippet
- func IdentFor[T any](ctx context.Context) Snippet
- func IdentOf(ctx context.Context, v any) Snippet
- func IdentRT(ctx context.Context, t reflect.Type) Snippet
- func IdentTT(ctx context.Context, t types.Type) Snippet
- func Imports(ctx context.Context) Snippet
- func Indent(n int) Snippet
- func InlineComment(line string) Snippet
- func NewLine(n int) Snippet
- func Poster(pkg string, generator, version string) Snippet
- func Snippets(sep Snippet, ss ...Snippet) Snippet
- func Strings(tail string, sep string, list ...string) Snippet
- func Template(r io.Reader, args ...*TArg) Snippet
- func Value(ctx context.Context, x any) Snippet
- type TArg
- func Arg(ctx context.Context, name string, s Snippet) *TArg
- func ArgExpose(ctx context.Context, path string, name string, targs ...Snippet) *TArg
- func ArgExposeUnsafe(ctx context.Context, path string, name string) *TArg
- func ArgFor[T any](ctx context.Context, name string) *TArg
- func ArgT[T any](ctx context.Context) *TArg
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type Placeholder ¶ added in v0.1.1
type Placeholder struct{}
Placeholder must be not nil and yield an empty 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 ¶
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 Comments ¶
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 Directive ¶
Directive creates a Snippet for a Go compiler directive (e.g., //go:directive args).
func Document ¶
Document creates a Snippet for a documentation comment block. If name is provided, it prefixes the first line with the name.
func Expose ¶
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 ¶
ExposeObject creates an exposer Snippet from a types.Object, validating the package and type arguments.
func ExposeObjectUnsafe ¶ added in v0.1.9
ExposeObjectUnsafe creates an exposer Snippet from a types.Object without validating the package or type arguments.
func ExposeUnsafe ¶ added in v0.1.6
ExposeUnsafe like Expose but skip validation of package and type arguments
func IdentFor ¶
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 ¶
IdentOf returns a Snippet representing the identifier for the type of the given value v.
func Imports ¶
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 InlineComment ¶
InlineComment creates a Snippet for an inline comment.
func Poster ¶
Poster creates a Snippet that generates the standard "Code generated" header and package declaration.
func Snippets ¶
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
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 ¶
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 ¶
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
}