mirror of
https://github.com/gohugoio/hugo.git
synced 2025-03-15 21:24:09 +00:00
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
This commit is contained in:
parent
56c61559b2
commit
5c5384916e
2 changed files with 150 additions and 75 deletions
|
@ -24,15 +24,14 @@ import (
|
||||||
// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
|
// decl keeps track of the variable mappings, i.e. $mysite => .Site etc.
|
||||||
type decl map[string]string
|
type decl map[string]string
|
||||||
|
|
||||||
var paramsPaths = [][]string{
|
const (
|
||||||
{"Params"},
|
paramsIdentifier = "Params"
|
||||||
{"Site", "Params"},
|
)
|
||||||
|
|
||||||
// Site and Pag referenced from shortcodes
|
// Containers that may contain Params that we will not touch.
|
||||||
{"Page", "Site", "Params"},
|
var reservedContainers = map[string]bool{
|
||||||
{"Page", "Params"},
|
// Aka .Site.Data.Params which must stay case sensitive.
|
||||||
|
"Data": true,
|
||||||
{"Site", "Language", "Params"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type templateContext struct {
|
type templateContext struct {
|
||||||
|
@ -155,6 +154,7 @@ func (c *templateContext) updateIdentsIfNeeded(idents []string) {
|
||||||
for i := index; i < len(idents); i++ {
|
for i := index; i < len(idents); i++ {
|
||||||
idents[i] = strings.ToLower(idents[i])
|
idents[i] = strings.ToLower(idents[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// indexOfReplacementStart will return the index of where to start doing replacement,
|
// indexOfReplacementStart will return the index of where to start doing replacement,
|
||||||
|
@ -167,31 +167,101 @@ func (d decl) indexOfReplacementStart(idents []string) int {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
first := idents[0]
|
if l == 1 {
|
||||||
firstIsVar := first[0] == '$'
|
first := idents[0]
|
||||||
|
if first == "" || first == paramsIdentifier || first[0] == '$' {
|
||||||
if l == 1 && !firstIsVar {
|
// This can not be a Params.x
|
||||||
// This can not be a Params.x
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
if !firstIsVar {
|
|
||||||
found := false
|
|
||||||
for _, paramsPath := range paramsPaths {
|
|
||||||
if first == paramsPath[0] {
|
|
||||||
found = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 !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 (
|
var (
|
||||||
resolvedIdents []string
|
replacements []string
|
||||||
replacements []string
|
replaced []string
|
||||||
replaced []string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// An Ident can start out as one of
|
// An Ident can start out as one of
|
||||||
|
@ -206,7 +276,7 @@ func (d decl) indexOfReplacementStart(idents []string) int {
|
||||||
|
|
||||||
if i > 20 {
|
if i > 20 {
|
||||||
// bail out
|
// bail out
|
||||||
return -1
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
potentialVar := replacements[i]
|
potentialVar := replacements[i]
|
||||||
|
@ -225,7 +295,7 @@ func (d decl) indexOfReplacementStart(idents []string) int {
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
// Temporary range vars. We do not care about those.
|
// Temporary range vars. We do not care about those.
|
||||||
return -1
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
replacement = strings.TrimPrefix(replacement, ".")
|
replacement = strings.TrimPrefix(replacement, ".")
|
||||||
|
@ -242,52 +312,10 @@ func (d decl) indexOfReplacementStart(idents []string) int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
func (d decl) isKeyword(s string) bool {
|
||||||
if !sliceStartsWith(resolvedIdents, words...) {
|
return !strings.ContainsAny(s, " -\"")
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ package tplimpl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -24,6 +25,11 @@ import (
|
||||||
var (
|
var (
|
||||||
testFuncs = map[string]interface{}{
|
testFuncs = map[string]interface{}{
|
||||||
"Echo": func(v interface{}) interface{} { return v },
|
"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{}{
|
paramsData = map[string]interface{}{
|
||||||
|
@ -31,6 +37,15 @@ var (
|
||||||
"Slice": []int{1, 3},
|
"Slice": []int{1, 3},
|
||||||
"Params": map[string]interface{}{
|
"Params": map[string]interface{}{
|
||||||
"lower": "P1L",
|
"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{}{
|
"Site": map[string]interface{}{
|
||||||
"Params": map[string]interface{}{
|
"Params": map[string]interface{}{
|
||||||
|
@ -52,12 +67,14 @@ var (
|
||||||
|
|
||||||
paramsTempl = `
|
paramsTempl = `
|
||||||
{{ $page := . }}
|
{{ $page := . }}
|
||||||
|
{{ $pages := .Pages }}
|
||||||
{{ $pageParams := .Params }}
|
{{ $pageParams := .Params }}
|
||||||
{{ $site := .Site }}
|
{{ $site := .Site }}
|
||||||
{{ $siteParams := .Site.Params }}
|
{{ $siteParams := .Site.Params }}
|
||||||
{{ $data := .Site.Data }}
|
{{ $data := .Site.Data }}
|
||||||
{{ $notparam := .NotParam }}
|
{{ $notparam := .NotParam }}
|
||||||
|
|
||||||
|
PCurrentSection: {{ .CurrentSection.Params.LOWER }}
|
||||||
P1: {{ .Params.LOWER }}
|
P1: {{ .Params.LOWER }}
|
||||||
P1_2: {{ $.Params.LOWER }}
|
P1_2: {{ $.Params.LOWER }}
|
||||||
P1_3: {{ $page.Params.LOWER }}
|
P1_3: {{ $page.Params.LOWER }}
|
||||||
|
@ -109,6 +126,15 @@ RANGE: {{ . }}: {{ $.Params.LOWER }}
|
||||||
F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
|
F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
|
||||||
F2: {{ Echo (printf "themes/%s-theme" $lower) }}
|
F2: {{ Echo (printf "themes/%s-theme" $lower) }}
|
||||||
F3: {{ Echo (printf "themes/%s-theme" .Site.Params.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 @@ func TestParamsKeysToLower(t *testing.T) {
|
||||||
require.Contains(t, result, "F2: themes/P2L-theme")
|
require.Contains(t, result, "F2: themes/P2L-theme")
|
||||||
require.Contains(t, result, "F3: 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) {
|
func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
|
||||||
|
@ -197,6 +231,9 @@ func TestParamsKeysToLowerVars(t *testing.T) {
|
||||||
"Params": map[string]interface{}{
|
"Params": map[string]interface{}{
|
||||||
"colors": map[string]interface{}{
|
"colors": map[string]interface{}{
|
||||||
"blue": "Amber",
|
"blue": "Amber",
|
||||||
|
"pretty": map[string]interface{}{
|
||||||
|
"first": "Indigo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -205,8 +242,14 @@ func TestParamsKeysToLowerVars(t *testing.T) {
|
||||||
paramsTempl = `
|
paramsTempl = `
|
||||||
{{$__amber_1 := .Params.Colors}}
|
{{$__amber_1 := .Params.Colors}}
|
||||||
{{$__amber_2 := $__amber_1.Blue}}
|
{{$__amber_2 := $__amber_1.Blue}}
|
||||||
|
{{$__amber_3 := $__amber_1.Pretty}}
|
||||||
|
{{$__amber_4 := .Params}}
|
||||||
|
|
||||||
Color: {{$__amber_2}}
|
Color: {{$__amber_2}}
|
||||||
Blue: {{ $__amber_1.Blue}}
|
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 @@ Blue: {{ $__amber_1.Blue}}
|
||||||
result := b.String()
|
result := b.String()
|
||||||
|
|
||||||
require.Contains(t, result, "Color: Amber")
|
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")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue