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 }