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 }