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  }