generator

package
v2.0.0-...-2b36238 Latest Latest
Warning

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

Go to latest
Published: Sep 11, 2024 License: Apache-2.0 Imports: 15 Imported by: 30

Documentation

Overview

Package generator defines an interface for code generators to implement.

To use this package, you'll implement the "Package" and "Generator" interfaces; you'll call NewContext to load up the types you want to work with, and then you'll call one or more of the Execute methods. See the interface definitions for explanations. All output will have gofmt called on it automatically, so you do not need to worry about generating correct indentation.

This package also exposes SnippetWriter. SnippetWriter reduces to a minimum the boilerplate involved in setting up a template from go's text/template package. Additionally, all naming systems in the Context will be added as functions to the parsed template, so that they can be called directly from your templates!

Index

Constants

View Source
const (
	GoFileType = "go"
)

Variables

This section is empty.

Functions

func NewImportTracker

func NewImportTracker(typesToAdd ...*types.Type) *namer.DefaultImportTracker

func NewImportTrackerForPackage

func NewImportTrackerForPackage(local string, typesToAdd ...*types.Type) *namer.DefaultImportTracker

NewImportTrackerForPackage creates a new import tracker which is aware of a generator's output package. The tracker will not add import lines when symbols or types are added from the same package, and LocalNameOf will return empty string for the output package.

e.g.:

tracker := NewImportTrackerForPackage("bar.com/pkg/foo")
tracker.AddSymbol(types.Name{"bar.com/pkg/foo.MyType"})
tracker.AddSymbol(types.Name{"bar.com/pkg/baz.MyType"})
tracker.AddSymbol(types.Name{"bar.com/pkg/baz/baz.MyType"})

tracker.LocalNameOf("bar.com/pkg/foo") -> ""
tracker.LocalNameOf("bar.com/pkg/baz") -> "baz"
tracker.LocalNameOf("bar.com/pkg/baz/baz") -> "bazbaz"
tracker.ImportLines() -> {`baz "bar.com/pkg/baz"`, `bazbaz "bar.com/pkg/baz/baz"`}

Types

type Args

type Args map[interface{}]interface{}

Args exists to make it convenient to construct arguments for SnippetWriter.Do.

func (Args) With

func (a Args) With(key, value interface{}) Args

With makes a copy of a and adds the given key, value pair. If key overlaps, the new value wins.

func (Args) WithArgs

func (a Args) WithArgs(rhs Args) Args

WithArgs makes a copy of a and adds the given arguments. If any keys overlap, the values from rhs win.

type Context

type Context struct {
	// A map from the naming system to the names for that system. E.g., you
	// might have public names and several private naming systems.
	Namers namer.NameSystems

	// All the types, in case you want to look up something.
	Universe types.Universe

	// All the user-specified packages.  This is after recursive expansion.
	Inputs []string

	// The canonical ordering of the types (will be filtered by both the
	// Target's and Generator's Filter methods).
	Order []*types.Type

	// A set of types this context can process. If this is empty or nil,
	// the default "go" filetype will be provided.
	FileTypes map[string]FileType
	// contains filtered or unexported fields
}

Context is global context for individual generators to consume.

func NewContext

func NewContext(p *parser.Parser, nameSystems namer.NameSystems, canonicalOrderName string) (*Context, error)

NewContext generates a context from the given parser, naming systems, and the naming system you wish to construct the canonical ordering from.

func (*Context) ExecuteTarget

func (c *Context) ExecuteTarget(tgt Target) error

ExecuteTarget runs the generators for a single target.

func (*Context) ExecuteTargets

func (c *Context) ExecuteTargets(targets []Target) error

ExecuteTargets runs the generators for the provided targets.

func (*Context) FindPackages

func (c *Context) FindPackages(patterns ...string) ([]string, error)

FindPackages expands Go package patterns into a list of package import paths, akin to `go list -find`.

func (*Context) LoadPackages

func (c *Context) LoadPackages(patterns ...string) ([]*types.Package, error)

LoadPackages adds Go packages to the context.

type DefaultFileType

type DefaultFileType struct {
	Format   func([]byte) ([]byte, error)
	Assemble func(io.Writer, *File)
}

func NewGoFile

func NewGoFile() *DefaultFileType

func (DefaultFileType) AssembleFile

func (ft DefaultFileType) AssembleFile(f *File, pathname string) error

type ErrorTracker

type ErrorTracker struct {
	io.Writer
	// contains filtered or unexported fields
}

ErrorTracker tracks errors to the underlying writer, so that you can ignore them until you're ready to return.

func NewErrorTracker

func NewErrorTracker(w io.Writer) *ErrorTracker

NewErrorTracker makes a new error tracker; note that it implements io.Writer.

func (*ErrorTracker) Error

func (et *ErrorTracker) Error() error

Error returns nil if no error has occurred, otherwise it returns the error.

func (*ErrorTracker) Write

func (et *ErrorTracker) Write(p []byte) (n int, err error)

Write intercepts calls to Write.

type File

type File struct {
	Name        string
	FileType    string
	PackageName string
	Header      []byte
	PackagePath string
	PackageDir  string
	Imports     map[string]struct{}
	Vars        bytes.Buffer
	Consts      bytes.Buffer
	Body        bytes.Buffer
}

type FileType

type FileType interface {
	AssembleFile(f *File, path string) error
}

type Generator

type Generator interface {
	// The name of this generator. Will be included in generated comments.
	Name() string

	// Filter should return true if this generator cares about this type.
	// (otherwise, GenerateType will not be called.)
	//
	// Filter is called before any of the generator's other functions;
	// subsequent calls will get a context with only the types that passed
	// this filter.
	Filter(*Context, *types.Type) bool

	// If this generator needs special namers, return them here. These will
	// override the original namers in the context if there is a collision.
	// You may return nil if you don't need special names. These names will
	// be available in the context passed to the rest of the generator's
	// functions.
	//
	// A use case for this is to return a namer that tracks imports.
	Namers(*Context) namer.NameSystems

	// Init should write an init function, and any other content that's not
	// generated per-type. (It's not intended for generator specific
	// initialization! Do that when your Target constructs the
	// Generators.)
	Init(*Context, io.Writer) error

	// Finalize should write finish up functions, and any other content that's not
	// generated per-type.
	Finalize(*Context, io.Writer) error

	// PackageVars should emit an array of variable lines. They will be
	// placed in a var ( ... ) block. There's no need to include a leading
	// \t or trailing \n.
	PackageVars(*Context) []string

	// PackageConsts should emit an array of constant lines. They will be
	// placed in a const ( ... ) block. There's no need to include a leading
	// \t or trailing \n.
	PackageConsts(*Context) []string

	// GenerateType should emit the code for a particular type.
	GenerateType(*Context, *types.Type, io.Writer) error

	// Imports should return a list of necessary imports. They will be
	// formatted correctly. You do not need to include quotation marks,
	// return only the package name; alternatively, you can also return
	// imports in the format `name "path/to/pkg"`. Imports will be called
	// after Init, PackageVars, PackageConsts, and GenerateType, to allow
	// you to keep track of what imports you actually need.
	Imports(*Context) []string

	// Preferred file name of this generator, not including a path. It is
	// allowed for multiple generators to use the same filename, but it's
	// up to you to make sure they don't have colliding import names.
	// TODO: provide per-file import tracking, removing the requirement
	// that generators coordinate..
	Filename() string

	// A registered file type in the context to generate this file with. If
	// the FileType is not found in the context, execution will stop.
	FileType() string
}

Generator is the contract for anything that wants to do auto-generation. It's expected that the io.Writers passed to the below functions will be ErrorTrackers; this allows implementations to not check for io errors, making more readable code.

The call order for the functions that take a Context is: 1. Filter() // Subsequent calls see only types that pass this. 2. Namers() // Subsequent calls see the namers provided by this. 3. PackageVars() 4. PackageConsts() 5. Init() 6. GenerateType() // Called N times, once per type in the context's Order. 7. Imports()

You may have multiple generators for the same file.

type GoGenerator

type GoGenerator struct {
	// OutputFilename is used as the Generator's name, and filename.
	OutputFilename string

	// Body, if present, will be used as the return from the "Init" method.
	// This causes it to be static content for the entire file if no other
	// generator touches the file.
	OptionalBody []byte
}

GoGenerator implements a do-nothing Generator for Go files. It can be used as a base for custom Generators, which embed it and then define the methods they need to specialize.

func (GoGenerator) FileType

func (gg GoGenerator) FileType() string

func (GoGenerator) Filename

func (gg GoGenerator) Filename() string

func (GoGenerator) Filter

func (gg GoGenerator) Filter(*Context, *types.Type) bool

func (GoGenerator) Finalize

func (gg GoGenerator) Finalize(*Context, io.Writer) error

func (GoGenerator) GenerateType

func (gg GoGenerator) GenerateType(*Context, *types.Type, io.Writer) error

func (GoGenerator) Imports

func (gg GoGenerator) Imports(*Context) []string

func (GoGenerator) Init

func (gg GoGenerator) Init(c *Context, w io.Writer) error

func (GoGenerator) Name

func (gg GoGenerator) Name() string

func (GoGenerator) Namers

func (gg GoGenerator) Namers(*Context) namer.NameSystems

func (GoGenerator) PackageConsts

func (gg GoGenerator) PackageConsts(*Context) []string

func (GoGenerator) PackageVars

func (gg GoGenerator) PackageVars(*Context) []string

type SimpleTarget

type SimpleTarget struct {
	// PkgName is the name of the resulting package (as in "package xxxx").
	// Required.
	PkgName string
	// PkgPath is the canonical Go import-path of the resulting package (as in
	// "import example.com/xxxx/yyyy"). Required.
	PkgPath string
	// PkgDir is the location of the resulting package on disk (which may not
	// exist yet). It may be absolute or relative to CWD. Required.
	PkgDir string

	// HeaderComment is emitted at the top of every output file. Optional.
	HeaderComment []byte

	// PkgDocComment is emitted after the header comment for a "doc.go" file.
	// Optional.
	PkgDocComment []byte

	// FilterFunc will be called to implement Target.Filter. Optional.
	FilterFunc func(*Context, *types.Type) bool

	// GeneratorsFunc will be called to implement Target.Generators. Optional.
	GeneratorsFunc func(*Context) []Generator
}

SimpleTarget is implements Target in terms of static configuration. The package name, path, and dir are required to be non-empty.

func (SimpleTarget) Dir

func (st SimpleTarget) Dir() string

func (SimpleTarget) Filter

func (st SimpleTarget) Filter(c *Context, t *types.Type) bool

func (SimpleTarget) Generators

func (st SimpleTarget) Generators(c *Context) []Generator

func (SimpleTarget) Header

func (st SimpleTarget) Header(filename string) []byte

func (SimpleTarget) Name

func (st SimpleTarget) Name() string

func (SimpleTarget) Path

func (st SimpleTarget) Path() string

type SnippetWriter

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

SnippetWriter is an attempt to make the template library usable. Methods are chainable, and you don't have to check Error() until you're all done.

func NewSnippetWriter

func NewSnippetWriter(w io.Writer, c *Context, left, right string) *SnippetWriter

w is the destination; left and right are the delimiters; @ and $ are both reasonable choices.

c is used to make a function for every naming system, to which you can pass a type and get the corresponding name.

func (*SnippetWriter) Append

func (s *SnippetWriter) Append(r io.Reader) error

Append adds the contents of the io.Reader to this SnippetWriter's buffer.

func (*SnippetWriter) Do

func (s *SnippetWriter) Do(format string, args interface{}) *SnippetWriter

Do parses format and runs args through it. You can have arbitrary logic in the format (see the text/template documentation), but consider running many short templates with ordinary go logic in between--this may be more readable. Do is chainable. Any error causes every other call to do to be ignored, and the error will be returned by Error(). So you can check it just once, at the end of your function.

'args' can be quite literally anything; read the text/template documentation for details. Maps and structs work particularly nicely. Conveniently, the types package is designed to have structs that are easily referencable from the template language.

Example:

sw := generator.NewSnippetWriter(outBuffer, context, "$", "$") sw.Do(`The public type name is: $.type|public$`, map[string]interface{}{"type": t}) return sw.Error()

Where:

  • "$" starts a template directive
  • "." references the entire thing passed as args
  • "type" therefore sees a map and looks up the key "type"
  • "|" means "pass the thing on the left to the thing on the right"
  • "public" is the name of a naming system, so the SnippetWriter has given the template a function called "public" that takes a *types.Type and returns the naming system's name. E.g., if the type is "string" this might return "String".
  • the second "$" ends the template directive.

The map is actually not necessary. The below does the same thing:

sw.Do(`The public type name is: $.|public$`, t)

You may or may not find it more readable to use the map with a descriptive key, but if you want to pass more than one arg, the map or a custom struct becomes a requirement. You can do arbitrary logic inside these templates, but you should consider doing the logic in go and stitching them together for the sake of your readers.

TODO: Change Do() to optionally take a list of pairs of parameters (key, value) and have it construct a combined map with that and args.

func (*SnippetWriter) Dup

func (s *SnippetWriter) Dup(w io.Writer) *SnippetWriter

Dup creates an exact duplicate SnippetWriter with a different io.Writer.

func (*SnippetWriter) Error

func (s *SnippetWriter) Error() error

Error returns any encountered error.

func (*SnippetWriter) Out

func (s *SnippetWriter) Out() io.Writer

type Target

type Target interface {
	// Name returns the package short name (as in `package foo`).
	Name() string
	// Path returns the package import path (as in `import "example.com/foo"`).
	Path() string
	// Dir returns the location of the resulting package on disk.  This may be
	// the same directory as an input package (when generating code in-place)
	// or a different directory entirely.
	Dir() string

	// Filter should return true if this package cares about this type.
	// Otherwise, this type will be omitted from the type ordering for
	// this package.
	Filter(*Context, *types.Type) bool

	// Header should return a header for the file, including comment markers.
	// Useful for copyright notices and doc strings. Include an
	// autogeneration notice! Do not include the "package x" line.
	Header(filename string) []byte

	// Generators returns the list of generators for this package. It is
	// allowed for more than one generator to write to the same file.
	// A Context is passed in case the list of generators depends on the
	// input types.
	Generators(*Context) []Generator
}

Target describes a Go package into which code will be generated. A single Target may have many Generators, each of which emits one file.

Jump to

Keyboard shortcuts

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