Skip to content

Commit

Permalink
Support PostProcess for all file types
Browse files Browse the repository at this point in the history
Not just HTML.

Fixes #10269
  • Loading branch information
bep committed Sep 14, 2022
1 parent 1fd4c56 commit 74daca6
Show file tree
Hide file tree
Showing 9 changed files with 265 additions and 24 deletions.
13 changes: 8 additions & 5 deletions commands/hugo.go
Expand Up @@ -511,12 +511,15 @@ func (c *commandeer) build() error {
c.hugo().PrintProcessingStats(os.Stdout)
fmt.Println()

if createCounter, ok := c.publishDirFs.(hugofs.DuplicatesReporter); ok {
dupes := createCounter.ReportDuplicates()
if dupes != "" {
c.logger.Warnln("Duplicate target paths:", dupes)
hugofs.WalkFilesystems(c.publishDirFs, func(fs afero.Fs) bool {
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
dupes := dfs.ReportDuplicates()
if dupes != "" {
c.logger.Warnln("Duplicate target paths:", dupes)
}
}
}
return false
})

unusedTemplates := c.hugo().Tmpl().(tpl.UnusedTemplatesProvider).UnusedTemplates()
for _, unusedTemplate := range unusedTemplates {
Expand Down
57 changes: 57 additions & 0 deletions common/hugio/hasBytesWriter.go
@@ -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
}
64 changes: 64 additions & 0 deletions common/hugio/hasBytesWriter_test.go
@@ -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 changes: 19 additions & 1 deletion deps/deps.go
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/postpub"

"github.com/gohugoio/hugo/metrics"
"github.com/gohugoio/hugo/output"
Expand Down Expand Up @@ -78,6 +79,10 @@ type Deps struct {
// All the output formats available for the current site.
OutputFormatsConfig output.Formats

// FilenameHasPostProcessPrefix is a set of filenames in /public that
// contains a post-processing prefix.
FilenameHasPostProcessPrefix []string

templateProvider ResourceProvider
WithTemplate func(templ tpl.TemplateManager) error `json:"-"`

Expand Down Expand Up @@ -202,6 +207,7 @@ func New(cfg DepsCfg) (*Deps, error) {
var (
logger = cfg.Logger
fs = cfg.Fs
d *Deps
)

if cfg.TemplateProvider == nil {
Expand Down Expand Up @@ -239,6 +245,18 @@ func New(cfg DepsCfg) (*Deps, error) {
}
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)
if err != nil {
return nil, fmt.Errorf("create PathSpec: %w", err)
Expand Down Expand Up @@ -274,7 +292,7 @@ func New(cfg DepsCfg) (*Deps, error) {

logDistinct := helpers.NewDistinctLogger(logger)

d := &Deps{
d = &Deps{
Fs: fs,
Log: ignorableLogger,
LogDistinct: logDistinct,
Expand Down
90 changes: 90 additions & 0 deletions hugofs/hasbytes_fs.go
@@ -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()
}
19 changes: 5 additions & 14 deletions hugolib/hugo_sites_build.go
Expand Up @@ -18,7 +18,6 @@ import (
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"runtime/trace"
"strings"
Expand Down Expand Up @@ -439,23 +438,15 @@ func (h *HugoSites) postProcess() error {
return nil
}

_ = afero.Walk(h.BaseFs.PublishFs, "", func(path string, info os.FileInfo, err error) error {
if info == nil || info.IsDir() {
return nil
}

if !strings.HasSuffix(path, "html") {
return nil
}

for _, filename := range h.Deps.FilenameHasPostProcessPrefix {
filename := filename
g.Run(func() error {
return handleFile(path)
return handleFile(filename)
})

return nil
})
}

// Prepare for a new build.
h.Deps.FilenameHasPostProcessPrefix = nil
for _, s := range h.Sites {
s.ResourceSpec.PostProcessResources = make(map[string]postpub.PostPublishedResource)
}
Expand Down
10 changes: 10 additions & 0 deletions hugolib/resource_chain_test.go
Expand Up @@ -168,6 +168,11 @@ HELLO: {{ $hello.RelPermalink }}
HELLO: {{ $hello.RelPermalink }}|Integrity: {{ $hello.Data.Integrity }}|MediaType: {{ $hello.MediaType.Type }}
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
<a href="hugo.rocks">foo</a>
<a href="{{ $hello.RelPermalink }}" integrity="{{ $hello.Data.Integrity}}">Hello</a>
Expand All @@ -188,6 +193,11 @@ End.`)

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/hello.json", `
integrity": "md5-otHLJPJLMip9rVIEFMUj6Q==
mediaType": "text/html
relPermalink": "/hello.min.a2d1cb24f24b322a7dad520414c523e9.html"
`)
}

func BenchmarkResourceChainPostProcess(b *testing.B) {
Expand Down
8 changes: 6 additions & 2 deletions hugolib/testhelpers_test.go
Expand Up @@ -764,8 +764,12 @@ func (s *sitesBuilder) AssertImage(width, height int, filename string) {

func (s *sitesBuilder) AssertNoDuplicateWrites() {
s.Helper()
d := s.Fs.PublishDir.(hugofs.DuplicatesReporter)
s.Assert(d.ReportDuplicates(), qt.Equals, "")
hugofs.WalkFilesystems(s.Fs.PublishDir, func(fs afero.Fs) bool {
if dfs, ok := fs.(hugofs.DuplicatesReporter); ok {
s.Assert(dfs.ReportDuplicates(), qt.Equals, "")
}
return false
})
}

func (s *sitesBuilder) FileContent(filename string) string {
Expand Down
8 changes: 6 additions & 2 deletions resources/transform_test.go
Expand Up @@ -71,8 +71,12 @@ func TestTransform(t *testing.T) {
// Verify that we publish the same file once only.
assertNoDuplicateWrites := func(c *qt.C, spec *Spec) {
c.Helper()
d := spec.Fs.PublishDir.(hugofs.DuplicatesReporter)
c.Assert(d.ReportDuplicates(), qt.Equals, "")
hugofs.WalkFilesystems(spec.Fs.PublishDir, func(fs afero.Fs) bool {
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) {
Expand Down

0 comments on commit 74daca6

Please sign in to comment.