templates.go

  1//go:generate go run ./inliner/inliner.go
  2
  3package templates
  4
  5import (
  6	"bytes"
  7	"fmt"
  8	"io/ioutil"
  9	"os"
 10	"path/filepath"
 11	"sort"
 12	"strconv"
 13	"strings"
 14	"text/template"
 15	"unicode"
 16
 17	"log"
 18
 19	"github.com/pkg/errors"
 20	"golang.org/x/tools/imports"
 21)
 22
 23func Run(name string, tpldata interface{}) (*bytes.Buffer, error) {
 24	t := template.New("").Funcs(template.FuncMap{
 25		"ucFirst":     ucFirst,
 26		"lcFirst":     lcFirst,
 27		"quote":       strconv.Quote,
 28		"rawQuote":    rawQuote,
 29		"toCamel":     ToCamel,
 30		"dump":        dump,
 31		"prefixLines": prefixLines,
 32	})
 33
 34	for filename, data := range data {
 35		_, err := t.New(filename).Parse(data)
 36		if err != nil {
 37			panic(err)
 38		}
 39	}
 40
 41	buf := &bytes.Buffer{}
 42	err := t.Lookup(name).Execute(buf, tpldata)
 43	if err != nil {
 44		return nil, err
 45	}
 46
 47	return buf, nil
 48}
 49
 50func ucFirst(s string) string {
 51	if s == "" {
 52		return ""
 53	}
 54	r := []rune(s)
 55	r[0] = unicode.ToUpper(r[0])
 56	return string(r)
 57}
 58
 59func lcFirst(s string) string {
 60	if s == "" {
 61		return ""
 62	}
 63
 64	r := []rune(s)
 65	r[0] = unicode.ToLower(r[0])
 66	return string(r)
 67}
 68
 69func isDelimiter(c rune) bool {
 70	return c == '-' || c == '_' || unicode.IsSpace(c)
 71}
 72
 73func ToCamel(s string) string {
 74	buffer := make([]rune, 0, len(s))
 75	upper := true
 76	lastWasUpper := false
 77
 78	for _, c := range s {
 79		if isDelimiter(c) {
 80			upper = true
 81			continue
 82		}
 83		if !lastWasUpper && unicode.IsUpper(c) {
 84			upper = true
 85		}
 86
 87		if upper {
 88			buffer = append(buffer, unicode.ToUpper(c))
 89		} else {
 90			buffer = append(buffer, unicode.ToLower(c))
 91		}
 92		upper = false
 93		lastWasUpper = unicode.IsUpper(c)
 94	}
 95
 96	return string(buffer)
 97}
 98
 99func rawQuote(s string) string {
100	return "`" + strings.Replace(s, "`", "`+\"`\"+`", -1) + "`"
101}
102
103func dump(val interface{}) string {
104	switch val := val.(type) {
105	case int:
106		return strconv.Itoa(val)
107	case int64:
108		return fmt.Sprintf("%d", val)
109	case float64:
110		return fmt.Sprintf("%f", val)
111	case string:
112		return strconv.Quote(val)
113	case bool:
114		return strconv.FormatBool(val)
115	case nil:
116		return "nil"
117	case []interface{}:
118		var parts []string
119		for _, part := range val {
120			parts = append(parts, dump(part))
121		}
122		return "[]interface{}{" + strings.Join(parts, ",") + "}"
123	case map[string]interface{}:
124		buf := bytes.Buffer{}
125		buf.WriteString("map[string]interface{}{")
126		var keys []string
127		for key := range val {
128			keys = append(keys, key)
129		}
130		sort.Strings(keys)
131
132		for _, key := range keys {
133			data := val[key]
134
135			buf.WriteString(strconv.Quote(key))
136			buf.WriteString(":")
137			buf.WriteString(dump(data))
138			buf.WriteString(",")
139		}
140		buf.WriteString("}")
141		return buf.String()
142	default:
143		panic(fmt.Errorf("unsupported type %T", val))
144	}
145}
146
147func prefixLines(prefix, s string) string {
148	return prefix + strings.Replace(s, "\n", "\n"+prefix, -1)
149}
150
151func RenderToFile(tpl string, filename string, data interface{}) error {
152	var buf *bytes.Buffer
153	buf, err := Run(tpl, data)
154	if err != nil {
155		return errors.Wrap(err, filename+" generation failed")
156	}
157
158	if err := write(filename, buf.Bytes()); err != nil {
159		return err
160	}
161
162	log.Println(filename)
163
164	return nil
165}
166
167func gofmt(filename string, b []byte) ([]byte, error) {
168	out, err := imports.Process(filename, b, nil)
169	if err != nil {
170		return b, errors.Wrap(err, "unable to gofmt")
171	}
172	return out, nil
173}
174
175func write(filename string, b []byte) error {
176	err := os.MkdirAll(filepath.Dir(filename), 0755)
177	if err != nil {
178		return errors.Wrap(err, "failed to create directory")
179	}
180
181	formatted, err := gofmt(filename, b)
182	if err != nil {
183		fmt.Fprintf(os.Stderr, "gofmt failed: %s\n", err.Error())
184		formatted = b
185	}
186
187	err = ioutil.WriteFile(filename, formatted, 0644)
188	if err != nil {
189		return errors.Wrapf(err, "failed to write %s", filename)
190	}
191
192	return nil
193}