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
This commit is contained in:
Bjørn Erik Pedersen
2017-07-24 09:00:23 +02:00
parent 02f2735f68
commit 3cdf19e9b7
85 changed files with 5791 additions and 3287 deletions

View File

@@ -25,6 +25,8 @@ import (
"github.com/bep/gitmap"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resource"
"github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/parser"
"github.com/mitchellh/mapstructure"
@@ -80,6 +82,8 @@ const (
kindSitemap = "sitemap"
kindRobotsTXT = "robotsTXT"
kind404 = "404"
pageResourceType = "page"
)
type Page struct {
@@ -101,6 +105,12 @@ type Page struct {
// This collection will be nil for regular pages.
Pages Pages
// Since Hugo 0.32, a Page can have resources such as images and CSS associated
// with itself. The resource will typically be placed relative to the Page,
// but templates should use the links (Permalink and RelPermalink)
// provided by the Resource object.
Resources resource.Resources
// translations will contain references to this page in other language
// if available.
translations Pages
@@ -155,9 +165,6 @@ type Page struct {
// workContent is a copy of rawContent that may be mutated during site build.
workContent []byte
// state telling if this is a "new page" or if we have rendered it previously.
rendered bool
// whether the content is in a CJK language.
isCJKLanguage bool
@@ -218,8 +225,9 @@ type Page struct {
Sitemap Sitemap
URLPath
permalink string
relPermalink string
permalink string
relPermalink string
relPermalinkBase string // relPermalink without extension
layoutDescriptor output.LayoutDescriptor
@@ -263,6 +271,10 @@ func (p *Page) PubDate() time.Time {
return p.Date
}
func (*Page) ResourceType() string {
return pageResourceType
}
func (p *Page) RSSLink() template.URL {
f, found := p.outputFormats.GetByName(output.RSSFormat.Name)
if !found {
@@ -726,22 +738,29 @@ func (p *Page) getRenderingConfig() *helpers.BlackFriday {
}
func (s *Site) newPage(filename string) *Page {
sp := source.NewSourceSpec(s.Cfg, s.Fs)
p := &Page{
fi := newFileInfo(
s.SourceSpec,
s.absContentDir(),
filename,
nil,
bundleNot,
)
return s.newPageFromFile(fi)
}
func (s *Site) newPageFromFile(fi *fileInfo) *Page {
return &Page{
pageInit: &pageInit{},
Kind: kindFromFilename(filename),
Kind: kindFromFilename(fi.Path()),
contentType: "",
Source: Source{File: *sp.NewFile(filename)},
Source: Source{File: fi},
Keywords: []string{}, Sitemap: Sitemap{Priority: -1},
Params: make(map[string]interface{}),
translations: make(Pages, 0),
sections: sectionsFromFilename(filename),
sections: sectionsFromDir(fi.Dir()),
Site: &s.Info,
s: s,
}
s.Log.DEBUG.Println("Reading from", p.File.Path())
return p
}
func (p *Page) IsRenderable() bool {
@@ -910,8 +929,8 @@ func (p *Page) LinkTitle() string {
}
func (p *Page) shouldBuild() bool {
return shouldBuild(p.s.Cfg.GetBool("buildFuture"), p.s.Cfg.GetBool("buildExpired"),
p.s.Cfg.GetBool("buildDrafts"), p.Draft, p.PublishDate, p.ExpiryDate)
return shouldBuild(p.s.BuildFuture, p.s.BuildExpired,
p.s.BuildDrafts, p.Draft, p.PublishDate, p.ExpiryDate)
}
func shouldBuild(buildFuture bool, buildExpired bool, buildDrafts bool, Draft bool,
@@ -967,20 +986,91 @@ func (p *Page) RelPermalink() string {
return p.relPermalink
}
func (p *Page) initURLs() error {
if len(p.outputFormats) == 0 {
p.outputFormats = p.s.outputFormats[p.Kind]
func (p *Page) subResourceLinkFactory(base string) string {
return path.Join(p.relPermalinkBase, base)
}
func (p *Page) prepareForRender(cfg *BuildCfg) error {
s := p.s
if !p.shouldRenderTo(s.rc.Format) {
// No need to prepare
return nil
}
var shortcodeUpdate bool
if p.shortcodeState != nil {
shortcodeUpdate = p.shortcodeState.updateDelta()
}
if !shortcodeUpdate && !cfg.whatChanged.other {
// No need to process it again.
return nil
}
// If we got this far it means that this is either a new Page pointer
// or a template or similar has changed so wee need to do a rerendering
// of the shortcodes etc.
// If in watch mode or if we have multiple output formats,
// we need to keep the original so we can
// potentially repeat this process on rebuild.
needsACopy := p.s.running() || len(p.outputFormats) > 1
var workContentCopy []byte
if needsACopy {
workContentCopy = make([]byte, len(p.workContent))
copy(workContentCopy, p.workContent)
} else {
// Just reuse the same slice.
workContentCopy = p.workContent
}
if p.Markup == "markdown" {
tmpContent, tmpTableOfContents := helpers.ExtractTOC(workContentCopy)
p.TableOfContents = helpers.BytesToHTML(tmpTableOfContents)
workContentCopy = tmpContent
}
rel := p.createRelativePermalink()
var err error
p.permalink, err = p.s.permalinkForOutputFormat(rel, p.outputFormats[0])
if err != nil {
return err
if workContentCopy, err = handleShortcodes(p, workContentCopy); err != nil {
s.Log.ERROR.Printf("Failed to handle shortcodes for page %s: %s", p.BaseFileName(), err)
}
rel = p.s.PathSpec.PrependBasePath(rel)
p.relPermalink = rel
p.layoutDescriptor = p.createLayoutDescriptor()
if p.Markup != "html" {
// Now we know enough to create a summary of the page and count some words
summaryContent, err := p.setUserDefinedSummaryIfProvided(workContentCopy)
if err != nil {
s.Log.ERROR.Printf("Failed to set user defined summary for page %q: %s", p.Path(), err)
} else if summaryContent != nil {
workContentCopy = summaryContent.content
}
p.Content = helpers.BytesToHTML(workContentCopy)
if summaryContent == nil {
if err := p.setAutoSummary(); err != nil {
s.Log.ERROR.Printf("Failed to set user auto summary for page %q: %s", p.pathOrTitle(), err)
}
}
} else {
p.Content = helpers.BytesToHTML(workContentCopy)
}
//analyze for raw stats
p.analyzePage()
// Handle bundled pages.
for _, r := range p.Resources.ByType(pageResourceType) {
p.s.PathSpec.ProcessingStats.Incr(&p.s.PathSpec.ProcessingStats.Pages)
bp := r.(*Page)
if err := bp.prepareForRender(cfg); err != nil {
s.Log.ERROR.Printf("Failed to prepare bundled page %q for render: %s", bp.BaseFileName(), err)
}
}
return nil
}
@@ -1849,14 +1939,18 @@ func (p *Page) addLangPathPrefixIfFlagSet(outfile string, should bool) string {
return outfile
}
func sectionsFromFilename(filename string) []string {
var sections []string
dir, _ := filepath.Split(filename)
dir = strings.TrimSuffix(dir, helpers.FilePathSeparator)
if dir == "" {
func sectionsFromDir(dirname string) []string {
sections := strings.Split(dirname, helpers.FilePathSeparator)
if len(sections) == 1 {
if sections[0] == "" {
return nil
}
return sections
}
sections = strings.Split(dir, helpers.FilePathSeparator)
if len(sections) > 1 && sections[0] == "" {
return sections[1:]
}
return sections
}