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}