diff options
33 files changed, 313 insertions, 151 deletions
diff --git a/commands/import_jekyll.go b/commands/import_jekyll.go index e5c39dc34..b1cc53378 100644 --- a/commands/import_jekyll.go +++ b/commands/import_jekyll.go @@ -30,12 +30,12 @@ import ( "github.com/gohugoio/hugo/parser/metadecoders" + "github.com/gohugoio/hugo/common/maps" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" "github.com/gohugoio/hugo/hugolib" "github.com/gohugoio/hugo/parser" "github.com/spf13/afero" - "github.com/spf13/cast" "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" ) @@ -420,7 +420,7 @@ func convertJekyllPost(s *hugolib.Site, path, relPath, targetDir string, draft b } func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, draft bool) (interface{}, error) { - metadata, err := cast.ToStringMapE(m) + metadata, err := maps.ToStringMapE(m) if err != nil { return nil, err } @@ -472,7 +472,7 @@ func convertJekyllMetaData(m interface{}, postName string, postDate time.Time, d } func convertJekyllContent(m interface{}, content string) string { - metadata, _ := cast.ToStringMapE(m) + metadata, _ := maps.ToStringMapE(m) lines := strings.Split(content, "\n") var resultLines []string diff --git a/common/maps/maps.go b/common/maps/maps.go index e0d4f964d..8b42ca764 100644 --- a/common/maps/maps.go +++ b/common/maps/maps.go @@ -25,26 +25,45 @@ import ( // recursively. // Notes: // * This will modify the map given. -// * Any nested map[interface{}]interface{} will be converted to map[string]interface{}. -func ToLower(m map[string]interface{}) { +// * Any nested map[interface{}]interface{} will be converted to Params. +func ToLower(m Params) { for k, v := range m { + var retyped bool switch v.(type) { case map[interface{}]interface{}: - v = cast.ToStringMap(v) - ToLower(v.(map[string]interface{})) + var p Params = cast.ToStringMap(v) + v = p + ToLower(p) + retyped = true case map[string]interface{}: - ToLower(v.(map[string]interface{})) + var p Params = v.(map[string]interface{}) + v = p + ToLower(p) + retyped = true } lKey := strings.ToLower(k) - if k != lKey { + if retyped || k != lKey { delete(m, k) m[lKey] = v } + } +} +func ToStringMapE(in interface{}) (map[string]interface{}, error) { + switch in.(type) { + case Params: + return in.(Params), nil + default: + return cast.ToStringMapE(in) } } +func ToStringMap(in interface{}) map[string]interface{} { + m, _ := ToStringMapE(in) + return m +} + type keyRename struct { pattern glob.Glob newKey string diff --git a/common/maps/maps_test.go b/common/maps/maps_test.go index 8b0aa5eb9..6e4947adb 100644 --- a/common/maps/maps_test.go +++ b/common/maps/maps_test.go @@ -14,6 +14,7 @@ package maps import ( + "fmt" "reflect" "testing" @@ -21,7 +22,6 @@ import ( ) func TestToLower(t *testing.T) { - tests := []struct { input map[string]interface{} expected map[string]interface{} @@ -30,7 +30,7 @@ func TestToLower(t *testing.T) { map[string]interface{}{ "abC": 32, }, - map[string]interface{}{ + Params{ "abc": 32, }, }, @@ -48,16 +48,16 @@ func TestToLower(t *testing.T) { "J": 25, }, }, - map[string]interface{}{ + Params{ "abc": 32, - "def": map[string]interface{}{ + "def": Params{ "23": "A value", - "24": map[string]interface{}{ + "24": Params{ "abcde": "A value", "efghi": "Another value", }, }, - "ghi": map[string]interface{}{ + "ghi": Params{ "j": 25, }, }, @@ -65,11 +65,13 @@ func TestToLower(t *testing.T) { } for i, test := range tests { - // ToLower modifies input. - ToLower(test.input) - if !reflect.DeepEqual(test.expected, test.input) { - t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input) - } + t.Run(fmt.Sprint(i), func(t *testing.T) { + // ToLower modifies input. + ToLower(test.input) + if !reflect.DeepEqual(test.expected, test.input) { + t.Errorf("[%d] Expected\n%#v, got\n%#v\n", i, test.expected, test.input) + } + }) } } diff --git a/common/maps/params.go b/common/maps/params.go index 2d62ad752..ecb63d7a5 100644 --- a/common/maps/params.go +++ b/common/maps/params.go @@ -19,76 +19,89 @@ import ( "github.com/spf13/cast" ) +// Params is a map where all keys are lower case. +type Params map[string]interface{} + +// Get does a lower case and nested search in this map. +// It will return nil if none found. +func (p Params) Get(indices ...string) interface{} { + v, _, _ := getNested(p, indices) + return v +} + +func getNested(m map[string]interface{}, indices []string) (interface{}, string, map[string]interface{}) { + if len(indices) == 0 { + return nil, "", nil + } + + first := indices[0] + v, found := m[strings.ToLower(cast.ToString(first))] + if !found { + return nil, "", nil + } + + if len(indices) == 1 { + return v, first, m + } + + switch m2 := v.(type) { + case Params: + return getNested(m2, indices[1:]) + case map[string]interface{}: + return getNested(m2, indices[1:]) + default: + return nil, "", nil + } +} + // GetNestedParam gets the first match of the keyStr in the candidates given. // It will first try the exact match and then try to find it as a nested map value, // using the given separator, e.g. "mymap.name". // It assumes that all the maps given have lower cased keys. -func GetNestedParam(keyStr, separator string, candidates ...map[string]interface{}) (interface{}, error) { +func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{}, error) { keyStr = strings.ToLower(keyStr) - lookupFn := func(key string) interface{} { - for _, m := range candidates { - if v, ok := m[key]; ok { - return v - } + // Try exact match first + for _, m := range candidates { + if v, ok := m[keyStr]; ok { + return v, nil } - - return nil - } - - v, _, _, err := GetNestedParamFn(keyStr, separator, lookupFn) - return v, err -} - -func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { - result, _ := traverseDirectParams(keyStr, lookupFn) - if result != nil { - return result, keyStr, nil, nil } keySegments := strings.Split(keyStr, separator) - if len(keySegments) == 1 { - return nil, keyStr, nil, nil + for _, m := range candidates { + if v := m.Get(keySegments...); v != nil { + return v, nil + } } - return traverseNestedParams(keySegments, lookupFn) -} + return nil, nil -func traverseDirectParams(keyStr string, lookupFn func(key string) interface{}) (interface{}, error) { - return lookupFn(keyStr), nil } -func traverseNestedParams(keySegments []string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { - firstKey, rest := keySegments[0], keySegments[1:] - result := lookupFn(firstKey) - if result == nil || len(rest) == 0 { - return result, firstKey, nil, nil +func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { + keySegments := strings.Split(strings.ToLower(keyStr), separator) + if len(keySegments) == 0 { + return nil, "", nil, nil } - switch m := result.(type) { - case map[string]interface{}: - v, key, owner := traverseParams(rest, m) - return v, key, owner, nil - default: + first := lookupFn(keySegments[0]) + if first == nil { return nil, "", nil, nil } -} - -func traverseParams(keys []string, m map[string]interface{}) (interface{}, string, map[string]interface{}) { - // Shift first element off. - firstKey, rest := keys[0], keys[1:] - result := m[firstKey] - // No point in continuing here. - if result == nil { - return result, "", nil + if len(keySegments) == 1 { + return first, keySegments[0], nil, nil } - if len(rest) == 0 { - // That was the last key. - return result, firstKey, m + switch m := first.(type) { + case map[string]interface{}: + v, key, owner := getNested(m, keySegments[1:]) + return v, key, owner, nil + case Params: + v, key, owner := getNested(m, keySegments[1:]) + return v, key, owner, nil } - // That was not the last key. - return traverseParams(rest, cast.ToStringMap(result)) + return nil, "", nil, nil } diff --git a/common/maps/params_test.go b/common/maps/params_test.go index 6477de6f4..8016a8bd6 100644 --- a/common/maps/params_test.go +++ b/common/maps/params_test.go @@ -35,7 +35,7 @@ func TestGetNestedParam(t *testing.T) { c := qt.New(t) - must := func(keyStr, separator string, candidates ...map[string]interface{}) interface{} { + must := func(keyStr, separator string, candidates ...Params) interface{} { v, err := GetNestedParam(keyStr, separator, candidates...) c.Assert(err, qt.IsNil) return v diff --git a/common/para/para.go b/common/para/para.go index 319bdb78f..69bfc205b 100644 --- a/common/para/para.go +++ b/common/para/para.go @@ -37,8 +37,8 @@ type Runner interface { type errGroupRunner struct { *errgroup.Group - w *Workers - ctx context.Context + w *Workers + ctx context.Context } func (g *errGroupRunner) Run(fn func() error) { @@ -68,6 +68,6 @@ func (w *Workers) Start(ctx context.Context) (Runner, context.Context) { return &errGroupRunner{ Group: g, ctx: ctx, - w: w, + w: w, }, ctx } diff --git a/common/para/para_test.go b/common/para/para_test.go index 9f33a234c..bda7f5d27 100644 --- a/common/para/para_test.go +++ b/common/para/para_test.go @@ -15,6 +15,7 @@ package para import ( "context" + "runtime" "sort" "sync" "sync/atomic" @@ -25,6 +26,9 @@ import ( ) func TestPara(t *testing.T) { + if runtime.NumCPU() < 4 { + t.Skipf("skip para test, CPU count is %d", runtime.NumCPU()) + } c := qt.New(t) diff --git a/hugolib/case_insensitive_test.go b/hugolib/case_insensitive_test.go index 5c7dbe073..9c2662044 100644 --- a/hugolib/case_insensitive_test.go +++ b/hugolib/case_insensitive_test.go @@ -61,7 +61,7 @@ angledQuotes = false hrefTargetBlank = false [Languages.en.Colors] BLUE = "blues" -yellow = "golden" +Yellow = "golden" ` caseMixingPage1En = ` --- @@ -137,18 +137,6 @@ func TestCaseInsensitiveConfigurationVariations(t *testing.T) { c := qt.New(t) - // See issues 2615, 1129, 2590 and maybe some others - // Also see 2598 - // - // Viper is now, at least for the Hugo part, case insensitive - // So we need tests for all of it, with needed adjustments on the Hugo side. - // Not sure what that will be. Let us see. - - // So all the below with case variations: - // config: regular fields, blackfriday config, param with nested map - // language: new and overridden values, in regular fields and nested paramsmap - // page frontmatter: regular fields, blackfriday config, param with nested map - mm := afero.NewMemMapFs() caseMixingTestsWriteCommonSources(t, mm) @@ -168,17 +156,27 @@ Block Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }} {{ define "main"}} Page Colors: {{ .Params.CoLOR }}|{{ .Params.Colors.Blue }} Site Colors: {{ .Site.Params.COlOR }}|{{ .Site.Params.COLORS.YELLOW }} +{{ template "index-color" (dict "name" "Page" "params" .Params) }} +{{ template "index-color" (dict "name" "Site" "params" .Site.Params) }} + {{ .Content }} {{ partial "partial.html" . }} {{ end }} +{{ define "index-color" }} +{{ $yellow := index .params "COLoRS" "yELLOW" }} +{{ $colors := index .params "COLoRS" }} +{{ $yellow2 := index $colors "yEllow" }} +index1|{{ .name }}: {{ $yellow }}| +index2|{{ .name }}: {{ $yellow2 }}| +{{ end }} `) writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), ` Page Title: {{ .Title }} Site Title: {{ .Site.Title }} Site Lang Mood: {{ .Site.Language.Params.MOoD }} -Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }} -Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }} +Page Colors: {{ .Params.COLOR }}|{{ .Params.Colors.Blue }}|{{ index .Params "ColOR" }} +Site Colors: {{ .Site.Params.COLOR }}|{{ .Site.Params.COLORS.YELLOW }}|{{ index .Site.Params "ColOR" }} {{ $page2 := .Site.GetPage "/sect2/page2" }} {{ if $page2 }} Page2: {{ $page2.Params.ColoR }} @@ -200,8 +198,8 @@ Page2: {{ $page2.Params.ColoR }} } th.assertFileContent(filepath.Join("public", "nn", "sect1", "page1", "index.html"), - "Page Colors: red|heavenly", - "Site Colors: green|yellow", + "Page Colors: red|heavenly|red", + "Site Colors: green|yellow|green", "Site Lang Mood: Happy", "Shortcode Page: red|heavenly", "Shortcode Site: green|yellow", @@ -230,6 +228,10 @@ Page2: {{ $page2.Params.ColoR }} "Block Page Colors: black|sky", "Partial Page: black|sky", "Partial Site: green|yellow", + "index1|Page: flower|", + "index1|Site: yellow|", + "index2|Page: flower|", + "index2|Site: yellow|", ) } diff --git a/hugolib/page__meta.go b/hugolib/page__meta.go index d137ac340..ca5c7007e 100644 --- a/hugolib/page__meta.go +++ b/hugolib/page__meta.go @@ -228,7 +228,7 @@ func (p *pageMeta) Param(key interface{}) (interface{}, error) { return resource.Param(p, p.s.Info.Params(), key) } -func (p *pageMeta) Params() map[string]interface{} { +func (p *pageMeta) Params() maps.Params { return p.params } @@ -312,7 +312,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte return errors.New("missing frontmatter data") } - pm.params = make(map[string]interface{}) + pm.params = make(maps.Params) if frontmatter != nil { // Needed for case insensitive fetching of params values @@ -320,7 +320,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte if p.IsNode() { // Check for any cascade define on itself. if cv, found := frontmatter["cascade"]; found { - cvm := cast.ToStringMap(cv) + cvm := maps.ToStringMap(cv) if bucket.cascade == nil { bucket.cascade = cvm } else { @@ -479,7 +479,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte } pm.params[loki] = pm.aliases case "sitemap": - p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, cast.ToStringMap(v)) + p.m.sitemap = config.DecodeSitemap(p.s.siteCfg.sitemap, maps.ToStringMap(v)) pm.params[loki] = p.m.sitemap sitemapSet = true case "iscjklanguage": @@ -495,7 +495,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte switch vv := v.(type) { case []map[interface{}]interface{}: for _, vvv := range vv { - resources = append(resources, cast.ToStringMap(vvv)) + resources = append(resources, maps.ToStringMap(vvv)) } case []map[string]interface{}: resources = append(resources, vv...) @@ -503,7 +503,7 @@ func (pm *pageMeta) setMetadata(bucket *pagesMapBucket, p *pageState, frontmatte for _, vvv := range vv { switch vvvv := vvv.(type) { case map[interface{}]interface{}: - resources = append(resources, cast.ToStringMap(vvvv)) + resources = append(resources, maps.ToStringMap(vvvv)) case map[string]interface{}: resources = append(resources, vvvv) } @@ -642,7 +642,7 @@ func (p *pageMeta) applyDefaultValues() error { var renderingConfigOverrides map[string]interface{} bfParam := getParamToLower(p, "blackfriday") if bfParam != nil { - renderingConfigOverrides = cast.ToStringMap(bfParam) + renderingConfigOverrides = maps.ToStringMap(bfParam) } cp := p.s.ContentSpec.Converters.Get(p.markup) @@ -705,14 +705,9 @@ func getParam(m resource.ResourceParamsProvider, key string, stringToLower bool) return helpers.SliceToLower(val) } return v - case map[string]interface{}: // JSON and TOML - return v - case map[interface{}]interface{}: // YAML + default: return v } - - //p.s.Log.ERROR.Printf("GetParam(\"%s\"): Unknown type %s\n", key, reflect.TypeOf(v)) - return nil } func getParamToLower(m resource.ResourceParamsProvider, key string) interface{} { diff --git a/hugolib/page_test.go b/hugolib/page_test.go index 6b9c4193d..abceec9c6 100644 --- a/hugolib/page_test.go +++ b/hugolib/page_test.go @@ -1573,7 +1573,8 @@ baseURL = "https://example.org" {{ $withStringParam := .Site.GetPage "withstringparam" }} Author page: {{ $withParam.Param "author.name" }} -Author page string: {{ $withStringParam.Param "author.name" }}| +Author name page string: {{ $withStringParam.Param "author.name" }}| +Author page string: {{ $withStringParam.Param "author" }}| Author site config: {{ $noParam.Param "author.name" }} `, @@ -1603,8 +1604,10 @@ author = "Jo Nesbø" `) b.Build(BuildCfg{}) - b.AssertFileContent("public/index.html", "Author page: Ernest Miller Hemingway") - b.AssertFileContent("public/index.html", "Author page string: |") - b.AssertFileContent("public/index.html", "Author site config: Kurt Vonnegut") + b.AssertFileContent("public/index.html", + "Author page: Ernest Miller Hemingway", + "Author name page string: Kurt Vonnegut|", + "Author page string: Jo Nesbø|", + "Author site config: Kurt Vonnegut") } diff --git a/hugolib/pages_map.go b/hugolib/pages_map.go index aba1aa4bf..5af86c95d 100644 --- a/hugolib/pages_map.go +++ b/hugolib/pages_map.go @@ -20,6 +20,8 @@ import ( "strings" "sync" + "github.com/gohugoio/hugo/common/maps" + radix "github.com/armon/go-radix" "github.com/spf13/cast" @@ -359,7 +361,7 @@ func (m *pagesMap) cleanKey(key string) string { func (m *pagesMap) mergeCascades(b1, b2 *pagesMapBucket) { if b1.cascade == nil { - b1.cascade = make(map[string]interface{}) + b1.cascade = make(maps.Params) } if b2 != nil && b2.cascade != nil { for k, v := range b2.cascade { diff --git a/hugolib/site.go b/hugolib/site.go index db0cd2ea5..0b45c4803 100644 --- a/hugolib/site.go +++ b/hugolib/site.go @@ -28,6 +28,8 @@ import ( "strings" "time" + "github.com/gohugoio/hugo/resources/resource" + "github.com/gohugoio/hugo/markup/converter" "github.com/gohugoio/hugo/hugofs/files" @@ -581,7 +583,7 @@ func (s *SiteInfo) Taxonomies() interface{} { return s.s.Taxonomies } -func (s *SiteInfo) Params() map[string]interface{} { +func (s *SiteInfo) Params() maps.Params { return s.s.Language().Params() } @@ -654,14 +656,9 @@ type SiteSocial map[string]string // Param is a convenience method to do lookups in SiteInfo's Params map. // -// This method is also implemented on Page and Node. +// This method is also implemented on Page. func (s *SiteInfo) Param(key interface{}) (interface{}, error) { - keyStr, err := cast.ToStringE(key) - if err != nil { - return nil, err - } - keyStr = strings.ToLower(keyStr) - return s.Params()[keyStr], nil + return resource.Param(s, nil, key) } func (s *SiteInfo) IsMultiLingual() bool { @@ -1272,7 +1269,7 @@ func (s *Site) getMenusFromConfig() navigation.Menus { s.Log.DEBUG.Printf("found menu: %q, in site config\n", name) menuEntry := navigation.MenuEntry{Menu: name} - ime, err := cast.ToStringMapE(entry) + ime, err := maps.ToStringMapE(entry) if err != nil { s.Log.ERROR.Printf("unable to process menus in site config\n") s.Log.ERROR.Println(err) diff --git a/langs/config.go b/langs/config.go index 927f3558f..184223650 100644 --- a/langs/config.go +++ b/ |