diff options
author | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-07-24 09:00:23 +0200 |
---|---|---|
committer | Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com> | 2017-12-27 18:44:47 +0100 |
commit | 3cdf19e9b7e46c57a9bb43ff02199177feb55768 (patch) | |
tree | d05e3dc15824c8eeef3e5455193d2d6328621f47 /resource/resource.go | |
parent | 02f2735f68e1bb2e2c412698755d52c4d396f237 (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.go | 275 |
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, + } +} |