github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/common/maps/params.go (about)

     1  // Copyright 2019 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 maps
    15  
    16  import (
    17  	"fmt"
    18  	"strings"
    19  
    20  	"github.com/spf13/cast"
    21  )
    22  
    23  // Params is a map where all keys are lower case.
    24  type Params map[string]any
    25  
    26  // KeyParams is an utility struct for the WalkParams method.
    27  type KeyParams struct {
    28  	Key    string
    29  	Params Params
    30  }
    31  
    32  // GetNested does a lower case and nested search in this map.
    33  // It will return nil if none found.
    34  // Make all of these methods internal somehow.
    35  func (p Params) GetNested(indices ...string) any {
    36  	v, _, _ := getNested(p, indices)
    37  	return v
    38  }
    39  
    40  // SetParams overwrites values in dst with values in src for common or new keys.
    41  // This is done recursively.
    42  func SetParams(dst, src Params) {
    43  	for k, v := range src {
    44  		vv, found := dst[k]
    45  		if !found {
    46  			dst[k] = v
    47  		} else {
    48  			switch vvv := vv.(type) {
    49  			case Params:
    50  				if pv, ok := v.(Params); ok {
    51  					SetParams(vvv, pv)
    52  				} else {
    53  					dst[k] = v
    54  				}
    55  			default:
    56  				dst[k] = v
    57  			}
    58  		}
    59  	}
    60  }
    61  
    62  // IsZero returns true if p is considered empty.
    63  func (p Params) IsZero() bool {
    64  	if p == nil || len(p) == 0 {
    65  		return true
    66  	}
    67  
    68  	if len(p) > 1 {
    69  		return false
    70  	}
    71  
    72  	for k := range p {
    73  		return k == MergeStrategyKey
    74  	}
    75  
    76  	return false
    77  
    78  }
    79  
    80  // MergeParamsWithStrategy transfers values from src to dst for new keys using the merge strategy given.
    81  // This is done recursively.
    82  func MergeParamsWithStrategy(strategy string, dst, src Params) {
    83  	dst.merge(ParamsMergeStrategy(strategy), src)
    84  }
    85  
    86  // MergeParams transfers values from src to dst for new keys using the merge encoded in dst.
    87  // This is done recursively.
    88  func MergeParams(dst, src Params) {
    89  	ms, _ := dst.GetMergeStrategy()
    90  	dst.merge(ms, src)
    91  }
    92  
    93  func (p Params) merge(ps ParamsMergeStrategy, pp Params) {
    94  	ns, found := p.GetMergeStrategy()
    95  
    96  	var ms = ns
    97  	if !found && ps != "" {
    98  		ms = ps
    99  	}
   100  
   101  	noUpdate := ms == ParamsMergeStrategyNone
   102  	noUpdate = noUpdate || (ps != "" && ps == ParamsMergeStrategyShallow)
   103  
   104  	for k, v := range pp {
   105  
   106  		if k == MergeStrategyKey {
   107  			continue
   108  		}
   109  		vv, found := p[k]
   110  
   111  		if found {
   112  			// Key matches, if both sides are Params, we try to merge.
   113  			if vvv, ok := vv.(Params); ok {
   114  				if pv, ok := v.(Params); ok {
   115  					vvv.merge(ms, pv)
   116  				}
   117  			}
   118  		} else if !noUpdate {
   119  			p[k] = v
   120  		}
   121  
   122  	}
   123  }
   124  
   125  // For internal use.
   126  func (p Params) GetMergeStrategy() (ParamsMergeStrategy, bool) {
   127  	if v, found := p[MergeStrategyKey]; found {
   128  		if s, ok := v.(ParamsMergeStrategy); ok {
   129  			return s, true
   130  		}
   131  	}
   132  	return ParamsMergeStrategyShallow, false
   133  }
   134  
   135  // For internal use.
   136  func (p Params) DeleteMergeStrategy() bool {
   137  	if _, found := p[MergeStrategyKey]; found {
   138  		delete(p, MergeStrategyKey)
   139  		return true
   140  	}
   141  	return false
   142  }
   143  
   144  // For internal use.
   145  func (p Params) SetMergeStrategy(s ParamsMergeStrategy) {
   146  	switch s {
   147  	case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
   148  	default:
   149  		panic(fmt.Sprintf("invalid merge strategy %q", s))
   150  	}
   151  	p[MergeStrategyKey] = s
   152  }
   153  
   154  func getNested(m map[string]any, indices []string) (any, string, map[string]any) {
   155  	if len(indices) == 0 {
   156  		return nil, "", nil
   157  	}
   158  
   159  	first := indices[0]
   160  	v, found := m[strings.ToLower(cast.ToString(first))]
   161  	if !found {
   162  		if len(indices) == 1 {
   163  			return nil, first, m
   164  		}
   165  		return nil, "", nil
   166  
   167  	}
   168  
   169  	if len(indices) == 1 {
   170  		return v, first, m
   171  	}
   172  
   173  	switch m2 := v.(type) {
   174  	case Params:
   175  		return getNested(m2, indices[1:])
   176  	case map[string]any:
   177  		return getNested(m2, indices[1:])
   178  	default:
   179  		return nil, "", nil
   180  	}
   181  }
   182  
   183  // GetNestedParam gets the first match of the keyStr in the candidates given.
   184  // It will first try the exact match and then try to find it as a nested map value,
   185  // using the given separator, e.g. "mymap.name".
   186  // It assumes that all the maps given have lower cased keys.
   187  func GetNestedParam(keyStr, separator string, candidates ...Params) (any, error) {
   188  	keyStr = strings.ToLower(keyStr)
   189  
   190  	// Try exact match first
   191  	for _, m := range candidates {
   192  		if v, ok := m[keyStr]; ok {
   193  			return v, nil
   194  		}
   195  	}
   196  
   197  	keySegments := strings.Split(keyStr, separator)
   198  	for _, m := range candidates {
   199  		if v := m.GetNested(keySegments...); v != nil {
   200  			return v, nil
   201  		}
   202  	}
   203  
   204  	return nil, nil
   205  }
   206  
   207  func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) any) (any, string, map[string]any, error) {
   208  	keySegments := strings.Split(keyStr, separator)
   209  	if len(keySegments) == 0 {
   210  		return nil, "", nil, nil
   211  	}
   212  
   213  	first := lookupFn(keySegments[0])
   214  	if first == nil {
   215  		return nil, "", nil, nil
   216  	}
   217  
   218  	if len(keySegments) == 1 {
   219  		return first, keySegments[0], nil, nil
   220  	}
   221  
   222  	switch m := first.(type) {
   223  	case map[string]any:
   224  		v, key, owner := getNested(m, keySegments[1:])
   225  		return v, key, owner, nil
   226  	case Params:
   227  		v, key, owner := getNested(m, keySegments[1:])
   228  		return v, key, owner, nil
   229  	}
   230  
   231  	return nil, "", nil, nil
   232  }
   233  
   234  // ParamsMergeStrategy tells what strategy to use in Params.Merge.
   235  type ParamsMergeStrategy string
   236  
   237  const (
   238  	// Do not merge.
   239  	ParamsMergeStrategyNone ParamsMergeStrategy = "none"
   240  	// Only add new keys.
   241  	ParamsMergeStrategyShallow ParamsMergeStrategy = "shallow"
   242  	// Add new keys, merge existing.
   243  	ParamsMergeStrategyDeep ParamsMergeStrategy = "deep"
   244  
   245  	MergeStrategyKey = "_merge"
   246  )
   247  
   248  // CleanConfigStringMapString removes any processing instructions from m,
   249  // m will never be modified.
   250  func CleanConfigStringMapString(m map[string]string) map[string]string {
   251  	if m == nil || len(m) == 0 {
   252  		return m
   253  	}
   254  	if _, found := m[MergeStrategyKey]; !found {
   255  		return m
   256  	}
   257  	// Create a new map and copy all the keys except the merge strategy key.
   258  	m2 := make(map[string]string, len(m)-1)
   259  	for k, v := range m {
   260  		if k != MergeStrategyKey {
   261  			m2[k] = v
   262  		}
   263  	}
   264  	return m2
   265  }
   266  
   267  // CleanConfigStringMap is the same as CleanConfigStringMapString but for
   268  // map[string]any.
   269  func CleanConfigStringMap(m map[string]any) map[string]any {
   270  	if m == nil || len(m) == 0 {
   271  		return m
   272  	}
   273  	if _, found := m[MergeStrategyKey]; !found {
   274  		return m
   275  	}
   276  	// Create a new map and copy all the keys except the merge strategy key.
   277  	m2 := make(map[string]any, len(m)-1)
   278  	for k, v := range m {
   279  		if k != MergeStrategyKey {
   280  			m2[k] = v
   281  		}
   282  		switch v2 := v.(type) {
   283  		case map[string]any:
   284  			m2[k] = CleanConfigStringMap(v2)
   285  		case Params:
   286  			var p Params = CleanConfigStringMap(v2)
   287  			m2[k] = p
   288  		case map[string]string:
   289  			m2[k] = CleanConfigStringMapString(v2)
   290  		}
   291  
   292  	}
   293  	return m2
   294  
   295  }
   296  
   297  func toMergeStrategy(v any) ParamsMergeStrategy {
   298  	s := ParamsMergeStrategy(cast.ToString(v))
   299  	switch s {
   300  	case ParamsMergeStrategyDeep, ParamsMergeStrategyNone, ParamsMergeStrategyShallow:
   301  		return s
   302  	default:
   303  		return ParamsMergeStrategyDeep
   304  	}
   305  }
   306  
   307  // PrepareParams
   308  // * makes all the keys in the given map lower cased and will do so
   309  // * This will modify the map given.
   310  // * Any nested map[interface{}]interface{}, map[string]interface{},map[string]string  will be converted to Params.
   311  // * Any _merge value will be converted to proper type and value.
   312  func PrepareParams(m Params) {
   313  	for k, v := range m {
   314  		var retyped bool
   315  		lKey := strings.ToLower(k)
   316  		if lKey == MergeStrategyKey {
   317  			v = toMergeStrategy(v)
   318  			retyped = true
   319  		} else {
   320  			switch vv := v.(type) {
   321  			case map[any]any:
   322  				var p Params = cast.ToStringMap(v)
   323  				v = p
   324  				PrepareParams(p)
   325  				retyped = true
   326  			case map[string]any:
   327  				var p Params = v.(map[string]any)
   328  				v = p
   329  				PrepareParams(p)
   330  				retyped = true
   331  			case map[string]string:
   332  				p := make(Params)
   333  				for k, v := range vv {
   334  					p[k] = v
   335  				}
   336  				v = p
   337  				PrepareParams(p)
   338  				retyped = true
   339  			}
   340  		}
   341  
   342  		if retyped || k != lKey {
   343  			delete(m, k)
   344  			m[lKey] = v
   345  		}
   346  	}
   347  }