ref: 5c5384916e8f954f3ea66148ecceb3732584588e
parent: 56c61559b2a9f9c4cec3f6c6de9bcc5095a78b57
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Mon Aug 13 16:50:07 EDT 2018
tpl/tplimpl: Reimplement the ".Params tolower" template transformer All `.Params` are stored lowercase, but it should work to access them `.Page.camelCase` etc. There was, however, some holes in the logic with the old transformer. This commit fixes that by applying a blacklist instead of the old whitelist logic. `.Param` is a very distinct key. The original case will be kept in `.Data.Params.myParam`, but other than that it will be lowercased. Fixes #5068
--- a/tpl/tplimpl/template_ast_transformers.go
+++ b/tpl/tplimpl/template_ast_transformers.go
@@ -24,15 +24,14 @@
// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
type decl map[string]string
-var paramsPaths = [][]string{- {"Params"},- {"Site", "Params"},+const (
+ paramsIdentifier = "Params"
+)
- // Site and Pag referenced from shortcodes
- {"Page", "Site", "Params"},- {"Page", "Params"},-
- {"Site", "Language", "Params"},+// Containers that may contain Params that we will not touch.
+var reservedContainers = map[string]bool{+ // Aka .Site.Data.Params which must stay case sensitive.
+ "Data": true,
}
type templateContext struct {@@ -155,6 +154,7 @@
for i := index; i < len(idents); i++ {idents[i] = strings.ToLower(idents[i])
}
+
}
// indexOfReplacementStart will return the index of where to start doing replacement,
@@ -167,31 +167,101 @@
return -1
}
- first := idents[0]
- firstIsVar := first[0] == '$'
+ if l == 1 {+ first := idents[0]
+ if first == "" || first == paramsIdentifier || first[0] == '$' {+ // This can not be a Params.x
+ return -1
+ }
+ }
- if l == 1 && !firstIsVar {- // This can not be a Params.x
+ var lookFurther bool
+ var needsVarExpansion bool
+ for _, ident := range idents {+ if ident[0] == '$' {+ lookFurther = true
+ needsVarExpansion = true
+ break
+ } else if ident == paramsIdentifier {+ lookFurther = true
+ break
+ }
+ }
+
+ if !lookFurther {return -1
}
- if !firstIsVar {- found := false
- for _, paramsPath := range paramsPaths {- if first == paramsPath[0] {- found = true
- break
+ var resolvedIdents []string
+
+ if !needsVarExpansion {+ resolvedIdents = idents
+ } else {+ var ok bool
+ resolvedIdents, ok = d.resolveVariables(idents)
+ if !ok {+ return -1
+ }
+ }
+
+ var paramFound bool
+ for i, ident := range resolvedIdents {+ if ident == paramsIdentifier {+ if i > 0 {+ container := resolvedIdents[i-1]
+ if reservedContainers[container] {+ // .Data.Params.someKey
+ return -1
+ }
+ if !d.isKeyword(container) {+ // where $pages ".Params.toc_hide" "!=" true
+ return -1
+ }
}
+ if i < len(resolvedIdents)-1 {+ next := resolvedIdents[i+1]
+ if !d.isKeyword(next) {+ return -1
+ }
+ }
+
+ paramFound = true
+ break
}
- if !found {- return -1
+ }
+
+ if !paramFound {+ return -1
+ }
+
+ var paramSeen bool
+ idx := -1
+ for i, ident := range idents {+ if ident == "" || ident[0] == '$' {+ continue
}
+
+ if ident == paramsIdentifier {+ paramSeen = true
+ idx = -1
+
+ } else {+ if paramSeen {+ return i
+ }
+ if idx == -1 {+ idx = i
+ }
+ }
}
+ return idx
+}
+
+func (d decl) resolveVariables(idents []string) ([]string, bool) {var (
- resolvedIdents []string
- replacements []string
- replaced []string
+ replacements []string
+ replaced []string
)
// An Ident can start out as one of
@@ -206,7 +276,7 @@
if i > 20 {// bail out
- return -1
+ return nil, false
}
potentialVar := replacements[i]
@@ -225,7 +295,7 @@
if !ok {// Temporary range vars. We do not care about those.
- return -1
+ return nil, false
}
replacement = strings.TrimPrefix(replacement, ".")
@@ -242,52 +312,10 @@
}
}
- resolvedIdents = append(replaced, idents[1:]...)
+ return append(replaced, idents[1:]...), true
- for _, paramPath := range paramsPaths {- if index := indexOfFirstRealIdentAfterWords(resolvedIdents, idents, paramPath...); index != -1 {- return index
- }
- }
-
- return -1
-
}
-func indexOfFirstRealIdentAfterWords(resolvedIdents, idents []string, words ...string) int {- if !sliceStartsWith(resolvedIdents, words...) {- return -1
- }
-
- for i, ident := range idents {- if ident == "" || ident[0] == '$' {- continue
- }
- found := true
- for _, word := range words {- if ident == word {- found = false
- break
- }
- }
- if found {- return i
- }
- }
-
- return -1
-}
-
-func sliceStartsWith(slice []string, words ...string) bool {-
- if len(slice) < len(words) {- return false
- }
-
- for i, word := range words {- if word != slice[i] {- return false
- }
- }
- return true
+func (d decl) isKeyword(s string) bool {+ return !strings.ContainsAny(s, " -\"")
}
--- a/tpl/tplimpl/template_ast_transformers_test.go
+++ b/tpl/tplimpl/template_ast_transformers_test.go
@@ -14,6 +14,7 @@
import (
"bytes"
+ "fmt"
"testing"
"html/template"
@@ -24,6 +25,11 @@
var (
testFuncs = map[string]interface{}{ "Echo": func(v interface{}) interface{} { return v },+ "where": func(seq, key interface{}, args ...interface{}) (interface{}, error) {+ return map[string]interface{}{+ "ByWeight": fmt.Sprintf("%v:%v:%v", seq, key, args),+ }, nil
+ },
}
paramsData = map[string]interface{}{@@ -31,7 +37,16 @@
"Slice": []int{1, 3}, "Params": map[string]interface{}{"lower": "P1L",
+ "slice": []int{1, 3},},
+ "Pages": map[string]interface{}{+ "ByWeight": []int{1, 3},+ },
+ "CurrentSection": map[string]interface{}{+ "Params": map[string]interface{}{+ "lower": "pcurrentsection",
+ },
+ },
"Site": map[string]interface{}{ "Params": map[string]interface{}{"lower": "P2L",
@@ -52,6 +67,7 @@
paramsTempl = `
{{ $page := . }}+{{ $pages := .Pages }} {{ $pageParams := .Params }} {{ $site := .Site }} {{ $siteParams := .Site.Params }}@@ -58,6 +74,7 @@
{{ $data := .Site.Data }} {{ $notparam := .NotParam }}+PCurrentSection: {{ .CurrentSection.Params.LOWER }} P1: {{ .Params.LOWER }} P1_2: {{ $.Params.LOWER }} P1_3: {{ $page.Params.LOWER }}@@ -109,6 +126,15 @@
F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }} F2: {{ Echo (printf "themes/%s-theme" $lower) }} F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}+
+PSLICE: {{ range .Params.SLICE }}PSLICE{{.}}|{{ end }}+
+{{ $pages := "foo" }}+{{ $pages := where $pages ".Params.toc_hide" "!=" true }}+PARAMS STRING: {{ $pages.ByWeight }}+PARAMS STRING2: {{ with $pages }}{{ .ByWeight }}{{ end }}+{{ $pages3 := where ".Params.TOC_HIDE" "!=" .Params.LOWER }}+PARAMS STRING3: {{ $pages3.ByWeight }}`
)
@@ -164,6 +190,14 @@
require.Contains(t, result, "F2: themes/P2L-theme")
require.Contains(t, result, "F3: themes/P2L-theme")
+ require.Contains(t, result, "PSLICE: PSLICE1|PSLICE3|")
+ require.Contains(t, result, "PARAMS STRING: foo:.Params.toc_hide:[!= true]")
+ require.Contains(t, result, "PARAMS STRING2: foo:.Params.toc_hide:[!= true]")
+ require.Contains(t, result, "PARAMS STRING3: .Params.TOC_HIDE:!=:[P1L]")
+
+ // Issue #5068
+ require.Contains(t, result, "PCurrentSection: pcurrentsection")
+
}
func BenchmarkTemplateParamsKeysToLower(b *testing.B) {@@ -197,6 +231,9 @@
"Params": map[string]interface{}{ "colors": map[string]interface{}{"blue": "Amber",
+ "pretty": map[string]interface{}{+ "first": "Indigo",
+ },
},
},
}
@@ -205,8 +242,14 @@
paramsTempl = `
{{$__amber_1 := .Params.Colors}} {{$__amber_2 := $__amber_1.Blue}}+{{$__amber_3 := $__amber_1.Pretty}}+{{$__amber_4 := .Params}}+
Color: {{$__amber_2}} Blue: {{ $__amber_1.Blue}}+Pretty First1: {{ $__amber_3.First}}+Pretty First2: {{ $__amber_1.Pretty.First}}+Pretty First3: {{ $__amber_4.COLORS.PRETTY.FIRST}}`
)
@@ -225,6 +268,10 @@
result := b.String()
require.Contains(t, result, "Color: Amber")
+ require.Contains(t, result, "Blue: Amber")
+ require.Contains(t, result, "Pretty First1: Indigo")
+ require.Contains(t, result, "Pretty First2: Indigo")
+ require.Contains(t, result, "Pretty First3: Indigo")
}
--
⑨