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