github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/defaultConfigProvider.go (about)

     1  // Copyright 2021 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 config
    15  
    16  import (
    17  	"fmt"
    18  	"sort"
    19  	"strings"
    20  	"sync"
    21  
    22  	xmaps "golang.org/x/exp/maps"
    23  
    24  	"github.com/spf13/cast"
    25  
    26  	"github.com/gohugoio/hugo/common/maps"
    27  )
    28  
    29  var (
    30  
    31  	// ConfigRootKeysSet contains all of the config map root keys.
    32  	ConfigRootKeysSet = map[string]bool{
    33  		"build":         true,
    34  		"caches":        true,
    35  		"cascade":       true,
    36  		"frontmatter":   true,
    37  		"languages":     true,
    38  		"imaging":       true,
    39  		"markup":        true,
    40  		"mediatypes":    true,
    41  		"menus":         true,
    42  		"minify":        true,
    43  		"module":        true,
    44  		"outputformats": true,
    45  		"params":        true,
    46  		"permalinks":    true,
    47  		"related":       true,
    48  		"sitemap":       true,
    49  		"privacy":       true,
    50  		"security":      true,
    51  		"taxonomies":    true,
    52  	}
    53  
    54  	// ConfigRootKeys is a sorted version of ConfigRootKeysSet.
    55  	ConfigRootKeys []string
    56  )
    57  
    58  func init() {
    59  	for k := range ConfigRootKeysSet {
    60  		ConfigRootKeys = append(ConfigRootKeys, k)
    61  	}
    62  	sort.Strings(ConfigRootKeys)
    63  }
    64  
    65  // New creates a Provider backed by an empty maps.Params.
    66  func New() Provider {
    67  	return &defaultConfigProvider{
    68  		root: make(maps.Params),
    69  	}
    70  }
    71  
    72  // NewFrom creates a Provider backed by params.
    73  func NewFrom(params maps.Params) Provider {
    74  	maps.PrepareParams(params)
    75  	return &defaultConfigProvider{
    76  		root: params,
    77  	}
    78  }
    79  
    80  // defaultConfigProvider is a Provider backed by a map where all keys are lower case.
    81  // All methods are thread safe.
    82  type defaultConfigProvider struct {
    83  	mu   sync.RWMutex
    84  	root maps.Params
    85  
    86  	keyCache sync.Map
    87  }
    88  
    89  func (c *defaultConfigProvider) Get(k string) any {
    90  	if k == "" {
    91  		return c.root
    92  	}
    93  	c.mu.RLock()
    94  	key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
    95  	if m == nil {
    96  		c.mu.RUnlock()
    97  		return nil
    98  	}
    99  	v := m[key]
   100  	c.mu.RUnlock()
   101  	return v
   102  }
   103  
   104  func (c *defaultConfigProvider) GetBool(k string) bool {
   105  	v := c.Get(k)
   106  	return cast.ToBool(v)
   107  }
   108  
   109  func (c *defaultConfigProvider) GetInt(k string) int {
   110  	v := c.Get(k)
   111  	return cast.ToInt(v)
   112  }
   113  
   114  func (c *defaultConfigProvider) IsSet(k string) bool {
   115  	var found bool
   116  	c.mu.RLock()
   117  	key, m := c.getNestedKeyAndMap(strings.ToLower(k), false)
   118  	if m != nil {
   119  		_, found = m[key]
   120  	}
   121  	c.mu.RUnlock()
   122  	return found
   123  }
   124  
   125  func (c *defaultConfigProvider) GetString(k string) string {
   126  	v := c.Get(k)
   127  	return cast.ToString(v)
   128  }
   129  
   130  func (c *defaultConfigProvider) GetParams(k string) maps.Params {
   131  	v := c.Get(k)
   132  	if v == nil {
   133  		return nil
   134  	}
   135  	return v.(maps.Params)
   136  }
   137  
   138  func (c *defaultConfigProvider) GetStringMap(k string) map[string]any {
   139  	v := c.Get(k)
   140  	return maps.ToStringMap(v)
   141  }
   142  
   143  func (c *defaultConfigProvider) GetStringMapString(k string) map[string]string {
   144  	v := c.Get(k)
   145  	return maps.ToStringMapString(v)
   146  }
   147  
   148  func (c *defaultConfigProvider) GetStringSlice(k string) []string {
   149  	v := c.Get(k)
   150  	return cast.ToStringSlice(v)
   151  }
   152  
   153  func (c *defaultConfigProvider) Set(k string, v any) {
   154  	c.mu.Lock()
   155  	defer c.mu.Unlock()
   156  
   157  	k = strings.ToLower(k)
   158  
   159  	if k == "" {
   160  		if p, err := maps.ToParamsAndPrepare(v); err == nil {
   161  			// Set the values directly in root.
   162  			maps.SetParams(c.root, p)
   163  		} else {
   164  			c.root[k] = v
   165  		}
   166  
   167  		return
   168  	}
   169  
   170  	switch vv := v.(type) {
   171  	case map[string]any, map[any]any, map[string]string:
   172  		p := maps.MustToParamsAndPrepare(vv)
   173  		v = p
   174  	}
   175  
   176  	key, m := c.getNestedKeyAndMap(k, true)
   177  	if m == nil {
   178  		return
   179  	}
   180  
   181  	if existing, found := m[key]; found {
   182  		if p1, ok := existing.(maps.Params); ok {
   183  			if p2, ok := v.(maps.Params); ok {
   184  				maps.SetParams(p1, p2)
   185  				return
   186  			}
   187  		}
   188  	}
   189  
   190  	m[key] = v
   191  }
   192  
   193  // SetDefaults will set values from params if not already set.
   194  func (c *defaultConfigProvider) SetDefaults(params maps.Params) {
   195  	maps.PrepareParams(params)
   196  	for k, v := range params {
   197  		if _, found := c.root[k]; !found {
   198  			c.root[k] = v
   199  		}
   200  	}
   201  }
   202  
   203  func (c *defaultConfigProvider) Merge(k string, v any) {
   204  	c.mu.Lock()
   205  	defer c.mu.Unlock()
   206  	k = strings.ToLower(k)
   207  
   208  	if k == "" {
   209  		rs, f := c.root.GetMergeStrategy()
   210  		if f && rs == maps.ParamsMergeStrategyNone {
   211  			// The user has set a "no merge" strategy on this,
   212  			// nothing more to do.
   213  			return
   214  		}
   215  
   216  		if p, err := maps.ToParamsAndPrepare(v); err == nil {
   217  			// As there may be keys in p not in root, we need to handle
   218  			// those as a special case.
   219  			var keysToDelete []string
   220  			for kk, vv := range p {
   221  				if pp, ok := vv.(maps.Params); ok {
   222  					if pppi, ok := c.root[kk]; ok {
   223  						ppp := pppi.(maps.Params)
   224  						maps.MergeParamsWithStrategy("", ppp, pp)
   225  					} else {
   226  						// We need to use the default merge strategy for
   227  						// this key.
   228  						np := make(maps.Params)
   229  						strategy := c.determineMergeStrategy(maps.KeyParams{Key: "", Params: c.root}, maps.KeyParams{Key: kk, Params: np})
   230  						np.SetMergeStrategy(strategy)
   231  						maps.MergeParamsWithStrategy("", np, pp)
   232  						c.root[kk] = np
   233  						if np.IsZero() {
   234  							// Just keep it until merge is done.
   235  							keysToDelete = append(keysToDelete, kk)
   236  						}
   237  					}
   238  				}
   239  			}
   240  			// Merge the rest.
   241  			maps.MergeParams(c.root, p)
   242  			for _, k := range keysToDelete {
   243  				delete(c.root, k)
   244  			}
   245  		} else {
   246  			panic(fmt.Sprintf("unsupported type %T received in Merge", v))
   247  		}
   248  
   249  		return
   250  	}
   251  
   252  	switch vv := v.(type) {
   253  	case map[string]any, map[any]any, map[string]string:
   254  		p := maps.MustToParamsAndPrepare(vv)
   255  		v = p
   256  	}
   257  
   258  	key, m := c.getNestedKeyAndMap(k, true)
   259  	if m == nil {
   260  		return
   261  	}
   262  
   263  	if existing, found := m[key]; found {
   264  		if p1, ok := existing.(maps.Params); ok {
   265  			if p2, ok := v.(maps.Params); ok {
   266  				maps.MergeParamsWithStrategy("", p1, p2)
   267  			}
   268  		}
   269  	} else {
   270  		m[key] = v
   271  	}
   272  }
   273  
   274  func (c *defaultConfigProvider) Keys() []string {
   275  	c.mu.RLock()
   276  	defer c.mu.RUnlock()
   277  	return xmaps.Keys(c.root)
   278  }
   279  
   280  func (c *defaultConfigProvider) WalkParams(walkFn func(params ...maps.KeyParams) bool) {
   281  	var walk func(params ...maps.KeyParams)
   282  	walk = func(params ...maps.KeyParams) {
   283  		if walkFn(params...) {
   284  			return
   285  		}
   286  		p1 := params[len(params)-1]
   287  		i := len(params)
   288  		for k, v := range p1.Params {
   289  			if p2, ok := v.(maps.Params); ok {
   290  				paramsplus1 := make([]maps.KeyParams, i+1)
   291  				copy(paramsplus1, params)
   292  				paramsplus1[i] = maps.KeyParams{Key: k, Params: p2}
   293  				walk(paramsplus1...)
   294  			}
   295  		}
   296  	}
   297  	walk(maps.KeyParams{Key: "", Params: c.root})
   298  }
   299  
   300  func (c *defaultConfigProvider) determineMergeStrategy(params ...maps.KeyParams) maps.ParamsMergeStrategy {
   301  	if len(params) == 0 {
   302  		return maps.ParamsMergeStrategyNone
   303  	}
   304  
   305  	var (
   306  		strategy   maps.ParamsMergeStrategy
   307  		prevIsRoot bool
   308  		curr       = params[len(params)-1]
   309  	)
   310  
   311  	if len(params) > 1 {
   312  		prev := params[len(params)-2]
   313  		prevIsRoot = prev.Key == ""
   314  
   315  		// Inherit from parent (but not from the root unless it's set by user).
   316  		s, found := prev.Params.GetMergeStrategy()
   317  		if !prevIsRoot && !found {
   318  			panic("invalid state, merge strategy not set on parent")
   319  		}
   320  		if found || !prevIsRoot {
   321  			strategy = s
   322  		}
   323  	}
   324  
   325  	switch curr.Key {
   326  	case "":
   327  	// Don't set a merge strategy on the root unless set by user.
   328  	// This will be handled as a special case.
   329  	case "params":
   330  		strategy = maps.ParamsMergeStrategyDeep
   331  	case "outputformats", "mediatypes":
   332  		if prevIsRoot {
   333  			strategy = maps.ParamsMergeStrategyShallow
   334  		}
   335  	case "menus":
   336  		isMenuKey := prevIsRoot
   337  		if !isMenuKey {
   338  			// Can also be set below languages.
   339  			// root > languages > en > menus
   340  			if len(params) == 4 && params[1].Key == "languages" {
   341  				isMenuKey = true
   342  			}
   343  		}
   344  		if isMenuKey {
   345  			strategy = maps.ParamsMergeStrategyShallow
   346  		}
   347  	default:
   348  		if strategy == "" {
   349  			strategy = maps.ParamsMergeStrategyNone
   350  		}
   351  	}
   352  
   353  	return strategy
   354  }
   355  
   356  func (c *defaultConfigProvider) SetDefaultMergeStrategy() {
   357  	c.WalkParams(func(params ...maps.KeyParams) bool {
   358  		if len(params) == 0 {
   359  			return false
   360  		}
   361  		p := params[len(params)-1].Params
   362  		var found bool
   363  		if _, found = p.GetMergeStrategy(); found {
   364  			// Set by user.
   365  			return false
   366  		}
   367  		strategy := c.determineMergeStrategy(params...)
   368  		if strategy != "" {
   369  			p.SetMergeStrategy(strategy)
   370  		}
   371  		return false
   372  	})
   373  
   374  }
   375  
   376  func (c *defaultConfigProvider) getNestedKeyAndMap(key string, create bool) (string, maps.Params) {
   377  	var parts []string
   378  	v, ok := c.keyCache.Load(key)
   379  	if ok {
   380  		parts = v.([]string)
   381  	} else {
   382  		parts = strings.Split(key, ".")
   383  		c.keyCache.Store(key, parts)
   384  	}
   385  	current := c.root
   386  	for i := 0; i < len(parts)-1; i++ {
   387  		next, found := current[parts[i]]
   388  		if !found {
   389  			if create {
   390  				next = make(maps.Params)
   391  				current[parts[i]] = next
   392  			} else {
   393  				return "", nil
   394  			}
   395  		}
   396  		var ok bool
   397  		current, ok = next.(maps.Params)
   398  		if !ok {
   399  			// E.g. a string, not a map that we can store values in.
   400  			return "", nil
   401  		}
   402  	}
   403  	return parts[len(parts)-1], current
   404  }