summaryrefslogtreecommitdiffstats
path: root/resource/resource.go
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-07-24 09:00:23 +0200
committerBjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>2017-12-27 18:44:47 +0100
commit3cdf19e9b7e46c57a9bb43ff02199177feb55768 (patch)
treed05e3dc15824c8eeef3e5455193d2d6328621f47 /resource/resource.go
parent02f2735f68e1bb2e2c412698755d52c4d396f237 (diff)
:sparkles: Implement Page bundling and image handling
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
Diffstat (limited to 'resource/resource.go')
-rw-r--r--resource/resource.go275
1 files changed, 275 insertions, 0 deletions
diff --git a/resource/resource.go b/resource/resource.go
new file mode 100644
index 000000000..2c934d031
--- /dev/null
+++ b/resource/resource.go
@@ -0,0 +1,275 @@
+// 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,
+ }
+}