page_matcher.go (3364B)
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/hugofs/glob" 23 "github.com/mitchellh/mapstructure" 24 ) 25 26 // A PageMatcher can be used to match a Page with Glob patterns. 27 // Note that the pattern matching is case insensitive. 28 type PageMatcher struct { 29 // A Glob pattern matching the content path below /content. 30 // Expects Unix-styled slashes. 31 // Note that this is the virtual path, so it starts at the mount root 32 // with a leading "/". 33 Path string 34 35 // A Glob pattern matching the Page's Kind(s), e.g. "{home,section}" 36 Kind string 37 38 // A Glob pattern matching the Page's language, e.g. "{en,sv}". 39 Lang string 40 41 // A Glob pattern matching the Page's Environment, e.g. "{production,development}". 42 Environment string 43 } 44 45 // Matches returns whether p matches this matcher. 46 func (m PageMatcher) Matches(p Page) bool { 47 if m.Kind != "" { 48 g, err := glob.GetGlob(m.Kind) 49 if err == nil && !g.Match(p.Kind()) { 50 return false 51 } 52 } 53 54 if m.Lang != "" { 55 g, err := glob.GetGlob(m.Lang) 56 if err == nil && !g.Match(p.Lang()) { 57 return false 58 } 59 } 60 61 if m.Path != "" { 62 g, err := glob.GetGlob(m.Path) 63 // TODO(bep) Path() vs filepath vs leading slash. 64 p := strings.ToLower(filepath.ToSlash(p.Pathc())) 65 if !(strings.HasPrefix(p, "/")) { 66 p = "/" + p 67 } 68 if err == nil && !g.Match(p) { 69 return false 70 } 71 } 72 73 if m.Environment != "" { 74 g, err := glob.GetGlob(m.Environment) 75 if err == nil && !g.Match(p.Site().Hugo().Environment) { 76 return false 77 } 78 } 79 80 return true 81 } 82 83 // DecodeCascade decodes in which could be either a map or a slice of maps. 84 func DecodeCascade(in any) (map[PageMatcher]maps.Params, error) { 85 m, err := maps.ToSliceStringMap(in) 86 if err != nil { 87 return map[PageMatcher]maps.Params{ 88 {}: maps.ToStringMap(in), 89 }, nil 90 } 91 92 cascade := make(map[PageMatcher]maps.Params) 93 94 for _, vv := range m { 95 var m PageMatcher 96 if mv, found := vv["_target"]; found { 97 err := DecodePageMatcher(mv, &m) 98 if err != nil { 99 return nil, err 100 } 101 } 102 c, found := cascade[m] 103 if found { 104 // Merge 105 for k, v := range vv { 106 if _, found := c[k]; !found { 107 c[k] = v 108 } 109 } 110 } else { 111 cascade[m] = vv 112 } 113 } 114 115 return cascade, nil 116 } 117 118 // DecodePageMatcher decodes m into v. 119 func DecodePageMatcher(m any, v *PageMatcher) error { 120 if err := mapstructure.WeakDecode(m, v); err != nil { 121 return err 122 } 123 124 v.Kind = strings.ToLower(v.Kind) 125 if v.Kind != "" { 126 g, _ := glob.GetGlob(v.Kind) 127 found := false 128 for _, k := range kindMap { 129 if g.Match(k) { 130 found = true 131 break 132 } 133 } 134 if !found { 135 return fmt.Errorf("%q did not match a valid Page Kind", v.Kind) 136 } 137 } 138 139 v.Path = filepath.ToSlash(strings.ToLower(v.Path)) 140 141 return nil 142 }