Skip to content

Commit

Permalink
Add render template hooks for links and images
Browse files Browse the repository at this point in the history
This commit also revises the change detection for templates used by content files in server mode.

Fixes gohugoio#6545
Fixes gohugoio#4663
Closes gohugoio#6043
  • Loading branch information
bep committed Dec 12, 2019
1 parent 0efb00c commit 16dd2db
Show file tree
Hide file tree
Showing 60 changed files with 1,784 additions and 347 deletions.
4 changes: 4 additions & 0 deletions deps/deps.go
Expand Up @@ -282,6 +282,10 @@ func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, er
return nil, err
}

if err != nil {
return nil, err
}

d.Site = cfg.Site

// The resource cache is global so reuse.
Expand Down
8 changes: 7 additions & 1 deletion docs/content/en/content-management/formats.md
Expand Up @@ -23,7 +23,13 @@ You can put any file type into your `/content` directories, but Hugo uses the `m
* [Shortcodes](/content-management/shortcodes/) processed
* Layout applied

## List of content formats
{{< deleteme >}}


## List of content formats.




The current list of content formats in Hugo:

Expand Down
3 changes: 3 additions & 0 deletions docs/content/en/getting-started/quick-start.md
Expand Up @@ -24,8 +24,11 @@ This quick start uses `macOS` in the examples. For instructions about how to ins
It is recommended to have [Git](https://git-scm.com/downloads) installed to run this tutorial.
{{% /note %}}

![Drag Racing](/images/Dragster.jpg "image title")


![Drag Racing](/images/Dragster2.jpg "image title")

## Step 1: Install Hugo

{{% note %}}
Expand Down
2 changes: 2 additions & 0 deletions docs/layouts/_default/_markup/render-image.html
@@ -0,0 +1,2 @@
{{ $url := "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6a/Mating_pair_of_Castalius_rosimon_WLB_DSC_2823.jpg/1024px-Mating_pair_of_Castalius_rosimon_WLB_DSC_2823.jpg" | safeURL }}
<img src="{{ $url }}" />
1 change: 1 addition & 0 deletions docs/layouts/_default/_markup/render-link.html
@@ -0,0 +1 @@
<a href="{{ .Destination | safeURL }}"{{ with .Title }} title="{{ . }}"{{ end }}>😉😉{{ .Text | safeHTML }} 😉😉</a>
1 change: 1 addition & 0 deletions docs/layouts/partials/deleteme.html
@@ -0,0 +1 @@
THIS IS PARTIAL!!!
3 changes: 3 additions & 0 deletions docs/layouts/shortcodes/deleteme.html
@@ -0,0 +1,3 @@
DELETEME PARTIAL:

{{ partial "deleteme" }}
4 changes: 3 additions & 1 deletion helpers/content.go
Expand Up @@ -25,13 +25,14 @@ import (

"github.com/gohugoio/hugo/common/loggers"

"github.com/spf13/afero"

"github.com/gohugoio/hugo/markup/converter"

"github.com/gohugoio/hugo/markup"

bp "github.com/gohugoio/hugo/bufferpool"
"github.com/gohugoio/hugo/config"
"github.com/spf13/afero"

"strings"
)
Expand Down Expand Up @@ -78,6 +79,7 @@ func NewContentSpec(cfg config.Provider, logger *loggers.Logger, contentFs afero
ContentFs: contentFs,
Logger: logger,
})

if err != nil {
return nil, err
}
Expand Down
3 changes: 1 addition & 2 deletions helpers/general_test.go
Expand Up @@ -21,9 +21,8 @@ import (

"github.com/spf13/viper"

"github.com/gohugoio/hugo/common/loggers"

qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
"github.com/spf13/afero"
)

Expand Down
144 changes: 144 additions & 0 deletions hugolib/content_render_hooks_test.go
@@ -0,0 +1,144 @@
// Copyright 2019 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 hugolib

import "testing"

func TestRenderHooks(t *testing.T) {
// TODO1 markdownify
config := `
baseURL="https://example.org"
workingDir="/mywork"
`
b := newTestSitesBuilder(t).WithWorkingDir("/mywork").WithConfigFile("toml", config).Running()
b.WithTemplatesAdded("_default/single.html", `{{ .Content }}`)
b.WithTemplatesAdded("shortcodes/myshortcode1.html", `{{ partial "mypartial1" }}`)
b.WithTemplatesAdded("shortcodes/myshortcode2.html", `{{ partial "mypartial2" }}`)
b.WithTemplatesAdded("shortcodes/myshortcode3.html", `SHORT3|`)
b.WithTemplatesAdded("shortcodes/myshortcode4.html", `
<div class="foo">
{{ .Inner | markdownify }}
</div>
`)
b.WithTemplatesAdded("shortcodes/myshortcode5.html", `
<div class="foo">
{{ .Inner | .Page.RenderString }}
</div>
`)

b.WithTemplatesAdded("partials/mypartial1.html", `PARTIAL1`)
b.WithTemplatesAdded("partials/mypartial2.html", `PARTIAL2 {{ partial "mypartial3.html" }}`)
b.WithTemplatesAdded("partials/mypartial3.html", `PARTIAL3`)
b.WithTemplatesAdded("_default/_markup/render-link.html", `{{ with .Page }}{{ .Title }}{{ end }}|{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)
b.WithTemplatesAdded("docs/_markup/render-link.html", `Link docs section: {{ .Text | safeHTML }}|END`)
b.WithTemplatesAdded("_default/_markup/render-image.html", `IMAGE: {{ .Page.Title }}||{{ .Destination | safeURL }}|Title: {{ .Title | safeHTML }}|Text: {{ .Text | safeHTML }}|END`)

b.WithContent("blog/p1.md", `---
title: Cool Page
---
[First Link](https://www.google.com "Google's Homepage")
{{< myshortcode3 >}}
[Second Link](https://www.google.com "Google's Homepage")
Image:
![Drag Racing](/images/Dragster.jpg "image title")
`, "blog/p2.md", `---
title: Cool Page2
layout: mylayout
---
{{< myshortcode1 >}}
[Some Text](https://www.google.com "Google's Homepage")
`, "blog/p3.md", `---
title: Cool Page3
---
{{< myshortcode2 >}}
`, "docs/docs1.md", `---
title: Docs 1
---
[Docs 1](https://www.google.com "Google's Homepage")
`, "blog/p4.md", `---
title: Cool Page With Image
---
Image:
![Drag Racing](/images/Dragster.jpg "image title")
`, "blog/p5.md", `---
title: Cool Page With Markdownify
---
{{< myshortcode4 >}}
Inner Link: [Inner Link](https://www.google.com "Google's Homepage")
{{< /myshortcode4 >}}
`, "blog/p6.md", `---
title: With RenderString
---
{{< myshortcode5 >}}Inner Link: [Inner Link](https://www.gohugo.io "Hugo's Homepage"){{< /myshortcode5 >}}
`)
b.Build(BuildCfg{})
b.AssertFileContent("public/blog/p1/index.html", `
<p>Cool Page|https://www.google.com|Title: Google's Homepage|Text: First Link|END</p>
Text: Second
SHORT3|
<p>IMAGE: Cool Page||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>
`)
b.AssertFileContent("public/blog/p2/index.html", `PARTIAL`)
b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3`)
b.AssertFileContent("public/docs/docs1/index.html", `Link docs section: Docs 1|END`)
b.AssertFileContent("public/blog/p4/index.html", `<p>IMAGE: Cool Page With Image||/images/Dragster.jpg|Title: image title|Text: Drag Racing|END</p>`)
// The regular markdownify func currently gets regular links.
b.AssertFileContent("public/blog/p5/index.html", "Inner Link: <a href=\"https://www.google.com\" title=\"Google's Homepage\">Inner Link</a>\n</div>")

b.AssertFileContent("public/blog/p6/index.html", "<div class=\"foo\">\n<p>Inner Link: With RenderString|https://www.gohugo.io|Title: Hugo's Homepage|Text: Inner Link|END</p>\n\n</div>")

b.EditFiles(
"layouts/_default/_markup/render-link.html", `EDITED: {{ .Destination | safeURL }}|`,
"layouts/_default/_markup/render-image.html", `IMAGE EDITED: {{ .Destination | safeURL }}|`,
"layouts/docs/_markup/render-link.html", `DOCS EDITED: {{ .Destination | safeURL }}|`,
"layouts/partials/mypartial1.html", `PARTIAL1_EDITED`,
"layouts/partials/mypartial3.html", `PARTIAL3_EDITED`,
"layouts/shortcodes/myshortcode3.html", `SHORT3_EDITED|`,
)
b.Build(BuildCfg{})
b.AssertFileContent("public/blog/p1/index.html", `<p>EDITED: https://www.google.com|</p>`, "SHORT3_EDITED|")
b.AssertFileContent("public/blog/p2/index.html", `PARTIAL1_EDITED`)
b.AssertFileContent("public/blog/p3/index.html", `PARTIAL3_EDITED`)
b.AssertFileContent("public/docs/docs1/index.html", `DOCS EDITED: https://www.google.com|</p>`)
b.AssertFileContent("public/blog/p4/index.html", `IMAGE EDITED: /images/Dragster.jpg|`)
b.AssertFileContent("public/blog/p6/index.html", "<p>Inner Link: EDITED: https://www.gohugo.io|</p>")

}
49 changes: 41 additions & 8 deletions hugolib/filesystems/basefs.go
Expand Up @@ -126,10 +126,28 @@ type SourceFilesystems struct {
StaticDirs []hugofs.FileMetaInfo
}

// FileSystems returns the FileSystems relevant for the change detection
// in server mode.
// Note: This does currently not return any static fs.
func (s *SourceFilesystems) FileSystems() []*SourceFilesystem {
return []*SourceFilesystem{
s.Content,
s.Data,
s.I18n,
s.Layouts,
s.Archetypes,
// TODO(bep) static
}

}

// A SourceFilesystem holds the filesystem for a given source type in Hugo (data,
// i18n, layouts, static) and additional metadata to be able to use that filesystem
// in server mode.
type SourceFilesystem struct {
// Name matches one in files.ComponentFolders
Name string

// This is a virtual composite filesystem. It expects path relative to a context.
Fs afero.Fs

Expand Down Expand Up @@ -275,6 +293,19 @@ func (d *SourceFilesystem) Contains(filename string) bool {
return false
}

// Path returns the relative path to the given filename if it is a member of
// of the current filesystem, an empty string if not.
func (d *SourceFilesystem) Path(filename string) string {
for _, dir := range d.Dirs {
meta := dir.Meta()
if strings.HasPrefix(filename, meta.Filename()) {
p := strings.TrimPrefix(strings.TrimPrefix(filename, meta.Filename()), filePathSeparator)
return p
}
}
return ""
}

// RealDirs gets a list of absolute paths to directories starting from the given
// path.
func (d *SourceFilesystem) RealDirs(from string) []string {
Expand Down Expand Up @@ -349,12 +380,14 @@ func newSourceFilesystemsBuilder(p *paths.Paths, logger *loggers.Logger, b *Base
return &sourceFilesystemsBuilder{p: p, logger: logger, sourceFs: sourceFs, theBigFs: b.theBigFs, result: &SourceFilesystems{}}
}

func (b *sourceFilesystemsBuilder) newSourceFilesystem(fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
func (b *sourceFilesystemsBuilder) newSourceFilesystem(name string, fs afero.Fs, dirs []hugofs.FileMetaInfo) *SourceFilesystem {
return &SourceFilesystem{
Name: name,
Fs: fs,
Dirs: dirs,
}
}

func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {

if b.theBigFs == nil {
Expand All @@ -369,12 +402,12 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {

createView := func(componentID string) *SourceFilesystem {
if b.theBigFs == nil || b.theBigFs.overlayMounts == nil {
return b.newSourceFilesystem(hugofs.NoOpFs, nil)
return b.newSourceFilesystem(componentID, hugofs.NoOpFs, nil)
}

dirs := b.theBigFs.overlayDirs[componentID]

return b.newSourceFilesystem(afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)
return b.newSourceFilesystem(componentID, afero.NewBasePathFs(b.theBigFs.overlayMounts, componentID), dirs)

}

Expand All @@ -392,14 +425,14 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, err
}

b.result.Data = b.newSourceFilesystem(dataFs, dataDirs)
b.result.Data = b.newSourceFilesystem(files.ComponentFolderData, dataFs, dataDirs)

i18nDirs := b.theBigFs.overlayDirs[files.ComponentFolderI18n]
i18nFs, err := hugofs.NewSliceFs(i18nDirs...)
if err != nil {
return nil, err
}
b.result.I18n = b.newSourceFilesystem(i18nFs, i18nDirs)
b.result.I18n = b.newSourceFilesystem(files.ComponentFolderI18n, i18nFs, i18nDirs)

contentDirs := b.theBigFs.overlayDirs[files.ComponentFolderContent]
contentBfs := afero.NewBasePathFs(b.theBigFs.overlayMountsContent, files.ComponentFolderContent)
Expand All @@ -409,7 +442,7 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
return nil, errors.Wrap(err, "create content filesystem")
}

b.result.Content = b.newSourceFilesystem(contentFs, contentDirs)
b.result.Content = b.newSourceFilesystem(files.ComponentFolderContent, contentFs, contentDirs)

b.result.Work = afero.NewReadOnlyFs(b.theBigFs.overlayFull)

Expand All @@ -421,13 +454,13 @@ func (b *sourceFilesystemsBuilder) Build() (*SourceFilesystems, error) {
if b.theBigFs.staticPerLanguage != nil {
// Multihost mode
for k, v := range b.theBigFs.staticPerLanguage {
sfs := b.newSourceFilesystem(v, b.result.StaticDirs)
sfs := b.newSourceFilesystem(files.ComponentFolderStatic, v, b.result.StaticDirs)
sfs.PublishFolder = k
ms[k] = sfs
}
} else {
bfs := afero.NewBasePathFs(b.theBigFs.overlayMountsStatic, files.ComponentFolderStatic)
ms[""] = b.newSourceFilesystem(bfs, b.result.StaticDirs)
ms[""] = b.newSourceFilesystem(files.ComponentFolderStatic, bfs, b.result.StaticDirs)
}

return b.result, nil
Expand Down
3 changes: 3 additions & 0 deletions hugolib/hugo_modules_test.go
Expand Up @@ -40,6 +40,9 @@ import (

// TODO(bep) this fails when testmodBuilder is also building ...
func TestHugoModules(t *testing.T) {
if !isCI() {
t.Skip("skip (relative) long running modules test when running locally")
}
t.Parallel()

if hugo.GoMinorVersion() < 12 {
Expand Down

0 comments on commit 16dd2db

Please sign in to comment.