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}