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