segments.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
segments.go (6701B)
---
1 // Copyright 2024 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 segments
15
16 import (
17 "fmt"
18
19 "github.com/gobwas/glob"
20 "github.com/gohugoio/hugo/common/maps"
21 "github.com/gohugoio/hugo/common/predicate"
22 "github.com/gohugoio/hugo/config"
23 hglob "github.com/gohugoio/hugo/hugofs/glob"
24 "github.com/mitchellh/mapstructure"
25 )
26
27 // Segments is a collection of named segments.
28 type Segments struct {
29 s map[string]excludeInclude
30 }
31
32 type excludeInclude struct {
33 exclude predicate.P[SegmentMatcherFields]
34 include predicate.P[SegmentMatcherFields]
35 }
36
37 // ShouldExcludeCoarse returns whether the given fields should be excluded.
38 // This is used for the coarser grained checks, e.g. language and output format.
39 // Note that ShouldExcludeCoarse(fields) == ShouldExcludeFine(fields) may
40 // not always be true, but ShouldExcludeCoarse(fields) == true == ShouldExcludeFine(fields)
41 // will always be truthful.
42 func (e excludeInclude) ShouldExcludeCoarse(fields SegmentMatcherFields) bool {
43 return e.exclude != nil && e.exclude(fields)
44 }
45
46 // ShouldExcludeFine returns whether the given fields should be excluded.
47 // This is used for the finer grained checks, e.g. on individual pages.
48 func (e excludeInclude) ShouldExcludeFine(fields SegmentMatcherFields) bool {
49 if e.exclude != nil && e.exclude(fields) {
50 return true
51 }
52 return e.include != nil && !e.include(fields)
53 }
54
55 type SegmentFilter interface {
56 // ShouldExcludeCoarse returns whether the given fields should be excluded on a coarse level.
57 ShouldExcludeCoarse(SegmentMatcherFields) bool
58
59 // ShouldExcludeFine returns whether the given fields should be excluded on a fine level.
60 ShouldExcludeFine(SegmentMatcherFields) bool
61 }
62
63 type segmentFilter struct {
64 coarse predicate.P[SegmentMatcherFields]
65 fine predicate.P[SegmentMatcherFields]
66 }
67
68 func (f segmentFilter) ShouldExcludeCoarse(field SegmentMatcherFields) bool {
69 return f.coarse(field)
70 }
71
72 func (f segmentFilter) ShouldExcludeFine(fields SegmentMatcherFields) bool {
73 return f.fine(fields)
74 }
75
76 var (
77 matchAll = func(SegmentMatcherFields) bool { return true }
78 matchNothing = func(SegmentMatcherFields) bool { return false }
79 )
80
81 // Get returns a SegmentFilter for the given segments.
82 func (sms Segments) Get(onNotFound func(s string), ss ...string) SegmentFilter {
83 if ss == nil {
84 return segmentFilter{coarse: matchNothing, fine: matchNothing}
85 }
86 var sf segmentFilter
87 for _, s := range ss {
88 if seg, ok := sms.s[s]; ok {
89 if sf.coarse == nil {
90 sf.coarse = seg.ShouldExcludeCoarse
91 } else {
92 sf.coarse = sf.coarse.Or(seg.ShouldExcludeCoarse)
93 }
94 if sf.fine == nil {
95 sf.fine = seg.ShouldExcludeFine
96 } else {
97 sf.fine = sf.fine.Or(seg.ShouldExcludeFine)
98 }
99 } else if onNotFound != nil {
100 onNotFound(s)
101 }
102 }
103
104 if sf.coarse == nil {
105 sf.coarse = matchAll
106 }
107 if sf.fine == nil {
108 sf.fine = matchAll
109 }
110
111 return sf
112 }
113
114 type SegmentConfig struct {
115 Excludes []SegmentMatcherFields
116 Includes []SegmentMatcherFields
117 }
118
119 // SegmentMatcherFields is a matcher for a segment include or exclude.
120 // All of these are Glob patterns.
121 type SegmentMatcherFields struct {
122 Kind string
123 Path string
124 Lang string
125 Output string
126 }
127
128 func getGlob(s string) (glob.Glob, error) {
129 if s == "" {
130 return nil, nil
131 }
132 g, err := hglob.GetGlob(s)
133 if err != nil {
134 return nil, fmt.Errorf("failed to compile Glob %q: %w", s, err)
135 }
136 return g, nil
137 }
138
139 func compileSegments(f []SegmentMatcherFields) (predicate.P[SegmentMatcherFields], error) {
140 if f == nil {
141 return func(SegmentMatcherFields) bool { return false }, nil
142 }
143 var (
144 result predicate.P[SegmentMatcherFields]
145 section predicate.P[SegmentMatcherFields]
146 )
147
148 addToSection := func(matcherFields SegmentMatcherFields, f func(fields SegmentMatcherFields) string) error {
149 s1 := f(matcherFields)
150 g, err := getGlob(s1)
151 if err != nil {
152 return err
153 }
154 matcher := func(fields SegmentMatcherFields) bool {
155 s2 := f(fields)
156 if s2 == "" {
157 return false
158 }
159 return g.Match(s2)
160 }
161 if section == nil {
162 section = matcher
163 } else {
164 section = section.And(matcher)
165 }
166 return nil
167 }
168
169 for _, fields := range f {
170 if fields.Kind != "" {
171 if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Kind }); err != nil {
172 return result, err
173 }
174 }
175 if fields.Path != "" {
176 if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Path }); err != nil {
177 return result, err
178 }
179 }
180 if fields.Lang != "" {
181 if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Lang }); err != nil {
182 return result, err
183 }
184 }
185 if fields.Output != "" {
186 if err := addToSection(fields, func(fields SegmentMatcherFields) string { return fields.Output }); err != nil {
187 return result, err
188 }
189 }
190
191 if result == nil {
192 result = section
193 } else {
194 result = result.Or(section)
195 }
196 section = nil
197
198 }
199
200 return result, nil
201 }
202
203 func DecodeSegments(in map[string]any) (*config.ConfigNamespace[map[string]SegmentConfig, Segments], error) {
204 buildConfig := func(in any) (Segments, any, error) {
205 sms := Segments{
206 s: map[string]excludeInclude{},
207 }
208 m, err := maps.ToStringMapE(in)
209 if err != nil {
210 return sms, nil, err
211 }
212 if m == nil {
213 m = map[string]any{}
214 }
215 m = maps.CleanConfigStringMap(m)
216
217 var scfgm map[string]SegmentConfig
218 if err := mapstructure.Decode(m, &scfgm); err != nil {
219 return sms, nil, err
220 }
221
222 for k, v := range scfgm {
223 var (
224 include predicate.P[SegmentMatcherFields]
225 exclude predicate.P[SegmentMatcherFields]
226 err error
227 )
228 if v.Excludes != nil {
229 exclude, err = compileSegments(v.Excludes)
230 if err != nil {
231 return sms, nil, err
232 }
233 }
234 if v.Includes != nil {
235 include, err = compileSegments(v.Includes)
236 if err != nil {
237 return sms, nil, err
238 }
239 }
240
241 ei := excludeInclude{
242 exclude: exclude,
243 include: include,
244 }
245 sms.s[k] = ei
246
247 }
248
249 return sms, nil, nil
250 }
251
252 ns, err := config.DecodeNamespace[map[string]SegmentConfig](in, buildConfig)
253 if err != nil {
254 return nil, fmt.Errorf("failed to decode segments: %w", err)
255 }
256 return ns, nil
257 }