github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/resource_metadata.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 resources
    15  
    16  import (
    17  	"fmt"
    18  	"strconv"
    19  	"strings"
    20  
    21  	"github.com/gohugoio/hugo/hugofs/glob"
    22  	"github.com/gohugoio/hugo/media"
    23  	"github.com/gohugoio/hugo/resources/resource"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/spf13/cast"
    27  
    28  	"github.com/gohugoio/hugo/common/maps"
    29  )
    30  
    31  var (
    32  	_ metaAssigner         = (*genericResource)(nil)
    33  	_ metaAssigner         = (*imageResource)(nil)
    34  	_ metaAssignerProvider = (*resourceAdapter)(nil)
    35  )
    36  
    37  type metaAssignerProvider interface {
    38  	getMetaAssigner() metaAssigner
    39  }
    40  
    41  // metaAssigner allows updating metadata in resources that supports it.
    42  type metaAssigner interface {
    43  	setTitle(title string)
    44  	setName(name string)
    45  	setMediaType(mediaType media.Type)
    46  	updateParams(params map[string]interface{})
    47  }
    48  
    49  const counterPlaceHolder = ":counter"
    50  
    51  // AssignMetadata assigns the given metadata to those resources that supports updates
    52  // and matching by wildcard given in `src` using `filepath.Match` with lower cased values.
    53  // This assignment is additive, but the most specific match needs to be first.
    54  // The `name` and `title` metadata field support shell-matched collection it got a match in.
    55  // See https://golang.org/pkg/path/#Match
    56  func AssignMetadata(metadata []map[string]interface{}, resources ...resource.Resource) error {
    57  	counters := make(map[string]int)
    58  
    59  	for _, r := range resources {
    60  		var ma metaAssigner
    61  		mp, ok := r.(metaAssignerProvider)
    62  		if ok {
    63  			ma = mp.getMetaAssigner()
    64  		} else {
    65  			ma, ok = r.(metaAssigner)
    66  			if !ok {
    67  				continue
    68  			}
    69  		}
    70  
    71  		var (
    72  			nameSet, titleSet                   bool
    73  			nameCounter, titleCounter           = 0, 0
    74  			nameCounterFound, titleCounterFound bool
    75  			resourceSrcKey                      = strings.ToLower(r.Name())
    76  		)
    77  
    78  		for _, meta := range metadata {
    79  			src, found := meta["src"]
    80  			if !found {
    81  				return fmt.Errorf("missing 'src' in metadata for resource")
    82  			}
    83  
    84  			srcKey := strings.ToLower(cast.ToString(src))
    85  
    86  			glob, err := glob.GetGlob(srcKey)
    87  			if err != nil {
    88  				return errors.Wrap(err, "failed to match resource with metadata")
    89  			}
    90  
    91  			match := glob.Match(resourceSrcKey)
    92  
    93  			if match {
    94  				if !nameSet {
    95  					name, found := meta["name"]
    96  					if found {
    97  						name := cast.ToString(name)
    98  						if !nameCounterFound {
    99  							nameCounterFound = strings.Contains(name, counterPlaceHolder)
   100  						}
   101  						if nameCounterFound && nameCounter == 0 {
   102  							counterKey := "name_" + srcKey
   103  							nameCounter = counters[counterKey] + 1
   104  							counters[counterKey] = nameCounter
   105  						}
   106  
   107  						ma.setName(replaceResourcePlaceholders(name, nameCounter))
   108  						nameSet = true
   109  					}
   110  				}
   111  
   112  				if !titleSet {
   113  					title, found := meta["title"]
   114  					if found {
   115  						title := cast.ToString(title)
   116  						if !titleCounterFound {
   117  							titleCounterFound = strings.Contains(title, counterPlaceHolder)
   118  						}
   119  						if titleCounterFound && titleCounter == 0 {
   120  							counterKey := "title_" + srcKey
   121  							titleCounter = counters[counterKey] + 1
   122  							counters[counterKey] = titleCounter
   123  						}
   124  						ma.setTitle((replaceResourcePlaceholders(title, titleCounter)))
   125  						titleSet = true
   126  					}
   127  				}
   128  
   129  				params, found := meta["params"]
   130  				if found {
   131  					m := maps.ToStringMap(params)
   132  					// Needed for case insensitive fetching of params values
   133  					maps.PrepareParams(m)
   134  					ma.updateParams(m)
   135  				}
   136  			}
   137  		}
   138  	}
   139  
   140  	return nil
   141  }
   142  
   143  func replaceResourcePlaceholders(in string, counter int) string {
   144  	return strings.Replace(in, counterPlaceHolder, strconv.Itoa(counter), -1)
   145  }