Compare commits

...

15 Commits

Author SHA1 Message Date
Bjørn Erik Pedersen 21e9cb7b79 dynacache: Fix potential deadlocks on panics in GetOrCreate 2024-10-26 18:26:11 +02:00
hugoreleaser ec3890affe releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-24 12:41:02 +00:00
hugoreleaser 46cccb021b releaser: Bump versions for release of 0.136.5
[ci skip]
2024-10-24 12:26:27 +00:00
Bjørn Erik Pedersen cb6e27b32a hugolib/commands: Fix stuck server error issues
Fixes #11378
2024-10-24 13:34:11 +02:00
Joe Mooring 5bbe95f9c5 tpl/transform: Revert unmarshal whitespace removal
Fixes #12977
2024-10-22 09:42:40 +02:00
hugoreleaser 31d19b505d releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-21 13:46:41 +00:00
hugoreleaser bfa2fd683e releaser: Bump versions for release of 0.136.3
[ci skip]
2024-10-21 13:31:54 +00:00
David Karlsson 88d598a049 docker: Fix permission issues in Dockerfile
Closes #12971
Closes #12970
2024-10-21 15:28:36 +02:00
Bjørn Erik Pedersen 352be5ba87 Make sure that HugoSites is always closed when done
Including all the integration tests.
2024-10-20 13:04:58 +02:00
Joe Mooring d37606d2c2 tpl/strings: Add TrimSpace function
Closes #12962
2024-10-19 21:10:00 +02:00
Bjørn Erik Pedersen f5e54d9c7d common/herrors: Fix the deferred error message cleaner regexp
Make it less gready.
2024-10-19 10:00:01 +02:00
Bjørn Erik Pedersen 42f37b4e98 tpl/transform: Don't fail on "no data to transform"
Fixes #12964
2024-10-18 10:30:36 +02:00
hugoreleaser e971b7d866 releaser: Prepare repository for 0.137.0-DEV
[ci skip]
2024-10-17 14:44:10 +00:00
hugoreleaser ad985550a4 releaser: Bump versions for release of 0.136.2
[ci skip]
2024-10-17 14:30:05 +00:00
David Karlsson b5852d0e68 docker: Fix Dart Sass ARM64 arch mismatch, /cache permissions
Also improve the final build step.

Closes #12956
Closes #12957
Closes #12960
2024-10-17 15:42:41 +02:00
25 changed files with 285 additions and 126 deletions
+15 -11
View File
@@ -3,7 +3,8 @@
# Website: https://gohugo.io/
ARG GO_VERSION="1.23.2"
ARG ALPINE_VERSION=3.20
ARG ALPINE_VERSION="3.20"
ARG DART_SASS_VERSION="1.79.3"
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.5.0 AS xx
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine${ALPINE_VERSION} AS gobuild
@@ -45,6 +46,15 @@ RUN --mount=target=. \
xx-verify /usr/bin/hugo
EOT
# dart-sass downloads the dart-sass runtime dependency
FROM alpine:${ALPINE_VERSION} AS dart-sass
ARG TARGETARCH
ARG DART_SASS_VERSION
ARG DART_ARCH=${TARGETARCH/amd64/x64}
WORKDIR /out
ADD https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz .
RUN tar -xf dart-sass-${DART_SASS_VERSION}-linux-${DART_ARCH}.tar.gz
FROM gorun AS final
COPY --from=build /usr/bin/hugo /usr/bin/hugo
@@ -54,32 +64,27 @@ RUN apk add --no-cache \
libc6-compat \
git \
runuser \
curl \
nodejs \
npm
RUN mkdir -p /var/hugo/bin && \
RUN mkdir -p /var/hugo/bin /cache && \
addgroup -Sg 1000 hugo && \
adduser -Sg hugo -u 1000 -h /var/hugo hugo && \
chown -R hugo: /var/hugo && \
chown -R hugo: /var/hugo /cache && \
# For the Hugo's Git integration to work.
runuser -u hugo -- git config --global --add safe.directory /project && \
# See https://github.com/gohugoio/hugo/issues/9810
runuser -u hugo -- git config --global core.quotepath false
USER hugo:hugo
VOLUME /project
WORKDIR /project
USER hugo:hugo
ENV HUGO_CACHEDIR=/cache
ARG BUILDARCH
ENV BUILDARCH=${BUILDARCH}
ENV PATH="/var/hugo/bin:$PATH"
COPY scripts/docker scripts/docker
COPY scripts/docker/entrypoint.sh /entrypoint.sh
COPY --from=dart-sass /out/dart-sass /var/hugo/bin/dart-sass
# Install default dependencies.
RUN scripts/docker/install_runtimedeps_default.sh
# Update PATH to reflect the new dependencies.
# For more complex setups, we should probably find a way to
# delegate this to the script itself, but this will have to do for now.
@@ -92,4 +97,3 @@ EXPOSE 1313
ENTRYPOINT ["/entrypoint.sh"]
CMD ["--help"]
+25
View File
@@ -14,8 +14,10 @@
package dynacache
import (
"fmt"
"path/filepath"
"testing"
"time"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/common/loggers"
@@ -165,6 +167,29 @@ func TestClear(t *testing.T) {
cache.adjustCurrentMaxSize()
}
func TestPanicInCreate(t *testing.T) {
t.Parallel()
c := qt.New(t)
cache := newTestCache(t)
p1 := GetOrCreatePartition[string, testItem](cache, "/aaaa/bbbb", OptionsPartition{Weight: 30, ClearWhen: ClearOnRebuild})
for i := 0; i < 3; i++ {
for j := 0; j < 3; j++ {
_, err := p1.GetOrCreate(fmt.Sprintf("panic1-%d", i), func(string) (testItem, error) {
panic("failed")
})
c.Assert(err, qt.Not(qt.IsNil))
_, err = p1.GetOrCreateWitTimeout(fmt.Sprintf("panic2-%d", i), 10*time.Second, func(string) (testItem, error) {
panic("failed")
})
c.Assert(err, qt.Not(qt.IsNil))
}
}
}
func TestAdjustCurrentMaxSize(t *testing.T) {
t.Parallel()
c := qt.New(t)
+20 -1
View File
@@ -42,6 +42,7 @@ import (
"github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/deps"
@@ -66,6 +67,12 @@ func Execute(args []string) error {
}
args = mapLegacyArgs(args)
cd, err := x.Execute(context.Background(), args)
if cd != nil {
if closer, ok := cd.Root.Command.(types.Closer); ok {
closer.Close()
}
}
if err != nil {
if err == errHelp {
cd.CobraCommand.Help()
@@ -149,6 +156,18 @@ func (r *rootCommand) isVerbose() bool {
return r.logger.Level() <= logg.LevelInfo
}
func (r *rootCommand) Close() error {
if r.hugoSites != nil {
r.hugoSites.DeleteFunc(func(key configKey, value *hugolib.HugoSites) bool {
if value != nil {
value.Close()
}
return false
})
}
return nil
}
func (r *rootCommand) Build(cd *simplecobra.Commandeer, bcfg hugolib.BuildCfg, cfg config.Provider) (*hugolib.HugoSites, error) {
h, err := r.Hugo(cfg)
if err != nil {
@@ -488,7 +507,7 @@ func (r *rootCommand) createLogger(running bool) (loggers.Logger, error) {
return loggers.New(optsLogger), nil
}
func (r *rootCommand) Reset() {
func (r *rootCommand) resetLogs() {
r.logger.Reset()
loggers.Log().Reset()
}
+34 -24
View File
@@ -27,7 +27,6 @@ import (
"sync/atomic"
"time"
"github.com/bep/logg"
"github.com/bep/simplecobra"
"github.com/fsnotify/fsnotify"
"github.com/gohugoio/hugo/common/herrors"
@@ -136,10 +135,6 @@ func (e *hugoBuilderErrState) wasErr() bool {
return e.waserr
}
func (c *hugoBuilder) errCount() int {
return c.r.logger.LoggCount(logg.LevelError) + loggers.Log().LoggCount(logg.LevelError)
}
// getDirList provides NewWatcher() with a list of directories to watch for changes.
func (c *hugoBuilder) getDirList() ([]string, error) {
h, err := c.hugo()
@@ -345,7 +340,6 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
for {
select {
case changes := <-c.r.changesFromBuild:
c.errState.setBuildErr(nil)
unlock, err := h.LockBuild()
if err != nil {
c.r.logger.Errorln("Failed to acquire a build lock: %s", err)
@@ -358,7 +352,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
}
if c.s != nil && c.s.doLiveReload {
doReload := c.changeDetector == nil || len(c.changeDetector.changed()) > 0
doReload = doReload || c.showErrorInBrowser && c.errCount() > 0
doReload = doReload || c.showErrorInBrowser && c.errState.buildErr() != nil
if doReload {
livereload.ForceRefresh()
}
@@ -372,7 +366,7 @@ func (c *hugoBuilder) newWatcher(pollIntervalStr string, dirList ...string) (*wa
return
}
c.handleEvents(watcher, staticSyncer, evs, configSet)
if c.showErrorInBrowser && c.errCount() > 0 {
if c.showErrorInBrowser && c.errState.buildErr() != nil {
// Need to reload browser to show the error
livereload.ForceRefresh()
}
@@ -419,11 +413,17 @@ func (c *hugoBuilder) build() error {
}
func (c *hugoBuilder) buildSites(noBuildLock bool) (err error) {
h, err := c.hugo()
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
return h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
err = h.Build(hugolib.BuildCfg{NoBuildLock: noBuildLock})
return
}
func (c *hugoBuilder) copyStatic() (map[string]uint64, error) {
@@ -619,6 +619,9 @@ func (c *hugoBuilder) fullRebuild(changeType string) {
// Set the processing on pause until the state is recovered.
c.errState.setPaused(true)
c.handleBuildErr(err, "Failed to reload config")
if c.s.doLiveReload {
livereload.ForceRefresh()
}
} else {
c.errState.setPaused(false)
}
@@ -1081,37 +1084,44 @@ func (c *hugoBuilder) printChangeDetected(typ string) {
c.r.logger.Println(htime.Now().Format(layout))
}
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) error {
func (c *hugoBuilder) rebuildSites(events []fsnotify.Event) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
if err := c.errState.buildErr(); err != nil {
ferrs := herrors.UnwrapFileErrorsWithErrorContext(err)
for _, err := range ferrs {
events = append(events, fsnotify.Event{Name: err.Position().Filename, Op: fsnotify.Write})
}
}
c.errState.setBuildErr(nil)
h, err := c.hugo()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
return h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()}, events...)
return
}
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) error {
c.errState.setBuildErr(nil)
h, err := c.hugo()
func (c *hugoBuilder) rebuildSitesForChanges(ids []identity.Identity) (err error) {
defer func() {
c.errState.setBuildErr(err)
}()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
whatChanged := &hugolib.WhatChanged{}
whatChanged.Add(ids...)
err = h.Build(hugolib.BuildCfg{NoBuildLock: true, WhatChanged: whatChanged, RecentlyVisited: c.visitedURLs, ErrRecovery: c.errState.wasErr()})
c.errState.setBuildErr(err)
return err
return
}
func (c *hugoBuilder) reloadConfig() error {
c.r.Reset()
c.r.resetLogs()
c.r.configVersionID.Add(1)
if err := c.withConfE(func(conf *commonConfig) error {
+11 -13
View File
@@ -648,9 +648,8 @@ func (c *serverCommand) setServerInfoInConfig() error {
}
func (c *serverCommand) getErrorWithContext() any {
errCount := c.errCount()
if errCount == 0 {
buildErr := c.errState.buildErr()
if buildErr == nil {
return nil
}
@@ -659,7 +658,7 @@ func (c *serverCommand) getErrorWithContext() any {
m["Error"] = cleanErrorLog(c.r.logger.Errors())
m["Version"] = hugo.BuildVersionString()
ferrors := herrors.UnwrapFileErrorsWithErrorContext(c.errState.buildErr())
ferrors := herrors.UnwrapFileErrorsWithErrorContext(buildErr)
m["Files"] = ferrors
return m
@@ -830,22 +829,25 @@ func (c *serverCommand) fixURL(baseURLFromConfig, baseURLFromFlag string, port i
return u.String(), nil
}
func (c *serverCommand) partialReRender(urls ...string) error {
func (c *serverCommand) partialReRender(urls ...string) (err error) {
defer func() {
c.errState.setWasErr(false)
}()
c.errState.setBuildErr(nil)
visited := types.NewEvictingStringQueue(len(urls))
for _, url := range urls {
visited.Add(url)
}
h, err := c.hugo()
var h *hugolib.HugoSites
h, err = c.hugo()
if err != nil {
return err
return
}
// Note: We do not set NoBuildLock as the file lock is not acquired at this stage.
return h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
err = h.Build(hugolib.BuildCfg{NoBuildLock: false, RecentlyVisited: visited, PartialReRender: true, ErrRecovery: c.errState.wasErr()})
return
}
func (c *serverCommand) serve() error {
@@ -1012,10 +1014,6 @@ func (c *serverCommand) serve() error {
c.r.Println("Error:", err)
}
if h := c.hugoTry(); h != nil {
h.Close()
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
wg2, ctx := errgroup.WithContext(ctx)
+1 -1
View File
@@ -137,7 +137,7 @@ var nilPointerErrRe = regexp.MustCompile(`at <(.*)>: error calling (.*?): runtim
const deferredPrefix = "__hdeferred/"
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*" `)
var deferredStringToRemove = regexp.MustCompile(`executing "__hdeferred/.*?" `)
// ImproveRenderErr improves the error message for rendering errors.
func ImproveRenderErr(inErr error) (outErr error) {
+7
View File
@@ -98,6 +98,8 @@ type Deps struct {
// TODO(bep) rethink this re. a plugin setup, but this will have to do for now.
WasmDispatchers *warpc.Dispatchers
isClosed bool
*globalErrHandler
}
@@ -345,6 +347,11 @@ func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
}
func (d *Deps) Close() error {
if d.isClosed {
return nil
}
d.isClosed = true
if d.MemCache != nil {
d.MemCache.Stop()
}
+1 -1
View File
@@ -16,7 +16,7 @@ require (
github.com/bep/gowebp v0.3.0
github.com/bep/helpers v0.5.0
github.com/bep/imagemeta v0.8.1
github.com/bep/lazycache v0.4.0
github.com/bep/lazycache v0.6.0
github.com/bep/logg v0.4.0
github.com/bep/mclib v1.20400.20402
github.com/bep/overlayfs v0.9.2
+2
View File
@@ -143,6 +143,8 @@ github.com/bep/imagemeta v0.8.1 h1:tjZLPRftjxU7PTI87o5e5WKOFQ4S9S0engiP1OTpJTI=
github.com/bep/imagemeta v0.8.1/go.mod h1:5piPAq5Qomh07m/dPPCLN3mDJyFusvUG7VwdRD/vX0s=
github.com/bep/lazycache v0.4.0 h1:X8yVyWNVupPd4e1jV7efi3zb7ZV/qcjKQgIQ5aPbkYI=
github.com/bep/lazycache v0.4.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
github.com/bep/lazycache v0.6.0 h1:0vCgFo7TBtMQpSx64jnH1sagmw0ZougIFRpsqPHTa5U=
github.com/bep/lazycache v0.6.0/go.mod h1:NmRm7Dexh3pmR1EignYR8PjO2cWybFQ68+QgY3VMCSc=
github.com/bep/logg v0.4.0 h1:luAo5mO4ZkhA5M1iDVDqDqnBBnlHjmtZF6VAyTp+nCQ=
github.com/bep/logg v0.4.0/go.mod h1:Ccp9yP3wbR1mm++Kpxet91hAZBEQgmWgFgnXX3GkIV0=
github.com/bep/mclib v1.20400.20402 h1:olpCE2WSPpOAbFE1R4hnftSEmQ34+xzy2HRzd0m69rA=
-3
View File
@@ -179,9 +179,6 @@ type hugoSitesInit struct {
// Loads the data from all of the /data folders.
data *lazy.Init
// Performs late initialization (before render) of the templates.
layouts *lazy.Init
// Loads the Git info and CODEOWNERS for all the pages if enabled.
gitInfo *lazy.Init
}
-10
View File
@@ -250,10 +250,6 @@ func (h *HugoSites) process(ctx context.Context, l logg.LevelLogger, config *Bui
l = l.WithField("step", "process")
defer loggers.TimeTrackf(l, time.Now(), nil, "")
if _, err := h.init.layouts.Do(ctx); err != nil {
return err
}
if len(events) > 0 {
// This is a rebuild triggered from file events.
return h.processPartialFileEvents(ctx, l, config, init, events)
@@ -1067,8 +1063,6 @@ func (h *HugoSites) processPartialFileEvents(ctx context.Context, l logg.LevelLo
}
if tmplChanged || i18nChanged {
// TODO(bep) we should split this, but currently the loading of i18n and layout files are tied together. See #12048.
h.init.layouts.Reset()
if err := loggers.TimeTrackfn(func() (logg.LevelLogger, error) {
// TODO(bep) this could probably be optimized to somehow
// only load the changed templates and its dependencies, but that is non-trivial.
@@ -1141,10 +1135,6 @@ func (s *Site) handleContentAdapterChanges(bi pagesfromdata.BuildInfo, buildConf
}
func (h *HugoSites) processContentAdaptersOnRebuild(ctx context.Context, buildConfig *BuildCfg) error {
// Make sure the layouts are initialized.
if _, err := h.init.layouts.Do(context.Background()); err != nil {
return err
}
g := rungroup.Run[*pagesfromdata.PagesFromTemplate](ctx, rungroup.Config[*pagesfromdata.PagesFromTemplate]{
NumWorkers: h.numWorkers,
Handle: func(ctx context.Context, p *pagesfromdata.PagesFromTemplate) error {
+6 -5
View File
@@ -246,11 +246,6 @@ func (s *IntegrationTestBuilder) AssertBuildCountGitInfo(count int) {
s.Assert(s.H.init.gitInfo.InitCount(), qt.Equals, count)
}
func (s *IntegrationTestBuilder) AssertBuildCountLayouts(count int) {
s.Helper()
s.Assert(s.H.init.layouts.InitCount(), qt.Equals, count)
}
func (s *IntegrationTestBuilder) AssertFileCount(dirname string, expected int) {
s.Helper()
fs := s.fs.WorkingDirReadOnly
@@ -421,6 +416,12 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
s.Assert(err, qt.IsNil)
}
s.Cleanup(func() {
if h := s.H; h != nil {
s.Assert(h.Close(), qt.IsNil)
}
})
return s
}
+9 -4
View File
@@ -34,6 +34,15 @@ import (
var pageIDCounter atomic.Uint64
func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
p, pth, err := h.doNewPage(m)
if err != nil {
// Make sure that any partially created page part is marked as stale.
m.MarkStale()
}
return p, pth, err
}
func (h *HugoSites) doNewPage(m *pageMeta) (*pageState, *paths.Path, error) {
m.Staler = &resources.AtomicStaler{}
if m.pageMetaParams == nil {
m.pageMetaParams = &pageMetaParams{
@@ -231,10 +240,6 @@ func (h *HugoSites) newPage(m *pageMeta) (*pageState, *paths.Path, error) {
}
return ps, nil
}()
// Make sure to evict any cached and now stale data.
if err != nil {
m.MarkStale()
}
if ps == nil {
return nil, nil, err
-10
View File
@@ -344,7 +344,6 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
skipRebuildForFilenames: make(map[string]bool),
init: &hugoSitesInit{
data: lazy.New(),
layouts: lazy.New(),
gitInfo: lazy.New(),
},
}
@@ -400,15 +399,6 @@ func newHugoSites(cfg deps.DepsCfg, d *deps.Deps, pageTrees *pageTrees, sites []
return nil, nil
})
h.init.layouts.Add(func(context.Context) (any, error) {
for _, s := range h.Sites {
if err := s.Tmpl().(tpl.TemplateManager).MarkReady(); err != nil {
return nil, err
}
}
return nil, nil
})
h.init.gitInfo.Add(func(context.Context) (any, error) {
err := h.loadGitInfo()
if err != nil {
+5 -2
View File
@@ -1,7 +1,10 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.136.1
HUGORELEASER_COMMITISH=64d1865c1e21c66feb96ffedda44a9eba2365af9
HUGORELEASER_TAG=v0.136.5
HUGORELEASER_COMMITISH=46cccb021bc6425455f4eec093f5cc4a32f1d12c
@@ -1,20 +0,0 @@
#!/bin/sh
set -ex
export DART_SASS_VERSION=1.79.3
# If $BUILDARCH=arm64, then we need to install the arm64 version of Dart Sass,
# otherwise we install the x64 version.
ARCH="x64"
if [ "$BUILDARCH" = "arm64" ]; then
ARCH="arm64"
fi
cd /tmp
curl -LJO https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz
ls -ltr
tar -xf dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz
rm dart-sass-${DART_SASS_VERSION}-linux-${ARCH}.tar.gz && \
# The dart-sass folder is added to the PATH by the caller.
mv dart-sass /var/hugo/bin
@@ -0,0 +1,42 @@
# Test the hugo server command when adding an error to a config file
# and then fixing it.
hugo server &
waitServer
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
replace $WORK/hugo.toml 'title =' 'titlefoo'
httpget ${HUGOTEST_BASEURL_0}p1/ 'failed'
replace $WORK/hugo.toml 'titlefoo' 'title ='
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
stopServer
-- hugo.toml --
title = "Hugo Server Test"
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term", "sitemap"]
-- layouts/index.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- layouts/_default/single.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- content/_index.md --
---
title: Hugo Home
---
-- content/p1/index.md --
---
title: P1
---
-- content/p2/index.md --
---
title: P2
---
-- static/staticfiles/static.txt --
static
@@ -0,0 +1,42 @@
# Test the hugo server command when adding a front matter error to a content file
# and then fixing it.
hugo server &
waitServer
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
replace $WORK/content/p1/index.md 'title:' 'titlecolon'
httpget ${HUGOTEST_BASEURL_0}p1/ 'failed'
replace $WORK/content/p1/index.md 'titlecolon' 'title:'
httpget ${HUGOTEST_BASEURL_0}p1/ 'Title: P1'
stopServer
-- hugo.toml --
title = "Hugo Server Test"
baseURL = "https://example.org/"
disableKinds = ["taxonomy", "term", "sitemap"]
-- layouts/index.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- layouts/_default/single.html --
Title: {{ .Title }}|BaseURL: {{ site.BaseURL }}|
-- content/_index.md --
---
title: Hugo Home
---
-- content/p1/index.md --
---
title: P1
---
-- content/p2/index.md --
---
title: P2
---
-- static/staticfiles/static.txt --
static
+11
View File
@@ -450,6 +450,17 @@ func (ns *Namespace) Trim(s, cutset any) (string, error) {
return strings.Trim(ss, sc), nil
}
// TrimSpace returns the given string, removing leading and trailing whitespace
// as defined by Unicode.
func (ns *Namespace) TrimSpace(s any) (string, error) {
ss, err := cast.ToStringE(s)
if err != nil {
return "", err
}
return strings.TrimSpace(ss), nil
}
// TrimLeft returns a slice of the string s with all leading characters
// contained in cutset removed.
func (ns *Namespace) TrimLeft(cutset, s any) (string, error) {
+27
View File
@@ -854,3 +854,30 @@ func TestDiff(t *testing.T) {
}
}
func TestTrimSpace(t *testing.T) {
t.Parallel()
c := qt.New(t)
for _, test := range []struct {
s any
expect any
}{
{"\n\r test \n\r", "test"},
{template.HTML("\n\r test \n\r"), "test"},
{[]byte("\n\r test \n\r"), "test"},
// errors
{tstNoStringer{}, false},
} {
result, err := ns.TrimSpace(test.s)
if b, ok := test.expect.(bool); ok && !b {
c.Assert(err, qt.Not(qt.IsNil))
continue
}
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, test.expect)
}
}
-1
View File
@@ -40,7 +40,6 @@ type TemplateManager interface {
TemplateHandler
TemplateFuncGetter
AddTemplate(name, tpl string) error
MarkReady() error
}
// TemplateVariants describes the possible variants of a template.
+4 -17
View File
@@ -168,6 +168,10 @@ func newTemplateHandlers(d *deps.Deps) (*tpl.TemplateHandlers, error) {
return nil, err
}
if err := h.main.createPrototypes(); err != nil {
return nil, err
}
e := &templateExec{
d: d,
executor: exec,
@@ -312,28 +316,11 @@ func (t *templateExec) GetFunc(name string) (reflect.Value, bool) {
return v, found
}
func (t *templateExec) MarkReady() error {
var err error
t.readyInit.Do(func() {
// We only need the clones if base templates are in use.
if len(t.needsBaseof) > 0 {
err = t.main.createPrototypes()
if err != nil {
return
}
}
})
return err
}
type templateHandler struct {
main *templateNamespace
needsBaseof map[string]templateInfo
baseof map[string]templateInfo
readyInit sync.Once
// This is the filesystem to load the templates from. All the templates are
// stored in the root of this filesystem.
layoutsFs afero.Fs
+19 -1
View File
@@ -248,7 +248,7 @@ func TestToMathMacros(t *testing.T) {
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/index.html --
{{ $macros := dict
{{ $macros := dict
"\\addBar" "\\bar{#1}"
"\\bold" "\\mathbf{#1}"
}}
@@ -261,3 +261,21 @@ disableKinds = ['page','rss','section','sitemap','taxonomy','term']
<mi>y</mi>
`)
}
// Issue #12977
func TestUnmarshalWithIndentedYAML(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
-- layouts/index.html --
{{ $yaml := "\n a:\n b: 1\n c:\n d: 2\n" }}
{{ $yaml | transform.Unmarshal | encoding.Jsonify }}
`
b := hugolib.Test(t, files)
b.AssertFileExists("public/index.html", true)
b.AssertFileContent("public/index.html", `{"a":{"b":1},"c":{"d":2}}`)
}
+2 -2
View File
@@ -113,8 +113,8 @@ func (ns *Namespace) Unmarshal(args ...any) (any, error) {
return nil, fmt.Errorf("type %T not supported", data)
}
if dataStr == "" {
return nil, errors.New("no data to transform")
if strings.TrimSpace(dataStr) == "" {
return nil, nil
}
key := hashing.MD5FromStringHexEncoded(dataStr)
+2
View File
@@ -139,6 +139,8 @@ func TestUnmarshal(t *testing.T) {
a;b;c`, mime: media.Builtin.CSVType}, map[string]any{"DElimiter": ";", "Comment": "%"}, func(r [][]string) {
b.Assert([][]string{{"a", "b", "c"}}, qt.DeepEquals, r)
}},
{``, nil, nil},
{` `, nil, nil},
// errors
{"thisisnotavaliddataformat", nil, false},
{testContentResource{key: "r1", content: `invalid&toml"`, mime: media.Builtin.TOMLType}, nil, false},