go-common/app/tool/gengo/generator/generator.go
2019-04-22 18:49:16 +08:00

206 lines
7.1 KiB
Go

package generator
import (
"bytes"
"io"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/parser"
"go-common/app/tool/gengo/types"
)
// Package contains the contract for generating a package.
type Package interface {
// Name returns the package short name.
Name() string
// Path returns the package import path.
Path() 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
}
// File is
type File struct {
Name string
FileType string
PackageName string
Header []byte
Imports map[string]struct{}
Vars bytes.Buffer
Consts bytes.Buffer
Body bytes.Buffer
}
// FileType is
type FileType interface {
AssembleFile(f *File, path string) error
VerifyFile(f *File, path string) error
}
// Packages is a list of packages to generate.
type Packages []Package
// 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 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 Package 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
}
// Context is global context for individual generators to consume.
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
// Package'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 "golang" filetype will be provided.
FileTypes map[string]FileType
// If true, Execute* calls will just verify that the existing output is
// correct. (You may set this after calling NewContext.)
Verify bool
// Allows generators to add packages at runtime.
builder *parser.Builder
}
// NewContext generates a context from the given builder, naming systems, and
// the naming system you wish to construct the canonical ordering from.
func NewContext(b *parser.Builder, nameSystems namer.NameSystems, canonicalOrderName string) (*Context, error) {
universe, err := b.FindTypes()
if err != nil {
return nil, err
}
c := &Context{
Namers: namer.NameSystems{},
Universe: universe,
Inputs: b.FindPackages(),
FileTypes: map[string]FileType{
GolangFileType: NewGolangFile(),
},
builder: b,
}
for name, systemNamer := range nameSystems {
c.Namers[name] = systemNamer
if name == canonicalOrderName {
orderer := namer.Orderer{Namer: systemNamer}
c.Order = orderer.OrderUniverse(universe)
}
}
return c, nil
}
// AddDir adds a Go package to the context. The specified path must be a single
// go package import path. GOPATH, GOROOT, and the location of your go binary
// (`which go`) will all be searched, in the normal Go fashion.
// Deprecated. Please use AddDirectory.
func (ctxt *Context) AddDir(path string) error {
return ctxt.builder.AddDirTo(path, &ctxt.Universe)
}
// AddDirectory adds a Go package to the context. The specified path must be a
// single go package import path. GOPATH, GOROOT, and the location of your go
// binary (`which go`) will all be searched, in the normal Go fashion.
func (ctxt *Context) AddDirectory(path string) (*types.Package, error) {
return ctxt.builder.AddDirectoryTo(path, &ctxt.Universe)
}