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 }