github.com/neohugo/neohugo@v0.123.8/resources/resource/resources.go (about) 1 // Copyright 2024 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 resource contains Resource related types. 15 package resource 16 17 import ( 18 "fmt" 19 "strings" 20 21 "github.com/neohugo/neohugo/hugofs/glob" 22 "github.com/spf13/cast" 23 ) 24 25 var _ ResourceFinder = (*Resources)(nil) 26 27 // Resources represents a slice of resources, which can be a mix of different types. 28 // I.e. both pages and images etc. 29 type Resources []Resource 30 31 // var _ resource.ResourceFinder = (*Namespace)(nil) 32 // ResourcesConverter converts a given slice of Resource objects to Resources. 33 type ResourcesConverter interface { 34 // For internal use. 35 ToResources() Resources 36 } 37 38 // ByType returns resources of a given resource type (e.g. "image"). 39 func (r Resources) ByType(typ any) Resources { 40 tpstr, err := cast.ToStringE(typ) 41 if err != nil { 42 panic(err) 43 } 44 var filtered Resources 45 46 for _, resource := range r { 47 if resource.ResourceType() == tpstr { 48 filtered = append(filtered, resource) 49 } 50 } 51 return filtered 52 } 53 54 // Get locates the name given in Resources. 55 // The search is case insensitive. 56 func (r Resources) Get(name any) Resource { 57 if r == nil { 58 return nil 59 } 60 namestr, err := cast.ToStringE(name) 61 if err != nil { 62 panic(err) 63 } 64 namestr = strings.ToLower(namestr) 65 66 // First check the Name. 67 // Note that this can be modified by the user in the front matter, 68 // also, it does not contain any language code. 69 for _, resource := range r { 70 if strings.EqualFold(namestr, resource.Name()) { 71 return resource 72 } 73 } 74 75 // Finally, check the normalized name. 76 for _, resource := range r { 77 if nop, ok := resource.(NameNormalizedProvider); ok { 78 if strings.EqualFold(namestr, nop.NameNormalized()) { 79 return resource 80 } 81 } 82 } 83 84 return nil 85 } 86 87 // GetMatch finds the first Resource matching the given pattern, or nil if none found. 88 // See Match for a more complete explanation about the rules used. 89 func (r Resources) GetMatch(pattern any) Resource { 90 patternstr, err := cast.ToStringE(pattern) 91 if err != nil { 92 panic(err) 93 } 94 95 g, err := glob.GetGlob(patternstr) 96 if err != nil { 97 panic(err) 98 } 99 100 for _, resource := range r { 101 if g.Match(resource.Name()) { 102 return resource 103 } 104 } 105 106 // Finally, check the original name. 107 for _, resource := range r { 108 if nop, ok := resource.(NameNormalizedProvider); ok { 109 if g.Match(nop.NameNormalized()) { 110 return resource 111 } 112 } 113 } 114 115 return nil 116 } 117 118 // Match gets all resources matching the given base filename prefix, e.g 119 // "*.png" will match all png files. The "*" does not match path delimiters (/), 120 // so if you organize your resources in sub-folders, you need to be explicit about it, e.g.: 121 // "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and 122 // to match all PNG images below the images folder, use "images/**.jpg". 123 // The matching is case insensitive. 124 // Match matches by using the value of Resource.Name, which, by default, is a filename with 125 // path relative to the bundle root with Unix style slashes (/) and no leading slash, e.g. "images/logo.png". 126 // See https://github.com/gobwas/glob for the full rules set. 127 func (r Resources) Match(pattern any) Resources { 128 patternstr, err := cast.ToStringE(pattern) 129 if err != nil { 130 panic(err) 131 } 132 133 g, err := glob.GetGlob(patternstr) 134 if err != nil { 135 panic(err) 136 } 137 138 var matches Resources 139 for _, resource := range r { 140 if g.Match(resource.Name()) { 141 matches = append(matches, resource) 142 } 143 } 144 if len(matches) == 0 { 145 // Fall back to the normalized name. 146 for _, resource := range r { 147 if nop, ok := resource.(NameNormalizedProvider); ok { 148 if g.Match(nop.NameNormalized()) { 149 matches = append(matches, resource) 150 } 151 } 152 } 153 } 154 return matches 155 } 156 157 type translatedResource interface { 158 TranslationKey() string 159 } 160 161 // MergeByLanguage adds missing translations in r1 from r2. 162 func (r Resources) MergeByLanguage(r2 Resources) Resources { 163 result := append(Resources(nil), r...) 164 m := make(map[string]bool) 165 for _, rr := range r { 166 if translated, ok := rr.(translatedResource); ok { 167 m[translated.TranslationKey()] = true 168 } 169 } 170 171 for _, rr := range r2 { 172 if translated, ok := rr.(translatedResource); ok { 173 if _, found := m[translated.TranslationKey()]; !found { 174 result = append(result, rr) 175 } 176 } 177 } 178 return result 179 } 180 181 // MergeByLanguageInterface is the generic version of MergeByLanguage. It 182 // is here just so it can be called from the tpl package. 183 // This is for internal use. 184 func (r Resources) MergeByLanguageInterface(in any) (any, error) { 185 r2, ok := in.(Resources) 186 if !ok { 187 return nil, fmt.Errorf("%T cannot be merged by language", in) 188 } 189 return r.MergeByLanguage(r2), nil 190 } 191 192 // Source is an internal template and not meant for use in the templates. It 193 // may change without notice. 194 type Source interface { 195 Publish() error 196 } 197 198 // ResourceFinder provides methods to find Resources. 199 // Note that GetRemote (as found in resources.GetRemote) is 200 // not covered by this interface, as this is only available as a global template function. 201 type ResourceFinder interface { 202 // Get locates the Resource with the given name in the current context (e.g. in .Page.Resources). 203 // 204 // It returns nil if no Resource could found, panics if name is invalid. 205 Get(name any) Resource 206 207 // GetMatch finds the first Resource matching the given pattern, or nil if none found. 208 // 209 // See Match for a more complete explanation about the rules used. 210 // 211 // It returns nil if no Resource could found, panics if pattern is invalid. 212 GetMatch(pattern any) Resource 213 214 // Match gets all resources matching the given base path prefix, e.g 215 // "*.png" will match all png files. The "*" does not match path delimiters (/), 216 // so if you organize your resources in sub-folders, you need to be explicit about it, e.g.: 217 // "images/*.png". To match any PNG image anywhere in the bundle you can do "**.png", and 218 // to match all PNG images below the images folder, use "images/**.jpg". 219 // 220 // The matching is case insensitive. 221 // 222 // Match matches by using a relative pathwith Unix style slashes (/) and no 223 // leading slash, e.g. "images/logo.png". 224 // 225 // See https://github.com/gobwas/glob for the full rules set. 226 // 227 // See Match for a more complete explanation about the rules used. 228 // 229 // It returns nil if no Resources could found, panics if pattern is invalid. 230 Match(pattern any) Resources 231 232 // ByType returns resources of a given resource type (e.g. "image"). 233 // It returns nil if no Resources could found, panics if typ is invalid. 234 ByType(typ any) Resources 235 }