mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
parent
1fd4c562af
commit
74daca6b30
9 changed files with 265 additions and 24 deletions
|
@ -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 {
|
||||||
|
|
57
common/hugio/hasBytesWriter.go
Normal file
57
common/hugio/hasBytesWriter.go
Normal 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
|
||||||
|
}
|
64
common/hugio/hasBytesWriter_test.go
Normal file
64
common/hugio/hasBytesWriter_test.go
Normal 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
20
deps/deps.go
vendored
|
@ -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
90
hugofs/hasbytes_fs.go
Normal 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()
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in a new issue