| // Copyright 2022 The Tint Authors. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // package template wraps the golang "text/template" package to provide an | 
 | // enhanced template generator. | 
 | package template | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"io/ioutil" | 
 | 	"path/filepath" | 
 | 	"strings" | 
 | 	"text/template" | 
 | 	"unicode" | 
 |  | 
 | 	"dawn.googlesource.com/dawn/tools/src/fileutils" | 
 | ) | 
 |  | 
 | // The template function binding table | 
 | type Functions map[string]interface{} | 
 |  | 
 | // Run executes the template tmpl, writing the output to w. | 
 | // funcs are the functions provided to the template. | 
 | // See https://golang.org/pkg/text/template/ for documentation on the template | 
 | // syntax. | 
 | func Run(tmpl string, w io.Writer, funcs Functions) error { | 
 | 	g := generator{ | 
 | 		template: template.New("<template>"), | 
 | 	} | 
 |  | 
 | 	globals := newMap() | 
 |  | 
 | 	// Add a bunch of generic useful functions | 
 | 	g.funcs = Functions{ | 
 | 		"Contains":   strings.Contains, | 
 | 		"Eval":       g.eval, | 
 | 		"Globals":    func() Map { return globals }, | 
 | 		"HasPrefix":  strings.HasPrefix, | 
 | 		"HasSuffix":  strings.HasSuffix, | 
 | 		"Import":     g.importTmpl, | 
 | 		"Iterate":    iterate, | 
 | 		"Map":        newMap, | 
 | 		"PascalCase": pascalCase, | 
 | 		"Split":      strings.Split, | 
 | 		"Title":      strings.Title, | 
 | 		"TrimLeft":   strings.TrimLeft, | 
 | 		"TrimPrefix": strings.TrimPrefix, | 
 | 		"TrimRight":  strings.TrimRight, | 
 | 		"TrimSuffix": strings.TrimSuffix, | 
 | 	} | 
 |  | 
 | 	// Append custom functions | 
 | 	for name, fn := range funcs { | 
 | 		g.funcs[name] = fn | 
 | 	} | 
 |  | 
 | 	if err := g.bindAndParse(g.template, tmpl); err != nil { | 
 | 		return err | 
 | 	} | 
 |  | 
 | 	return g.template.Execute(w, nil) | 
 | } | 
 |  | 
 | type generator struct { | 
 | 	template *template.Template | 
 | 	funcs    Functions | 
 | } | 
 |  | 
 | func (g *generator) bindAndParse(t *template.Template, tmpl string) error { | 
 | 	_, err := t. | 
 | 		Funcs(map[string]interface{}(g.funcs)). | 
 | 		Option("missingkey=error"). | 
 | 		Parse(tmpl) | 
 | 	return err | 
 | } | 
 |  | 
 | // eval executes the sub-template with the given name and argument, returning | 
 | // the generated output | 
 | func (g *generator) eval(template string, args ...interface{}) (string, error) { | 
 | 	target := g.template.Lookup(template) | 
 | 	if target == nil { | 
 | 		return "", fmt.Errorf("template '%v' not found", template) | 
 | 	} | 
 | 	sb := strings.Builder{} | 
 |  | 
 | 	var err error | 
 | 	if len(args) == 1 { | 
 | 		err = target.Execute(&sb, args[0]) | 
 | 	} else { | 
 | 		m := newMap() | 
 | 		if len(args)%2 != 0 { | 
 | 			return "", fmt.Errorf("Eval expects a single argument or list name-value pairs") | 
 | 		} | 
 | 		for i := 0; i < len(args); i += 2 { | 
 | 			name, ok := args[i].(string) | 
 | 			if !ok { | 
 | 				return "", fmt.Errorf("Eval argument %v is not a string", i) | 
 | 			} | 
 | 			m.Put(name, args[i+1]) | 
 | 		} | 
 | 		err = target.Execute(&sb, m) | 
 | 	} | 
 |  | 
 | 	if err != nil { | 
 | 		return "", fmt.Errorf("while evaluating '%v': %v", template, err) | 
 | 	} | 
 | 	return sb.String(), nil | 
 | } | 
 |  | 
 | // importTmpl parses the template at the given project-relative path, merging | 
 | // the template definitions into the global namespace. | 
 | // Note: The body of the template is not executed. | 
 | func (g *generator) importTmpl(path string) (string, error) { | 
 | 	if strings.Contains(path, "..") { | 
 | 		return "", fmt.Errorf("import path must not contain '..'") | 
 | 	} | 
 | 	path = filepath.Join(fileutils.DawnRoot(), path) | 
 | 	data, err := ioutil.ReadFile(path) | 
 | 	if err != nil { | 
 | 		return "", fmt.Errorf("failed to open '%v': %w", path, err) | 
 | 	} | 
 | 	t := g.template.New("") | 
 | 	if err := g.bindAndParse(t, string(data)); err != nil { | 
 | 		return "", fmt.Errorf("failed to parse '%v': %w", path, err) | 
 | 	} | 
 | 	if err := t.Execute(ioutil.Discard, nil); err != nil { | 
 | 		return "", fmt.Errorf("failed to execute '%v': %w", path, err) | 
 | 	} | 
 | 	return "", nil | 
 | } | 
 |  | 
 | // Map is a simple generic key-value map, which can be used in the template | 
 | type Map map[interface{}]interface{} | 
 |  | 
 | func newMap() Map { return Map{} } | 
 |  | 
 | // Put adds the key-value pair into the map. | 
 | // Put always returns an empty string so nothing is printed in the template. | 
 | func (m Map) Put(key, value interface{}) string { | 
 | 	m[key] = value | 
 | 	return "" | 
 | } | 
 |  | 
 | // Get looks up and returns the value with the given key. If the map does not | 
 | // contain the given key, then nil is returned. | 
 | func (m Map) Get(key interface{}) interface{} { | 
 | 	return m[key] | 
 | } | 
 |  | 
 | // iterate returns a slice of length 'n', with each element equal to its index. | 
 | // Useful for: {{- range Iterate $n -}}<this will be looped $n times>{{end}} | 
 | func iterate(n int) []int { | 
 | 	out := make([]int, n) | 
 | 	for i := range out { | 
 | 		out[i] = i | 
 | 	} | 
 | 	return out | 
 | } | 
 |  | 
 | // pascalCase returns the snake-case string s transformed into 'PascalCase', | 
 | // Rules: | 
 | // * The first letter of the string is capitalized | 
 | // * Characters following an underscore or number are capitalized | 
 | // * Underscores are removed from the returned string | 
 | // See: https://en.wikipedia.org/wiki/Camel_case | 
 | func pascalCase(s string) string { | 
 | 	b := strings.Builder{} | 
 | 	upper := true | 
 | 	for _, r := range s { | 
 | 		if r == '_' || r == ' ' { | 
 | 			upper = true | 
 | 			continue | 
 | 		} | 
 | 		if upper { | 
 | 			b.WriteRune(unicode.ToUpper(r)) | 
 | 			upper = false | 
 | 		} else { | 
 | 			b.WriteRune(r) | 
 | 		} | 
 | 		if unicode.IsNumber(r) { | 
 | 			upper = true | 
 | 		} | 
 | 	} | 
 | 	return b.String() | 
 | } |