-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy traverse code from pkg/util/template/validation.go
- Loading branch information
Showing
2 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"html/template" | ||
"io" | ||
"reflect" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/Masterminds/sprig" | ||
) | ||
|
||
const ( | ||
templateTranslationMessageTemplateName = "__translation_message.html" | ||
) | ||
|
||
type tpl interface { | ||
ExecuteTemplate(wr io.Writer, name string, data any) error | ||
} | ||
|
||
func MakeTemplateFuncMap(t tpl) map[string]interface{} { | ||
var templateFuncMap = sprig.HermeticHtmlFuncMap() | ||
// templateFuncMap[messageformat.TemplateRuntimeFuncName] = messageformat.TemplateRuntimeFunc | ||
templateFuncMap["rfc3339"] = RFC3339 | ||
templateFuncMap["ensureTime"] = EnsureTime | ||
templateFuncMap["isNil"] = IsNil | ||
templateFuncMap["showAttributeValue"] = ShowAttributeValue | ||
templateFuncMap["htmlattr"] = HTMLAttr | ||
templateFuncMap["include"] = makeInclude(t) | ||
templateFuncMap["translate"] = makeTranslate(t) | ||
templateFuncMap["trimHTML"] = trimHTML | ||
return templateFuncMap | ||
} | ||
|
||
func RFC3339(date interface{}) interface{} { | ||
switch date := date.(type) { | ||
case *time.Time: | ||
return date.UTC().Format(time.RFC3339) | ||
case time.Time: | ||
return date.UTC().Format(time.RFC3339) | ||
default: | ||
return "INVALID_DATE" | ||
} | ||
} | ||
|
||
func EnsureTime(anyValue interface{}) interface{} { | ||
switch anyValue := anyValue.(type) { | ||
case *time.Time: | ||
return anyValue | ||
case time.Time: | ||
return anyValue | ||
case string: | ||
t, err := time.Parse(time.RFC3339, anyValue) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return t | ||
case *string: | ||
t, err := time.Parse(time.RFC3339, *anyValue) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return t | ||
default: | ||
return anyValue | ||
} | ||
} | ||
|
||
func IsNil(v interface{}) bool { | ||
return v == nil || | ||
(reflect.ValueOf(v).Kind() == reflect.Ptr && reflect.ValueOf(v).IsNil()) | ||
} | ||
|
||
func ShowAttributeValue(v interface{}) string { | ||
value := reflect.ValueOf(v) | ||
if value.Kind() == reflect.Ptr { | ||
if !value.IsNil() { | ||
return ShowAttributeValue(reflect.ValueOf(v).Elem().Interface()) | ||
} | ||
return "" | ||
} | ||
|
||
switch v := v.(type) { | ||
case string: | ||
return v | ||
case float64: | ||
return strconv.FormatFloat(v, 'f', -1, 64) | ||
case float32: | ||
return strconv.FormatFloat(float64(v), 'f', -1, 32) | ||
case nil: | ||
return "" | ||
default: | ||
return fmt.Sprintf("%v", v) | ||
|
||
} | ||
} | ||
|
||
func HTMLAttr(v string) template.HTMLAttr { | ||
// Ignore gosec error because the app developer can actually write any template | ||
// But we should be careful that do not pass any user input to this function | ||
return template.HTMLAttr(v) // nolint:gosec | ||
} | ||
|
||
func makeInclude(t tpl) func(tplName string, data any) (template.HTML, error) { | ||
return func( | ||
tplName string, | ||
data any, | ||
) (template.HTML, error) { | ||
buf := &bytes.Buffer{} | ||
err := t.ExecuteTemplate(buf, tplName, data) | ||
// Ignore gosec error because the app developer can actually write any template | ||
// But we should be careful that do not pass any user input to this function | ||
html := template.HTML(buf.String()) // nolint:gosec | ||
return html, err | ||
} | ||
} | ||
|
||
// `translate` is intended for `include` a translation message but wrapped it | ||
// in a span and set its translation key with data attribute | ||
// In theory it can be used with resources other than translation, but take your | ||
// own risks | ||
func makeTranslate(t tpl) func(tranlsationKey string, data any) (template.HTML, error) { | ||
include := makeInclude(t) | ||
return func( | ||
tranlsationKey string, | ||
data any, | ||
) (template.HTML, error) { | ||
included, err := include(tranlsationKey, data) | ||
if err != nil { | ||
return template.HTML(""), err | ||
} | ||
buf := &bytes.Buffer{} | ||
d := make(map[string]interface{}) | ||
d["Key"] = tranlsationKey | ||
d["Value"] = included | ||
err = t.ExecuteTemplate(buf, templateTranslationMessageTemplateName, d) | ||
// Ignore gosec error because the app developer can actually write any template | ||
// But we should be careful that do not pass any user input to this function | ||
html := template.HTML(buf.String()) // nolint:gosec | ||
return html, err | ||
} | ||
} | ||
|
||
func trimHTML(input interface{}) interface{} { | ||
switch input := input.(type) { | ||
case string: | ||
return strings.TrimSpace(input) | ||
case template.HTML: | ||
// `Masterminds/sprig`'s `trimAll` cannot handle html type, so we need to convert it to string first | ||
// Ignore gosec error because this function is intended to be used with trusted input (__alert_message.html) | ||
// and the output is intended to be used as HTML | ||
return template.HTML(strings.TrimSpace(string(input))) // nolint:gosec | ||
default: | ||
return "" | ||
} | ||
} |
140 changes: 139 additions & 1 deletion
140
devtools/gotemplatetranslationlinter/translation_key_rule.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,147 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
htmltemplate "html/template" | ||
"sort" | ||
"text/template/parse" | ||
) | ||
|
||
type TranslationKeyRule struct{} | ||
|
||
func (r TranslationKeyRule) Check(content string, path string) LintViolations { | ||
return r.check(content, path) | ||
} | ||
|
||
func (r TranslationKeyRule) check(content string, path string) LintViolations { | ||
var violations LintViolations | ||
// TODO: implement linting logic here | ||
t := r.makeTemplate(content) | ||
|
||
r.validateHTMLTemplate(t) | ||
|
||
// TODO: add violations | ||
return violations | ||
} | ||
|
||
func (r TranslationKeyRule) makeTemplate(content string) *htmltemplate.Template { | ||
t := htmltemplate.New("") | ||
funcMap := MakeTemplateFuncMap(t) | ||
t.Funcs(funcMap) | ||
parsed := htmltemplate.Must(t.Parse(content)) | ||
|
||
return parsed | ||
} | ||
|
||
func (r TranslationKeyRule) validateHTMLTemplate(template *htmltemplate.Template) error { | ||
tpls := template.Templates() | ||
|
||
sort.Slice(tpls, func(i, j int) bool { | ||
return tpls[i].Name() < tpls[j].Name() | ||
}) | ||
|
||
for _, tpl := range tpls { | ||
if tpl.Tree == nil { | ||
fmt.Printf("tpl.Tree == nil\n") | ||
continue | ||
} | ||
// fmt.Printf("tpl.Tree != nil\n") | ||
if err := r.validateTree(tpl.Tree); err != nil { | ||
return err | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func (r TranslationKeyRule) validateTree(tree *parse.Tree) (err error) { | ||
validateFn := func(n parse.Node, depth int) (cont bool) { | ||
// fmt.Printf("inside validateFn\n") | ||
switch n := n.(type) { | ||
case *parse.CommandNode: | ||
fmt.Printf("case *parse.CommandNode:\n") | ||
for _, arg := range n.Args { | ||
if ident, ok := arg.(*parse.IdentifierNode); ok && ident.String() == "include" { | ||
// TODO: handle include fn | ||
fmt.Printf("pkong# xdd %s\n", ident) | ||
fmt.Printf("pkong# include n: %s\n", n) | ||
} | ||
} | ||
case *parse.TemplateNode: | ||
fmt.Printf("case *parse.TemplateNode:\n") | ||
// TODO: handle template node | ||
fmt.Printf("pkong# template n: %s\n", n) | ||
// default: | ||
// fmt.Printf("pkong# node n: %s\n", n) | ||
// fmt.Printf("pkong# node n.(type): %t\n", n) | ||
} | ||
|
||
return err == nil | ||
} | ||
|
||
r.traverseTree(tree, validateFn) | ||
return | ||
} | ||
|
||
func (r TranslationKeyRule) traverseTree(tree *parse.Tree, fn func(n parse.Node, depth int) (cont bool)) { | ||
r.traverseTreeVisit(tree.Root, 0, fn) | ||
} | ||
|
||
func (r TranslationKeyRule) traverseTreeVisitBranch(n *parse.BranchNode, depth int, fn func(n parse.Node, depth int) (cont bool)) (cont bool) { | ||
if cont = r.traverseTreeVisit(n.Pipe, depth, fn); !cont { | ||
return | ||
} | ||
if cont = r.traverseTreeVisit(n.List, depth, fn); !cont { | ||
return | ||
} | ||
if n.ElseList != nil { | ||
if cont = r.traverseTreeVisit(n.ElseList, depth, fn); !cont { | ||
return false | ||
} | ||
} | ||
return | ||
} | ||
|
||
func (r TranslationKeyRule) traverseTreeVisit(n parse.Node, depth int, fn func(n parse.Node, depth int) (cont bool)) (cont bool) { | ||
// fmt.Printf("traverseTreeVisit\n") | ||
cont = fn(n, depth) | ||
if !cont { | ||
return | ||
} | ||
|
||
// fmt.Printf("n: %s\n", n) | ||
switch n := n.(type) { | ||
case *parse.PipeNode: | ||
fmt.Printf("case *parse.PipeNode:\n") | ||
for _, cmd := range n.Cmds { | ||
if cont = r.traverseTreeVisit(cmd, depth, fn); !cont { | ||
break | ||
} | ||
} | ||
case *parse.CommandNode: | ||
fmt.Printf("case *parse.CommandNode:\n") | ||
for _, arg := range n.Args { | ||
if pipe, ok := arg.(*parse.PipeNode); ok { | ||
if cont = r.traverseTreeVisit(pipe, depth+1, fn); !cont { | ||
break | ||
} | ||
} | ||
} | ||
case *parse.ActionNode: | ||
cont = r.traverseTreeVisit(n.Pipe, depth, fn) | ||
case *parse.TemplateNode, *parse.TextNode: | ||
break | ||
case *parse.IfNode: | ||
cont = r.traverseTreeVisitBranch(&n.BranchNode, depth, fn) | ||
case *parse.RangeNode: | ||
cont = r.traverseTreeVisitBranch(&n.BranchNode, depth, fn) | ||
case *parse.WithNode: | ||
cont = r.traverseTreeVisitBranch(&n.BranchNode, depth, fn) | ||
case *parse.ListNode: | ||
for _, n := range n.Nodes { | ||
if cont = r.traverseTreeVisit(n, depth+1, fn); !cont { | ||
break | ||
} | ||
} | ||
} | ||
|
||
return | ||
} |