github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/resources/page/page_matcher.go (about) 1 // Copyright 2020 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package page 15 16 import ( 17 "fmt" 18 "path/filepath" 19 "strings" 20 21 "github.com/gohugoio/hugo/common/maps" 22 "github.com/gohugoio/hugo/config" 23 "github.com/gohugoio/hugo/hugofs/glob" 24 "github.com/gohugoio/hugo/resources/kinds" 25 "github.com/mitchellh/mapstructure" 26 ) 27 28 // A PageMatcher can be used to match a Page with Glob patterns. 29 // Note that the pattern matching is case insensitive. 30 type PageMatcher struct { 31 // A Glob pattern matching the content path below /content. 32 // Expects Unix-styled slashes. 33 // Note that this is the virtual path, so it starts at the mount root 34 // with a leading "/". 35 Path string 36 37 // A Glob pattern matching the Page's Kind(s), e.g. "{home,section}" 38 Kind string 39 40 // A Glob pattern matching the Page's language, e.g. "{en,sv}". 41 Lang string 42 43 // A Glob pattern matching the Page's Environment, e.g. "{production,development}". 44 Environment string 45 } 46 47 // Matches returns whether p matches this matcher. 48 func (m PageMatcher) Matches(p Page) bool { 49 if m.Kind != "" { 50 g, err := glob.GetGlob(m.Kind) 51 if err == nil && !g.Match(p.Kind()) { 52 return false 53 } 54 } 55 56 if m.Lang != "" { 57 g, err := glob.GetGlob(m.Lang) 58 if err == nil && !g.Match(p.Lang()) { 59 return false 60 } 61 } 62 63 if m.Path != "" { 64 g, err := glob.GetGlob(m.Path) 65 // TODO(bep) Path() vs filepath vs leading slash. 66 p := strings.ToLower(filepath.ToSlash(p.Pathc())) 67 if !(strings.HasPrefix(p, "/")) { 68 p = "/" + p 69 } 70 if err == nil && !g.Match(p) { 71 return false 72 } 73 } 74 75 if m.Environment != "" { 76 g, err := glob.GetGlob(m.Environment) 77 if err == nil && !g.Match(p.Site().Hugo().Environment) { 78 return false 79 } 80 } 81 82 return true 83 } 84 85 func DecodeCascadeConfig(in any) (*config.ConfigNamespace[[]PageMatcherParamsConfig, map[PageMatcher]maps.Params], error) { 86 buildConfig := func(in any) (map[PageMatcher]maps.Params, any, error) { 87 cascade := make(map[PageMatcher]maps.Params) 88 if in == nil { 89 return cascade, []map[string]any{}, nil 90 } 91 ms, err := maps.ToSliceStringMap(in) 92 if err != nil { 93 return nil, nil, err 94 } 95 96 var cfgs []PageMatcherParamsConfig 97 98 for _, m := range ms { 99 m = maps.CleanConfigStringMap(m) 100 c, err := mapToPageMatcherParamsConfig(m) 101 if err != nil { 102 return nil, nil, err 103 } 104 cfgs = append(cfgs, c) 105 } 106 107 for _, cfg := range cfgs { 108 m := cfg.Target 109 c, found := cascade[m] 110 if found { 111 // Merge 112 for k, v := range cfg.Params { 113 if _, found := c[k]; !found { 114 c[k] = v 115 } 116 } 117 } else { 118 cascade[m] = cfg.Params 119 } 120 } 121 122 return cascade, cfgs, nil 123 } 124 125 return config.DecodeNamespace[[]PageMatcherParamsConfig](in, buildConfig) 126 127 } 128 129 // DecodeCascade decodes in which could be either a map or a slice of maps. 130 func DecodeCascade(in any) (map[PageMatcher]maps.Params, error) { 131 conf, err := DecodeCascadeConfig(in) 132 if err != nil { 133 return nil, err 134 } 135 return conf.Config, nil 136 } 137 138 func mapToPageMatcherParamsConfig(m map[string]any) (PageMatcherParamsConfig, error) { 139 var pcfg PageMatcherParamsConfig 140 for k, v := range m { 141 switch strings.ToLower(k) { 142 case "params": 143 // We simplified the structure of the cascade config in Hugo 0.111.0. 144 // There is a small chance that someone has used the old structure with the params keyword, 145 // those values will now be moved to the top level. 146 // This should be very unlikely as it would lead to constructs like .Params.params.foo, 147 // and most people see params as an Hugo internal keyword. 148 pcfg.Params = maps.ToStringMap(v) 149 case "_target", "target": 150 var target PageMatcher 151 if err := decodePageMatcher(v, &target); err != nil { 152 return pcfg, err 153 } 154 pcfg.Target = target 155 default: 156 // Legacy config. 157 if pcfg.Params == nil { 158 pcfg.Params = make(maps.Params) 159 } 160 pcfg.Params[k] = v 161 } 162 } 163 return pcfg, pcfg.init() 164 165 } 166 167 // decodePageMatcher decodes m into v. 168 func decodePageMatcher(m any, v *PageMatcher) error { 169 if err := mapstructure.WeakDecode(m, v); err != nil { 170 return err 171 } 172 173 v.Kind = strings.ToLower(v.Kind) 174 if v.Kind != "" { 175 g, _ := glob.GetGlob(v.Kind) 176 found := false 177 for _, k := range kinds.AllKindsInPages { 178 if g.Match(k) { 179 found = true 180 break 181 } 182 } 183 if !found { 184 return fmt.Errorf("%q did not match a valid Page Kind", v.Kind) 185 } 186 } 187 188 v.Path = filepath.ToSlash(strings.ToLower(v.Path)) 189 190 return nil 191 } 192 193 type PageMatcherParamsConfig struct { 194 // Apply Params to all Pages matching Target. 195 Params maps.Params 196 Target PageMatcher 197 } 198 199 func (p *PageMatcherParamsConfig) init() error { 200 maps.PrepareParams(p.Params) 201 return nil 202 }