mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
3cdf19e9b7
This commit is not the smallest in Hugo's history. Some hightlights include: * Page bundles (for complete articles, keeping images and content together etc.). * Bundled images can be processed in as many versions/sizes as you need with the three methods `Resize`, `Fill` and `Fit`. * Processed images are cached inside `resources/_gen/images` (default) in your project. * Symbolic links (both files and dirs) are now allowed anywhere inside /content * A new table based build summary * The "Total in nn ms" now reports the total including the handling of the files inside /static. So if it now reports more than you're used to, it is just **more real** and probably faster than before (see below). A site building benchmark run compared to `v0.31.1` shows that this should be slightly faster and use less memory: ```bash ▶ ./benchSite.sh "TOML,num_langs=.*,num_root_sections=5,num_pages=(500|1000),tags_per_page=5,shortcodes,render" benchmark old ns/op new ns/op delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 101785785 78067944 -23.30% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 185481057 149159919 -19.58% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 103149918 85679409 -16.94% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 203515478 169208775 -16.86% benchmark old allocs new allocs delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 532464 391539 -26.47% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1056549 772702 -26.87% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 555974 406630 -26.86% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 1086545 789922 -27.30% benchmark old bytes new bytes delta BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 53243246 43598155 -18.12% BenchmarkSiteBuilding/TOML,num_langs=1,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 105811617 86087116 -18.64% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=500,tags_per_page=5,shortcodes,render-4 54558852 44545097 -18.35% BenchmarkSiteBuilding/TOML,num_langs=3,num_root_sections=5,num_pages=1000,tags_per_page=5,shortcodes,render-4 106903858 86978413 -18.64% ``` Fixes #3651 Closes #3158 Fixes #1014 Closes #2021 Fixes #1240 Updates #3757
275 lines
6.7 KiB
Go
275 lines
6.7 KiB
Go
// Copyright 2017-present The Hugo Authors. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package resource
|
|
|
|
import (
|
|
"fmt"
|
|
"mime"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gohugoio/hugo/media"
|
|
"github.com/gohugoio/hugo/source"
|
|
|
|
"github.com/gohugoio/hugo/helpers"
|
|
)
|
|
|
|
var (
|
|
_ Resource = (*genericResource)(nil)
|
|
_ Source = (*genericResource)(nil)
|
|
_ Cloner = (*genericResource)(nil)
|
|
)
|
|
|
|
const DefaultResourceType = "unknown"
|
|
|
|
type Source interface {
|
|
AbsSourceFilename() string
|
|
Publish() error
|
|
}
|
|
|
|
type Cloner interface {
|
|
WithNewBase(base string) Resource
|
|
}
|
|
|
|
// Resource represents a linkable resource, i.e. a content page, image etc.
|
|
type Resource interface {
|
|
Permalink() string
|
|
RelPermalink() string
|
|
ResourceType() string
|
|
}
|
|
|
|
// Resources represents a slice of resources, which can be a mix of different types.
|
|
// I.e. both pages and images etc.
|
|
type Resources []Resource
|
|
|
|
func (r Resources) ByType(tp string) []Resource {
|
|
var filtered []Resource
|
|
|
|
for _, resource := range r {
|
|
if resource.ResourceType() == tp {
|
|
filtered = append(filtered, resource)
|
|
}
|
|
}
|
|
return filtered
|
|
}
|
|
|
|
// GetBySuffix gets the first resource matching the given filename prefix, e.g
|
|
// "logo" will match logo.png. It returns nil of none found.
|
|
// In potential ambiguous situations, combine it with ByType.
|
|
func (r Resources) GetByPrefix(prefix string) Resource {
|
|
for _, resource := range r {
|
|
_, name := filepath.Split(resource.RelPermalink())
|
|
if strings.HasPrefix(name, prefix) {
|
|
return resource
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type Spec struct {
|
|
*helpers.PathSpec
|
|
mimeTypes media.Types
|
|
|
|
// Holds default filter settings etc.
|
|
imaging *Imaging
|
|
|
|
imageCache *imageCache
|
|
|
|
AbsGenImagePath string
|
|
}
|
|
|
|
func NewSpec(s *helpers.PathSpec, mimeTypes media.Types) (*Spec, error) {
|
|
|
|
imaging, err := decodeImaging(s.Cfg.GetStringMap("imaging"))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.GetLayoutDirPath()
|
|
|
|
genImagePath := s.AbsPathify(filepath.Join(s.Cfg.GetString("resourceDir"), "_gen", "images"))
|
|
|
|
return &Spec{AbsGenImagePath: genImagePath, PathSpec: s, imaging: &imaging, mimeTypes: mimeTypes, imageCache: newImageCache(
|
|
s,
|
|
// We're going to write a cache pruning routine later, so make it extremely
|
|
// unlikely that the user shoots him or herself in the foot
|
|
// and this is set to a value that represents data he/she
|
|
// cares about. This should be set in stone once released.
|
|
genImagePath,
|
|
s.AbsPathify(s.Cfg.GetString("publishDir")))}, nil
|
|
}
|
|
|
|
func (r *Spec) NewResourceFromFile(
|
|
linker func(base string) string,
|
|
absPublishDir string,
|
|
file source.File, relTargetFilename string) (Resource, error) {
|
|
|
|
return r.newResource(linker, absPublishDir, file.Filename(), file.FileInfo(), relTargetFilename)
|
|
}
|
|
|
|
func (r *Spec) NewResourceFromFilename(
|
|
linker func(base string) string,
|
|
absPublishDir,
|
|
absSourceFilename, relTargetFilename string) (Resource, error) {
|
|
|
|
fi, err := r.Fs.Source.Stat(absSourceFilename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r.newResource(linker, absPublishDir, absSourceFilename, fi, relTargetFilename)
|
|
}
|
|
|
|
func (r *Spec) newResource(
|
|
linker func(base string) string,
|
|
absPublishDir,
|
|
absSourceFilename string, fi os.FileInfo, relTargetFilename string) (Resource, error) {
|
|
|
|
var mimeType string
|
|
ext := filepath.Ext(relTargetFilename)
|
|
m, found := r.mimeTypes.GetBySuffix(strings.TrimPrefix(ext, "."))
|
|
if found {
|
|
mimeType = m.SubType
|
|
} else {
|
|
mimeType = mime.TypeByExtension(ext)
|
|
if mimeType == "" {
|
|
mimeType = DefaultResourceType
|
|
} else {
|
|
mimeType = mimeType[:strings.Index(mimeType, "/")]
|
|
}
|
|
}
|
|
|
|
gr := r.newGenericResource(linker, fi, absPublishDir, absSourceFilename, filepath.ToSlash(relTargetFilename), mimeType)
|
|
|
|
if mimeType == "image" {
|
|
return &Image{
|
|
imaging: r.imaging,
|
|
genericResource: gr}, nil
|
|
}
|
|
return gr, nil
|
|
}
|
|
|
|
func (r *Spec) IsInCache(key string) bool {
|
|
// This is used for cache pruning. We currently only have images, but we could
|
|
// imagine expanding on this.
|
|
return r.imageCache.isInCache(key)
|
|
}
|
|
|
|
func (r *Spec) DeleteCacheByPrefix(prefix string) {
|
|
r.imageCache.deleteByPrefix(prefix)
|
|
}
|
|
|
|
func (r *Spec) CacheStats() string {
|
|
r.imageCache.mu.RLock()
|
|
defer r.imageCache.mu.RUnlock()
|
|
|
|
s := fmt.Sprintf("Cache entries: %d", len(r.imageCache.store))
|
|
|
|
count := 0
|
|
for k, _ := range r.imageCache.store {
|
|
if count > 5 {
|
|
break
|
|
}
|
|
s += "\n" + k
|
|
count++
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// genericResource represents a generic linkable resource.
|
|
type genericResource struct {
|
|
// The relative path to this resource.
|
|
rel string
|
|
|
|
// Base is set when the output format's path has a offset, e.g. for AMP.
|
|
base string
|
|
|
|
// Absolute filename to the source, including any content folder path.
|
|
absSourceFilename string
|
|
absPublishDir string
|
|
resourceType string
|
|
osFileInfo os.FileInfo
|
|
|
|
spec *Spec
|
|
link func(rel string) string
|
|
}
|
|
|
|
func (l *genericResource) Permalink() string {
|
|
return l.spec.PermalinkForBaseURL(l.RelPermalink(), l.spec.BaseURL.String())
|
|
}
|
|
|
|
func (l *genericResource) RelPermalink() string {
|
|
return l.relPermalinkForRel(l.rel)
|
|
}
|
|
|
|
// Implement the Cloner interface.
|
|
func (l genericResource) WithNewBase(base string) Resource {
|
|
l.base = base
|
|
return &l
|
|
}
|
|
|
|
func (l *genericResource) relPermalinkForRel(rel string) string {
|
|
if l.link != nil {
|
|
rel = l.link(rel)
|
|
}
|
|
|
|
if l.base != "" {
|
|
rel = path.Join(l.base, rel)
|
|
if rel[0] != '/' {
|
|
rel = "/" + rel
|
|
}
|
|
}
|
|
|
|
return l.spec.PathSpec.URLizeFilename(rel)
|
|
}
|
|
|
|
func (l *genericResource) ResourceType() string {
|
|
return l.resourceType
|
|
}
|
|
|
|
func (l *genericResource) AbsSourceFilename() string {
|
|
return l.absSourceFilename
|
|
}
|
|
|
|
func (l *genericResource) Publish() error {
|
|
f, err := l.spec.Fs.Source.Open(l.AbsSourceFilename())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
|
|
target := filepath.Join(l.absPublishDir, l.RelPermalink())
|
|
|
|
return helpers.WriteToDisk(target, f, l.spec.Fs.Destination)
|
|
}
|
|
|
|
func (r *Spec) newGenericResource(
|
|
linker func(base string) string,
|
|
osFileInfo os.FileInfo,
|
|
absPublishDir,
|
|
absSourceFilename,
|
|
baseFilename,
|
|
resourceType string) *genericResource {
|
|
|
|
return &genericResource{
|
|
link: linker,
|
|
osFileInfo: osFileInfo,
|
|
absPublishDir: absPublishDir,
|
|
absSourceFilename: absSourceFilename,
|
|
rel: baseFilename,
|
|
resourceType: resourceType,
|
|
spec: r,
|
|
}
|
|
}
|