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

141 lines
4.1 KiB
Go

package generator
import (
"fmt"
"io"
"runtime"
"text/template"
)
// 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.
type SnippetWriter struct {
w io.Writer
context *Context
// Left & right delimiters. text/template defaults to "{{" and "}}"
// which is totally unusable for go code based templates.
left, right string
funcMap template.FuncMap
err error
}
// NewSnippetWriter is
// 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 NewSnippetWriter(w io.Writer, c *Context, left, right string) *SnippetWriter {
sw := &SnippetWriter{
w: w,
context: c,
left: left,
right: right,
funcMap: template.FuncMap{},
}
for name, namer := range c.Namers {
sw.funcMap[name] = namer.Name
}
return sw
}
// 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 templaces, 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 (s *SnippetWriter) Do(format string, args interface{}) *SnippetWriter {
if s.err != nil {
return s
}
// Name the template by source file:line so it can be found when
// there's an error.
_, file, line, _ := runtime.Caller(1)
tmpl, err := template.
New(fmt.Sprintf("%s:%d", file, line)).
Delims(s.left, s.right).
Funcs(s.funcMap).
Parse(format)
if err != nil {
s.err = err
return s
}
err = tmpl.Execute(s.w, args)
if err != nil {
s.err = err
}
return s
}
// Args exists to make it convenient to construct arguments for
// SnippetWriter.Do.
type Args map[interface{}]interface{}
// With makes a copy of a and adds the given key, value pair.
func (a Args) With(key, value interface{}) Args {
a2 := Args{key: value}
for k, v := range a {
a2[k] = v
}
return a2
}
// WithArgs makes a copy of a and adds the given arguments.
func (a Args) WithArgs(rhs Args) Args {
a2 := Args{}
for k, v := range rhs {
a2[k] = v
}
for k, v := range a {
a2[k] = v
}
return a2
}
// Out is
func (s *SnippetWriter) Out() io.Writer {
return s.w
}
// Error returns any encountered error.
func (s *SnippetWriter) Error() error {
return s.err
}