github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/resources/resource_spec.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 "errors" 18 "fmt" 19 "mime" 20 "os" 21 "path" 22 "path/filepath" 23 "strings" 24 "sync" 25 26 "github.com/gohugoio/hugo/resources/jsconfig" 27 28 "github.com/gohugoio/hugo/common/herrors" 29 30 "github.com/gohugoio/hugo/config" 31 "github.com/gohugoio/hugo/identity" 32 33 "github.com/gohugoio/hugo/helpers" 34 "github.com/gohugoio/hugo/hugofs" 35 "github.com/gohugoio/hugo/resources/postpub" 36 37 "github.com/gohugoio/hugo/cache/filecache" 38 "github.com/gohugoio/hugo/common/loggers" 39 "github.com/gohugoio/hugo/media" 40 "github.com/gohugoio/hugo/output" 41 "github.com/gohugoio/hugo/resources/images" 42 "github.com/gohugoio/hugo/resources/page" 43 "github.com/gohugoio/hugo/resources/resource" 44 "github.com/gohugoio/hugo/tpl" 45 "github.com/spf13/afero" 46 ) 47 48 func NewSpec( 49 s *helpers.PathSpec, 50 fileCaches filecache.Caches, 51 incr identity.Incrementer, 52 logger loggers.Logger, 53 errorHandler herrors.ErrorSender, 54 outputFormats output.Formats, 55 mimeTypes media.Types) (*Spec, error) { 56 imgConfig, err := images.DecodeConfig(s.Cfg.GetStringMap("imaging")) 57 if err != nil { 58 return nil, err 59 } 60 61 imaging, err := images.NewImageProcessor(imgConfig) 62 if err != nil { 63 return nil, err 64 } 65 66 if incr == nil { 67 incr = &identity.IncrementByOne{} 68 } 69 70 if logger == nil { 71 logger = loggers.NewErrorLogger() 72 } 73 74 permalinks, err := page.NewPermalinkExpander(s) 75 if err != nil { 76 return nil, err 77 } 78 79 rs := &Spec{ 80 PathSpec: s, 81 Logger: logger, 82 ErrorSender: errorHandler, 83 imaging: imaging, 84 incr: incr, 85 MediaTypes: mimeTypes, 86 OutputFormats: outputFormats, 87 Permalinks: permalinks, 88 BuildConfig: config.DecodeBuild(s.Cfg), 89 FileCaches: fileCaches, 90 PostBuildAssets: &PostBuildAssets{ 91 PostProcessResources: make(map[string]postpub.PostPublishedResource), 92 JSConfigBuilder: jsconfig.NewBuilder(), 93 }, 94 imageCache: newImageCache( 95 fileCaches.ImageCache(), 96 97 s, 98 ), 99 } 100 101 rs.ResourceCache = newResourceCache(rs) 102 103 return rs, nil 104 } 105 106 type Spec struct { 107 *helpers.PathSpec 108 109 MediaTypes media.Types 110 OutputFormats output.Formats 111 112 Logger loggers.Logger 113 ErrorSender herrors.ErrorSender 114 115 TextTemplates tpl.TemplateParseFinder 116 117 Permalinks page.PermalinkExpander 118 BuildConfig config.Build 119 120 // Holds default filter settings etc. 121 imaging *images.ImageProcessor 122 123 incr identity.Incrementer 124 imageCache *imageCache 125 ResourceCache *ResourceCache 126 FileCaches filecache.Caches 127 128 // Assets used after the build is done. 129 // This is shared between all sites. 130 *PostBuildAssets 131 } 132 133 type PostBuildAssets struct { 134 postProcessMu sync.RWMutex 135 PostProcessResources map[string]postpub.PostPublishedResource 136 JSConfigBuilder *jsconfig.Builder 137 } 138 139 func (r *Spec) New(fd ResourceSourceDescriptor) (resource.Resource, error) { 140 return r.newResourceFor(fd) 141 } 142 143 func (r *Spec) CacheStats() string { 144 r.imageCache.mu.RLock() 145 defer r.imageCache.mu.RUnlock() 146 147 s := fmt.Sprintf("Cache entries: %d", len(r.imageCache.store)) 148 149 count := 0 150 for k := range r.imageCache.store { 151 if count > 5 { 152 break 153 } 154 s += "\n" + k 155 count++ 156 } 157 158 return s 159 } 160 161 func (r *Spec) ClearCaches() { 162 r.imageCache.clear() 163 r.ResourceCache.clear() 164 } 165 166 func (r *Spec) DeleteBySubstring(s string) { 167 r.imageCache.deleteIfContains(s) 168 } 169 170 func (s *Spec) String() string { 171 return "spec" 172 } 173 174 // TODO(bep) clean up below 175 func (r *Spec) newGenericResource(sourceFs afero.Fs, 176 targetPathBuilder func() page.TargetPaths, 177 osFileInfo os.FileInfo, 178 sourceFilename, 179 baseFilename string, 180 mediaType media.Type) *genericResource { 181 return r.newGenericResourceWithBase( 182 sourceFs, 183 nil, 184 nil, 185 targetPathBuilder, 186 osFileInfo, 187 sourceFilename, 188 baseFilename, 189 mediaType, 190 ) 191 } 192 193 func (r *Spec) newGenericResourceWithBase( 194 sourceFs afero.Fs, 195 openReadSeekerCloser resource.OpenReadSeekCloser, 196 targetPathBaseDirs []string, 197 targetPathBuilder func() page.TargetPaths, 198 osFileInfo os.FileInfo, 199 sourceFilename, 200 baseFilename string, 201 mediaType media.Type) *genericResource { 202 if osFileInfo != nil && osFileInfo.IsDir() { 203 panic(fmt.Sprintf("dirs not supported resource types: %v", osFileInfo)) 204 } 205 206 // This value is used both to construct URLs and file paths, but start 207 // with a Unix-styled path. 208 baseFilename = helpers.ToSlashTrimLeading(baseFilename) 209 fpath, fname := path.Split(baseFilename) 210 211 resourceType := mediaType.MainType 212 213 pathDescriptor := &resourcePathDescriptor{ 214 baseTargetPathDirs: helpers.UniqueStringsReuse(targetPathBaseDirs), 215 targetPathBuilder: targetPathBuilder, 216 relTargetDirFile: dirFile{dir: fpath, file: fname}, 217 } 218 219 var fim hugofs.FileMetaInfo 220 if osFileInfo != nil { 221 fim = osFileInfo.(hugofs.FileMetaInfo) 222 } 223 224 gfi := &resourceFileInfo{ 225 fi: fim, 226 openReadSeekerCloser: openReadSeekerCloser, 227 sourceFs: sourceFs, 228 sourceFilename: sourceFilename, 229 h: &resourceHash{}, 230 } 231 232 g := &genericResource{ 233 resourceFileInfo: gfi, 234 resourcePathDescriptor: pathDescriptor, 235 mediaType: mediaType, 236 resourceType: resourceType, 237 spec: r, 238 params: make(map[string]interface{}), 239 name: baseFilename, 240 title: baseFilename, 241 resourceContent: &resourceContent{}, 242 } 243 244 return g 245 } 246 247 func (r *Spec) newResource(sourceFs afero.Fs, fd ResourceSourceDescriptor) (resource.Resource, error) { 248 fi := fd.FileInfo 249 var sourceFilename string 250 251 if fd.OpenReadSeekCloser != nil { 252 } else if fd.SourceFilename != "" { 253 var err error 254 fi, err = sourceFs.Stat(fd.SourceFilename) 255 if err != nil { 256 if os.IsNotExist(err) { 257 return nil, nil 258 } 259 return nil, err 260 } 261 sourceFilename = fd.SourceFilename 262 } else { 263 sourceFilename = fd.SourceFile.Filename() 264 } 265 266 if fd.RelTargetFilename == "" { 267 fd.RelTargetFilename = sourceFilename 268 } 269 270 ext := strings.ToLower(filepath.Ext(fd.RelTargetFilename)) 271 mimeType, suffixInfo, found := r.MediaTypes.GetFirstBySuffix(strings.TrimPrefix(ext, ".")) 272 // TODO(bep) we need to handle these ambiguous types better, but in this context 273 // we most likely want the application/xml type. 274 if suffixInfo.Suffix == "xml" && mimeType.SubType == "rss" { 275 mimeType, found = r.MediaTypes.GetByType("application/xml") 276 } 277 278 if !found { 279 // A fallback. Note that mime.TypeByExtension is slow by Hugo standards, 280 // so we should configure media types to avoid this lookup for most 281 // situations. 282 mimeStr := mime.TypeByExtension(ext) 283 if mimeStr != "" { 284 mimeType, _ = media.FromStringAndExt(mimeStr, ext) 285 } 286 } 287 288 gr := r.newGenericResourceWithBase( 289 sourceFs, 290 fd.OpenReadSeekCloser, 291 fd.TargetBasePaths, 292 fd.TargetPaths, 293 fi, 294 sourceFilename, 295 fd.RelTargetFilename, 296 mimeType) 297 298 if mimeType.MainType == "image" { 299 imgFormat, ok := images.ImageFormatFromExt(ext) 300 if ok { 301 ir := &imageResource{ 302 Image: images.NewImage(imgFormat, r.imaging, nil, gr), 303 baseResource: gr, 304 } 305 ir.root = ir 306 return newResourceAdapter(gr.spec, fd.LazyPublish, ir), nil 307 } 308 309 } 310 311 return newResourceAdapter(gr.spec, fd.LazyPublish, gr), nil 312 } 313 314 func (r *Spec) newResourceFor(fd ResourceSourceDescriptor) (resource.Resource, error) { 315 if fd.OpenReadSeekCloser == nil { 316 if fd.SourceFile != nil && fd.SourceFilename != "" { 317 return nil, errors.New("both SourceFile and AbsSourceFilename provided") 318 } else if fd.SourceFile == nil && fd.SourceFilename == "" { 319 return nil, errors.New("either SourceFile or AbsSourceFilename must be provided") 320 } 321 } 322 323 if fd.RelTargetFilename == "" { 324 fd.RelTargetFilename = fd.Filename() 325 } 326 327 if len(fd.TargetBasePaths) == 0 { 328 // If not set, we publish the same resource to all hosts. 329 fd.TargetBasePaths = r.MultihostTargetBasePaths 330 } 331 332 return r.newResource(fd.Fs, fd) 333 }