//go:generate go run ./inliner/inliner.go
package templates
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"text/template"
"unicode"
"log"
"github.com/pkg/errors"
"golang.org/x/tools/imports"
)
func Run(name string, tpldata interface{}) (*bytes.Buffer, error) {
t := template.New("").Funcs(template.FuncMap{
"ucFirst": ucFirst,
"lcFirst": lcFirst,
"quote": strconv.Quote,
"rawQuote": rawQuote,
"toCamel": ToCamel,
"dump": dump,
"prefixLines": prefixLines,
})
for filename, data := range data {
_, err := t.New(filename).Parse(data)
if err != nil {
panic(err)
}
}
buf := &bytes.Buffer{}
err := t.Lookup(name).Execute(buf, tpldata)
if err != nil {
return nil, err
}
return buf, nil
}
func ucFirst(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToUpper(r[0])
return string(r)
}
func lcFirst(s string) string {
if s == "" {
return ""
}
r := []rune(s)
r[0] = unicode.ToLower(r[0])
return string(r)
}
func isDelimiter(c rune) bool {
return c == '-' || c == '_' || unicode.IsSpace(c)
}
func ToCamel(s string) string {
buffer := make([]rune, 0, len(s))
upper := true
lastWasUpper := false
for _, c := range s {
if isDelimiter(c) {
upper = true
continue
}
if !lastWasUpper && unicode.IsUpper(c) {
upper = true
}
if upper {
buffer = append(buffer, unicode.ToUpper(c))
} else {
buffer = append(buffer, unicode.ToLower(c))
}
upper = false
lastWasUpper = unicode.IsUpper(c)
}
return string(buffer)
}
func rawQuote(s string) string {
return "`" + strings.Replace(s, "`", "`+\"`\"+`", -1) + "`"
}
func dump(val interface{}) string {
switch val := val.(type) {
case int:
return strconv.Itoa(val)
case int64:
return fmt.Sprintf("%d", val)
case float64:
return fmt.Sprintf("%f", val)
case string:
return strconv.Quote(val)
case bool:
return strconv.FormatBool(val)
case nil:
return "nil"
case []interface{}:
var parts []string
for _, part := range val {
parts = append(parts, dump(part))
}
return "[]interface{}{" + strings.Join(parts, ",") + "}"
case map[string]interface{}:
buf := bytes.Buffer{}
buf.WriteString("map[string]interface{}{")
var keys []string
for key := range val {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
data := val[key]
buf.WriteString(strconv.Quote(key))
buf.WriteString(":")
buf.WriteString(dump(data))
buf.WriteString(",")
}
buf.WriteString("}")
return buf.String()
default:
panic(fmt.Errorf("unsupported type %T", val))
}
}
func prefixLines(prefix, s string) string {
return prefix + strings.Replace(s, "\n", "\n"+prefix, -1)
}
func RenderToFile(tpl string, filename string, data interface{}) error {
var buf *bytes.Buffer
buf, err := Run(tpl, data)
if err != nil {
return errors.Wrap(err, filename+" generation failed")
}
if err := write(filename, buf.Bytes()); err != nil {
return err
}
log.Println(filename)
return nil
}
func gofmt(filename string, b []byte) ([]byte, error) {
out, err := imports.Process(filename, b, nil)
if err != nil {
return b, errors.Wrap(err, "unable to gofmt")
}
return out, nil
}
func write(filename string, b []byte) error {
err := os.MkdirAll(filepath.Dir(filename), 0755)
if err != nil {
return errors.Wrap(err, "failed to create directory")
}
formatted, err := gofmt(filename, b)
if err != nil {
fmt.Fprintf(os.Stderr, "gofmt failed: %s\n", err.Error())
formatted = b
}
err = ioutil.WriteFile(filename, formatted, 0644)
if err != nil {
return errors.Wrapf(err, "failed to write %s", filename)
}
return nil
}