hugo/source/fileInfo.go
Bjørn Erik Pedersen 3cdf19e9b7
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
2017-12-27 18:44:47 +01:00

214 lines
5.4 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 source
import (
"io"
"os"
"path/filepath"
"strings"
"sync"
"github.com/gohugoio/hugo/helpers"
)
// fileInfo implements the File interface.
var (
_ File = (*FileInfo)(nil)
_ ReadableFile = (*FileInfo)(nil)
)
type File interface {
// Filename gets the full path and filename to the file.
Filename() string
// Path gets the relative path including file name and extension.
// The directory is relative to the content root.
Path() string
// Dir gets the name of the directory that contains this file.
// The directory is relative to the content root.
Dir() string
// Extension gets the file extension, i.e "myblogpost.md" will return "md".
Extension() string
// Ext is an alias for Extension.
Ext() string // Hmm... Deprecate Extension
// Lang for this page, if `Multilingual` is enabled on your site.
Lang() string
// LogicalName is filename and extension of the file.
LogicalName() string
// Section is first directory below the content root.
Section() string
// BaseFileName is a filename without extension.
BaseFileName() string
// TranslationBaseName is a filename with no extension,
// not even the optional language extension part.
TranslationBaseName() string
// UniqueID is the MD5 hash of the file's path and is for most practical applications,
// Hugo content files being one of them, considered to be unique.
UniqueID() string
FileInfo() os.FileInfo
String() string
// Deprecated
Bytes() []byte
}
// A ReadableFile is a File that is readable.
type ReadableFile interface {
File
Open() (io.ReadCloser, error)
}
type FileInfo struct {
// Absolute filename to the file on disk.
filename string
fi os.FileInfo
// Derived from filename
ext string // Extension without any "."
lang string
name string
dir string
relDir string
relPath string
baseName string
translationBaseName string
section string
uniqueID string
sp *SourceSpec
lazyInit sync.Once
}
func (fi *FileInfo) Filename() string { return fi.filename }
func (fi *FileInfo) Path() string { return fi.relPath }
func (fi *FileInfo) Dir() string { return fi.relDir }
func (fi *FileInfo) Extension() string { return fi.Ext() }
func (fi *FileInfo) Ext() string { return fi.ext }
func (fi *FileInfo) Lang() string { return fi.lang }
func (fi *FileInfo) LogicalName() string { return fi.name }
func (fi *FileInfo) BaseFileName() string { return fi.baseName }
func (fi *FileInfo) TranslationBaseName() string { return fi.translationBaseName }
func (fi *FileInfo) Section() string {
fi.init()
return fi.section
}
func (fi *FileInfo) UniqueID() string {
fi.init()
return fi.uniqueID
}
func (fi *FileInfo) FileInfo() os.FileInfo {
return fi.fi
}
func (fi *FileInfo) Bytes() []byte {
// Remove in Hugo 0.34
helpers.Deprecated("File", "Bytes", "", false)
return []byte("")
}
func (fi *FileInfo) String() string { return fi.BaseFileName() }
// We create a lot of these FileInfo objects, but there are parts of it used only
// in some cases that is slightly expensive to construct.
func (fi *FileInfo) init() {
fi.lazyInit.Do(func() {
parts := strings.Split(fi.relDir, helpers.FilePathSeparator)
var section string
if len(parts) == 1 {
section = parts[0]
} else if len(parts) > 1 {
if parts[0] == "" {
section = parts[1]
} else {
section = parts[0]
}
}
fi.section = section
fi.uniqueID = helpers.MD5String(filepath.ToSlash(fi.relPath))
})
}
func (sp *SourceSpec) NewFileInfo(baseDir, filename string, fi os.FileInfo) *FileInfo {
dir, name := filepath.Split(filename)
dir = strings.TrimSuffix(dir, helpers.FilePathSeparator)
baseDir = strings.TrimSuffix(baseDir, helpers.FilePathSeparator)
relDir := ""
if dir != baseDir {
relDir = strings.TrimPrefix(dir, baseDir)
}
relDir = strings.TrimPrefix(relDir, helpers.FilePathSeparator)
relPath := filepath.Join(relDir, name)
ext := strings.ToLower(strings.TrimPrefix(filepath.Ext(name), "."))
baseName := helpers.Filename(name)
lang := strings.TrimPrefix(filepath.Ext(baseName), ".")
var translationBaseName string
if _, ok := sp.Languages[lang]; lang == "" || !ok {
lang = sp.DefaultContentLanguage
translationBaseName = baseName
} else {
translationBaseName = helpers.Filename(baseName)
}
f := &FileInfo{
sp: sp,
filename: filename,
fi: fi,
lang: lang,
ext: ext,
dir: dir,
relDir: relDir,
relPath: relPath,
name: name,
baseName: baseName,
translationBaseName: translationBaseName,
}
return f
}
// Open implements ReadableFile.
func (fi *FileInfo) Open() (io.ReadCloser, error) {
return fi.sp.Fs.Source.Open(fi.Filename())
}