Support PostProcess for all file types

Not just HTML.

Fixes #10269
This commit is contained in:
Bjørn Erik Pedersen 2022-09-14 11:58:45 +02:00
parent 1fd4c562af
commit 74daca6b30
9 changed files with 265 additions and 24 deletions

View file

@ -511,12 +511,15 @@ func (c *commandeer) build() error {
c.hugo().PrintProcessingStats(os.Stdout) c.hugo().PrintProcessingStats(os.Stdout)
fmt.Println() fmt.Println()
if createCounter, ok := c.publishDirFs.(hugofs.DuplicatesReporter); ok { hugofs.WalkFilesystems(c.publishDirFs, func(fs afero.Fs) bool {
dupes := createCounter.ReportDuplicates() if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
if dupes != "" { dupes := dfs.ReportDuplicates()
c.logger.Warnln("Duplicate target paths:", dupes) if dupes != "" {
c.logger.Warnln("Duplicate target paths:", dupes)
}
} }
} return false
})
unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates() unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
for _, unusedTemplate := range unusedTemplates { for _, unusedTemplate := range unusedTemplates {

View file

@ -0,0 +1,57 @@
// Copyright 2022 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 hugio
import (
"bytes"
)
// HasBytesWriter is a writer that will set Match to true if the given pattern
// is found in the stream.
type HasBytesWriter struct {
Match bool
Pattern []byte
i int
done bool
buff []byte
}
func (h *HasBytesWriter) Write(p []byte) (n int, err error) {
if h.done {
return len(p), nil
}
if len(h.buff) == 0 {
h.buff = make([]byte, len(h.Pattern)*2)
}
for i := range p {
h.buff[h.i] = p[i]
h.i++
if h.i == len(h.buff) {
// Shift left.
copy(h.buff, h.buff[len(h.buff)/2:])
h.i = len(h.buff) / 2
}
if bytes.Contains(h.buff, h.Pattern) {
h.Match = true
h.done = true
return len(p), nil
}
}
return len(p), nil
}

View file

@ -0,0 +1,64 @@
// Copyright 2022 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 hugio
import (
"bytes"
"fmt"
"io"
"math/rand"
"strings"
"testing"
"time"
qt "github.com/frankban/quicktest"
)
func TestHasBytesWriter(t *testing.T) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
c := qt.New((t))
neww := func() (*HasBytesWriter, io.Writer) {
var b bytes.Buffer
h := &HasBytesWriter{
Pattern: []byte("__foo"),
}
return h, io.MultiWriter(&b, h)
}
rndStr := func() string {
return strings.Repeat("ab cfo", r.Intn(33))
}
for i := 0; i < 22; i++ {
h, w := neww()
fmt.Fprintf(w, rndStr()+"abc __foobar"+rndStr())
c.Assert(h.Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __f")
fmt.Fprintf(w, "oo bar"+rndStr())
c.Assert(h.Match, qt.Equals, true)
h, w = neww()
fmt.Fprintf(w, rndStr()+"abc __moo bar")
c.Assert(h.Match, qt.Equals, false)
}
h, w := neww()
fmt.Fprintf(w, "__foo")
c.Assert(h.Match, qt.Equals, true)
}

20
deps/deps.go vendored
View file

@ -16,6 +16,7 @@ import (
"github.com/gohugoio/hugo/langs" "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media" "github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page" "github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/postpub"
"github.com/gohugoio/hugo/metrics" "github.com/gohugoio/hugo/metrics"
"github.com/gohugoio/hugo/output" "github.com/gohugoio/hugo/output"
@ -78,6 +79,10 @@ type Deps struct {
// All the output formats available for the current site. // All the output formats available for the current site.
OutputFormatsConfig output.Formats OutputFormatsConfig output.Formats
// FilenameHasPostProcessPrefix is a set of filenames in /public that
// contains a post-processing prefix.
FilenameHasPostProcessPrefix []string
templateProvider ResourceProvider templateProvider ResourceProvider
WithTemplate func(templ tpl.TemplateManager) error `json:"-"` WithTemplate func(templ tpl.TemplateManager) error `json:"-"`
@ -202,6 +207,7 @@ func New(cfg DepsCfg) (*Deps, error) {
var ( var (
logger = cfg.Logger logger = cfg.Logger
fs = cfg.Fs fs = cfg.Fs
d *Deps
) )
if cfg.TemplateProvider == nil { if cfg.TemplateProvider == nil {
@ -239,6 +245,18 @@ func New(cfg DepsCfg) (*Deps, error) {
} }
execHelper := hexec.New(securityConfig) execHelper := hexec.New(securityConfig)
var filenameHasPostProcessPrefixMu sync.Mutex
cb := func(name string, match bool) {
if !match {
return
}
filenameHasPostProcessPrefixMu.Lock()
d.FilenameHasPostProcessPrefix = append(d.FilenameHasPostProcessPrefix, name)
filenameHasPostProcessPrefixMu.Unlock()
}
fs.PublishDir = hugofs.NewHasBytesReceiver(fs.PublishDir, cb, []byte(postpub.PostProcessPrefix))
ps, err := helpers.NewPathSpec(fs, cfg.Language, logger) ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
if err != nil { if err != nil {
return nil, fmt.Errorf("create PathSpec: %w", err) return nil, fmt.Errorf("create PathSpec: %w", err)
@ -274,7 +292,7 @@ func New(cfg DepsCfg) (*Deps, error) {
logDistinct := helpers.NewDistinctLogger(logger) logDistinct := helpers.NewDistinctLogger(logger)
d := &Deps{ d = &Deps{
Fs: fs, Fs: fs,
Log: ignorableLogger, Log: ignorableLogger,
LogDistinct: logDistinct, LogDistinct: logDistinct,

90
hugofs/hasbytes_fs.go Normal file
View file

@ -0,0 +1,90 @@
// Copyright 2022 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 hugofs
import (
"os"
"github.com/gohugoio/hugo/common/hugio"
"github.com/spf13/afero"
)
var (
_ afero.Fs = (*hasBytesFs)(nil)
_ FilesystemUnwrapper = (*hasBytesFs)(nil)
)
type hasBytesFs struct {
afero.Fs
hasBytesCallback func(name string, match bool)
pattern []byte
}
func NewHasBytesReceiver(delegate afero.Fs, hasBytesCallback func(name string, match bool), pattern []byte) afero.Fs {
return &hasBytesFs{Fs: delegate, hasBytesCallback: hasBytesCallback, pattern: pattern}
}
func (fs *hasBytesFs) UnwrapFilesystem() afero.Fs {
return fs.Fs
}
func (fs *hasBytesFs) Create(name string) (afero.File, error) {
f, err := fs.Fs.Create(name)
if err == nil {
f = fs.wrapFile(f)
}
return f, err
}
func (fs *hasBytesFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
f, err := fs.Fs.OpenFile(name, flag, perm)
if err == nil && isWrite(flag) {
f = fs.wrapFile(f)
}
return f, err
}
func (fs *hasBytesFs) wrapFile(f afero.File) afero.File {
return &hasBytesFile{
File: f,
hbw: &hugio.HasBytesWriter{
Pattern: fs.pattern,
},
hasBytesCallback: fs.hasBytesCallback,
}
}
func (fs *hasBytesFs) Name() string {
return "hasBytesFs"
}
type hasBytesFile struct {
hasBytesCallback func(name string, match bool)
hbw *hugio.HasBytesWriter
afero.File
}
func (h *hasBytesFile) Write(p []byte) (n int, err error) {
n, err = h.File.Write(p)
if err != nil {
return
}
return h.hbw.Write(p)
}
func (h *hasBytesFile) Close() error {
h.hasBytesCallback(h.Name(), h.hbw.Match)
return h.File.Close()
}

View file

@ -18,7 +18,6 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os"
"path/filepath" "path/filepath"
"runtime/trace" "runtime/trace"
"strings" "strings"
@ -439,23 +438,15 @@ func (h *HugoSites) postProcess() error {
return nil return nil
} }
_ = afero.Walk(h.BaseFs.PublishFs, "", func(path string, info os.FileInfo, err error) error { for _, filename := range h.Deps.FilenameHasPostProcessPrefix {
if info == nil || info.IsDir() { filename := filename
return nil
}
if !strings.HasSuffix(path, "html") {
return nil
}
g.Run(func() error { g.Run(func() error {
return handleFile(path) return handleFile(filename)
}) })
}
return nil
})
// Prepare for a new build. // Prepare for a new build.
h.Deps.FilenameHasPostProcessPrefix = nil
for _, s := range h.Sites { for _, s := range h.Sites {
s.ResourceSpec.PostProcessResources = make(map[string]postpub.PostPublishedResource) s.ResourceSpec.PostProcessResources = make(map[string]postpub.PostPublishedResource)
} }

View file

@ -168,6 +168,11 @@ HELLO: {{ $hello.RelPermalink }}
HELLO: {{ $hello.RelPermalink }}|Integrity: {{ $hello.Data.Integrity }}|MediaType: {{ $hello.MediaType.Type }} HELLO: {{ $hello.RelPermalink }}|Integrity: {{ $hello.Data.Integrity }}|MediaType: {{ $hello.MediaType.Type }}
HELLO2: Name: {{ $hello.Name }}|Content: {{ $hello.Content }}|Title: {{ $hello.Title }}|ResourceType: {{ $hello.ResourceType }} HELLO2: Name: {{ $hello.Name }}|Content: {{ $hello.Content }}|Title: {{ $hello.Title }}|ResourceType: {{ $hello.ResourceType }}
// Issue #10269
{{ $m := dict "relPermalink" $hello.RelPermalink "integrity" $hello.Data.Integrity "mediaType" $hello.MediaType.Type }}
{{ $json := jsonify (dict "indent" " ") $m | resources.FromString "hello.json" -}}
JSON: {{ $json.RelPermalink }}
// Issue #8884 // Issue #8884
<a href="hugo.rocks">foo</a> <a href="hugo.rocks">foo</a>
<a href="{{ $hello.RelPermalink }}" integrity="{{ $hello.Data.Integrity}}">Hello</a> <a href="{{ $hello.RelPermalink }}" integrity="{{ $hello.Data.Integrity}}">Hello</a>
@ -188,6 +193,11 @@ End.`)
b.AssertFileContent("public/page1/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`) b.AssertFileContent("public/page1/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`)
b.AssertFileContent("public/page2/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`) b.AssertFileContent("public/page2/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`)
b.AssertFileContent("public/hello.json", `
integrity": "md5-otHLJPJLMip9rVIEFMUj6Q==
mediaType": "text/html
relPermalink": "/hello.min.a2d1cb24f24b322a7dad520414c523e9.html"
`)
} }
func BenchmarkResourceChainPostProcess(b *testing.B) { func BenchmarkResourceChainPostProcess(b *testing.B) {

View file

@ -764,8 +764,12 @@ func (s *sitesBuilder) AssertImage(width, height int, filename string) {
func (s *sitesBuilder) AssertNoDuplicateWrites() { func (s *sitesBuilder) AssertNoDuplicateWrites() {
s.Helper() s.Helper()
d := s.Fs.PublishDir.(hugofs.DuplicatesReporter) hugofs.WalkFilesystems(s.Fs.PublishDir, func(fs afero.Fs) bool {
s.Assert(d.ReportDuplicates(), qt.Equals, "") if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
s.Assert(dfs.ReportDuplicates(), qt.Equals, "")
}
return false
})
} }
func (s *sitesBuilder) FileContent(filename string) string { func (s *sitesBuilder) FileContent(filename string) string {

View file

@ -71,8 +71,12 @@ func TestTransform(t *testing.T) {
// Verify that we publish the same file once only. // Verify that we publish the same file once only.
assertNoDuplicateWrites := func(c *qt.C, spec *Spec) { assertNoDuplicateWrites := func(c *qt.C, spec *Spec) {
c.Helper() c.Helper()
d := spec.Fs.PublishDir.(hugofs.DuplicatesReporter) hugofs.WalkFilesystems(spec.Fs.PublishDir, func(fs afero.Fs) bool {
c.Assert(d.ReportDuplicates(), qt.Equals, "") if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
c.Assert(dfs.ReportDuplicates(), qt.Equals, "")
}
return false
})
} }
assertShouldExist := func(c *qt.C, spec *Spec, filename string, should bool) { assertShouldExist := func(c *qt.C, spec *Spec, filename string, should bool) {