Compare commits

...

12 commits

Author SHA1 Message Date
hugoreleaser 4164f8fef9 releaser: Bump versions for release of 0.111.2
[ci skip]
2023-03-05 12:32:20 +00:00
Bjørn Erik Pedersen b83050cb40 Fix .Fragments when called cross sites on uninitialized output format
Fixes #10794
2023-03-05 12:51:57 +01:00
Bjørn Erik Pedersen df5608f8a0 Allow page.TableOfContents on self in shortcode
Fixes #10791
2023-03-05 12:51:57 +01:00
Bjørn Erik Pedersen f56ce01ae1
tpl/partial: Consolidate GoDoc 2023-03-04 22:04:01 +01:00
Bjørn Erik Pedersen 3bbeb5688c Fix "context canceled" with partial
Make sure the context used for timeouts isn't created based on the incoming
context, as we have cases where this can cancel the context prematurely.

Fixes #10789
2023-03-04 21:29:05 +01:00
Oleksandr Redko 184a67ac47 cache: Fix --gc failure on Windows
Fixes "Error: failed to prune cache" on Windows and removes
work around from ec1c97e7e9.

Follows #10781.
2023-03-04 18:47:43 +01:00
Bjørn Erik Pedersen 6c798eba60 Page context handling in i18n
This is a workaround. We need to improve on this, but not today.

Fixes #10782
2023-03-04 17:19:14 +01:00
Bjørn Erik Pedersen ec1c97e7e9 Work around --gc failure on Windows <= 10
This applies two related fixes/improvements:

* The --gc now keeps empty `_resources/_gen/images` etc folders, even if empty. This should have been the behaviour
from the start.
* Also, if removal of an empty dir on Windows fails with the "used by another process" error, just ignore it for now.

Fixes #10781
2023-03-04 13:40:55 +01:00
Bjørn Erik Pedersen f10009e7f1
Update to Go 1.20.1
Fixes #10785
2023-03-04 10:36:59 +01:00
Joe Mooring a950950f1b snap: Fix dart-sass-embedded installation
Closes #10783
2023-03-04 10:15:03 +01:00
Oleksandr Redko 36ce3a4a9d Correct typos in Go comments 2023-03-02 16:32:32 +01:00
hugoreleaser 17e60b77e1 releaser: Prepare repository for 0.112.0-DEV
[ci skip]
2023-03-02 10:19:25 +00:00
51 changed files with 451 additions and 100 deletions

View file

@ -4,7 +4,7 @@ parameters:
defaults: &defaults
resource_class: large
docker:
- image: bepsays/ci-hugoreleaser:1.22000.20001
- image: bepsays/ci-hugoreleaser:1.22000.20100
environment: &buildenv
GOMODCACHE: /root/project/gomodcache
version: 2
@ -60,7 +60,7 @@ jobs:
environment:
<<: [*buildenv]
docker:
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20001
- image: bepsays/ci-hugoreleaser-linux-arm64:1.22000.20100
steps:
- *restore-cache
- &attach-workspace

View file

@ -248,7 +248,7 @@ func (c *Cache) GetBytes(id string) (ItemInfo, []byte, error) {
return info, nil, nil
}
// Get gets the file with the given id from the cahce, nil if none found.
// Get gets the file with the given id from the cache, nil if none found.
func (c *Cache) Get(id string) (ItemInfo, io.ReadCloser, error) {
id = cleanID(id)

View file

@ -66,15 +66,21 @@ func (c *Cache) Prune(force bool) (int, error) {
if info.IsDir() {
f, err := c.Fs.Open(name)
if err != nil {
// This cache dir may not exist.
return nil
}
defer f.Close()
_, err = f.Readdirnames(1)
f.Close()
if err == io.EOF {
// Empty dir.
err = c.Fs.Remove(name)
if name == "." {
// e.g. /_gen/images -- keep it even if empty.
err = nil
} else {
err = c.Fs.Remove(name)
}
}
if err != nil && !herrors.IsNotExist(err) {

97
cache/filecache/integration_test.go vendored Normal file
View file

@ -0,0 +1,97 @@
// Copyright 2023 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 filecache_test
import (
"path/filepath"
"testing"
"time"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugolib"
)
// See issue #10781. That issue wouldn't have been triggered if we kept
// the empty root directories (e.g. _resources/gen/images).
// It's still an upstream Go issue that we also need to handle, but
// this is a test for the first part.
func TestPruneShouldPreserveEmptyCacheRoots(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
-- content/_index.md --
---
title: "Home"
---
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true},
).Build()
_, err := b.H.BaseFs.ResourcesCache.Stat(filepath.Join("_gen", "images"))
b.Assert(err, qt.IsNil)
}
func TestPruneImages(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
[caches]
[caches.images]
maxAge = "200ms"
dir = ":resourceDir/_gen"
-- content/_index.md --
---
title: "Home"
---
-- assets/a/pixel.png --
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
-- layouts/index.html --
{{ $img := resources.GetMatch "**.png" }}
{{ $img = $img.Resize "3x3" }}
{{ $img.RelPermalink }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{T: t, TxtarString: files, RunGC: true, NeedsOsFS: true},
).Build()
b.Assert(b.GCCount, qt.Equals, 0)
imagesCacheDir := filepath.Join("_gen", "images")
_, err := b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
b.Assert(err, qt.IsNil)
// TODO(bep) we need a way to test full rebuilds.
// For now, just sleep a little so the cache elements expires.
time.Sleep(300 * time.Millisecond)
b.RenameFile("assets/a/pixel.png", "assets/b/pixel2.png").Build()
b.Assert(b.GCCount, qt.Equals, 1)
// Build it again to GC the empty a dir.
b.Build()
_, err = b.H.BaseFs.ResourcesCache.Stat(filepath.Join(imagesCacheDir, "a"))
b.Assert(err, qt.Not(qt.IsNil))
_, err = b.H.BaseFs.ResourcesCache.Stat(imagesCacheDir)
b.Assert(err, qt.IsNil)
}

View file

@ -229,7 +229,7 @@ func TestFlags(t *testing.T) {
if cmd.getCommand() == nil {
continue
}
// We are only intereseted in the flag handling here.
// We are only interested in the flag handling here.
cmd.getCommand().RunE = noOpRunE
}
rootCmd := root.getCommand()

View file

@ -505,7 +505,7 @@ func removeErrorPrefixFromLog(content string) string {
}
var logReplacer = strings.NewReplacer(
"can't", "cant", // Chroma lexer does'nt do well with "can't"
"can't", "cant", // Chroma lexer doesn't do well with "can't"
"*hugolib.pageState", "page.Page", // Page is the public interface.
"Rebuild failed:", "",
)
@ -547,7 +547,7 @@ func (c *commandeer) serve(s *serverCmd) error {
roots = []string{""}
}
// Cache it here. The HugoSites object may be unavaialble later on due to intermitent configuration errors.
// Cache it here. The HugoSites object may be unavailable later on due to intermittent configuration errors.
// To allow the en user to change the error template while the server is running, we use
// the freshest template we can provide.
var (

View file

@ -211,7 +211,7 @@ func TestServerBugs(t *testing.T) {
c.Assert(r.err, qt.IsNil)
c.Assert(r.homesContent[0], qt.Contains, "PostProcess: /foo.min.css")
}},
// Isue 9901
// Issue 9901
{"Multihost", `
defaultContentLanguage = 'en'
[languages]

View file

@ -18,6 +18,6 @@ package hugo
var CurrentVersion = Version{
Major: 0,
Minor: 111,
PatchLevel: 1,
PatchLevel: 2,
Suffix: "",
}

View file

@ -27,7 +27,7 @@ type Workers struct {
// Runner wraps the lifecycle methods of a new task set.
//
// Run wil block until a worker is available or the context is cancelled,
// Run will block until a worker is available or the context is cancelled,
// and then run the given func in a new goroutine.
// Wait will wait for all the running goroutines to finish.
type Runner interface {

View file

@ -44,7 +44,7 @@ func (w Whitelist) MarshalJSON() ([]byte, error) {
// NewWhitelist creates a new Whitelist from zero or more patterns.
// An empty patterns list or a pattern with the value 'none' will create
// a whitelist that will Accept noone.
// a whitelist that will Accept none.
func NewWhitelist(patterns ...string) Whitelist {
if len(patterns) == 0 {
return Whitelist{acceptNone: true}

3
deps/deps.go vendored
View file

@ -1,6 +1,7 @@
package deps
import (
"context"
"fmt"
"path/filepath"
"strings"
@ -71,7 +72,7 @@ type Deps struct {
FileCaches filecache.Caches
// The translation func to use
Translate func(translationID string, templateData any) string `json:"-"`
Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"`
// The language in use. TODO(bep) consolidate with site
Language *langs.Language

View file

@ -42,7 +42,7 @@ func NewPathSpec(fs *hugofs.Fs, cfg config.Provider, logger loggers.Logger) (*Pa
return NewPathSpecWithBaseBaseFsProvided(fs, cfg, logger, nil)
}
// NewPathSpecWithBaseBaseFsProvided creats a new PathSpec from the given filesystems and language.
// NewPathSpecWithBaseBaseFsProvided creates a new PathSpec from the given filesystems and language.
// If an existing BaseFs is provided, parts of that is reused.
func NewPathSpecWithBaseBaseFsProvided(fs *hugofs.Fs, cfg config.Provider, logger loggers.Logger, baseBaseFs *filesystems.BaseFs) (*PathSpec, error) {
p, err := paths.New(fs, cfg)

View file

@ -82,7 +82,7 @@ func (gc *globCache) GetGlob(pattern string) (glob.Glob, error) {
type globDecorator struct {
// On Windows we may get filenames with Windows slashes to match,
// which wee need to normalize.
// which we need to normalize.
isWindows bool
g glob.Glob

View file

@ -43,7 +43,7 @@ import (
//
// For bundled pages (/mybundle/index.md), we use the folder name.
//
// An exmple of a full page key would be "/blog/__hb_page1__hl_"
// An example of a full page key would be "/blog/__hb_page1__hl_"
//
// Bundled resources are stored in the `resources` having their path prefixed
// with the bundle they belong to, e.g.
@ -317,7 +317,7 @@ type contentMap struct {
// There are currently two cases where this is used:
// 1. Short name lookups in ref/relRef, e.g. using only "mypage.md" without a path.
// 2. Links resolved from a remounted content directory. These are restricted to the same module.
// Both of the above cases can result in ambigous lookup errors.
// Both of the above cases can result in ambiguous lookup errors.
pageReverseIndex *contentTreeReverseIndex
// Section nodes.

View file

@ -297,7 +297,7 @@ func (s SourceFilesystems) StaticFs(lang string) afero.Fs {
// StatResource looks for a resource in these filesystems in order: static, assets and finally content.
// If found in any of them, it returns FileInfo and the relevant filesystem.
// Any non herrors.IsNotExist error will be returned.
// An herrors.IsNotExist error wil be returned only if all filesystems return such an error.
// An herrors.IsNotExist error will be returned only if all filesystems return such an error.
// Note that if we only wanted to find the file, we could create a composite Afero fs,
// but we also need to know which filesystem root it lives in.
func (s SourceFilesystems) StatResource(lang, filename string) (fi os.FileInfo, fs afero.Fs, err error) {

View file

@ -723,7 +723,7 @@ type BuildCfg struct {
// shouldRender is used in the Fast Render Mode to determine if we need to re-render
// a Page: If it is recently visited (the home pages will always be in this set) or changed.
// Note that a page does not have to have a content page / file.
// For regular builds, this will allways return true.
// For regular builds, this will always return true.
// TODO(bep) rename/work this.
func (cfg *BuildCfg) shouldRender(p *pageState) bool {
if p == nil {

View file

@ -84,6 +84,7 @@ type IntegrationTestBuilder struct {
renamedFiles []string
buildCount int
GCCount int
counters *testCounters
logBuff lockingBuffer
@ -193,6 +194,9 @@ func (s *IntegrationTestBuilder) Build() *IntegrationTestBuilder {
if s.Cfg.Verbose || err != nil {
fmt.Println(s.logBuff.String())
}
if s.Cfg.RunGC {
s.GCCount, err = s.H.GC()
}
s.Assert(err, qt.IsNil)
return s
}
@ -263,6 +267,7 @@ func (s *IntegrationTestBuilder) RenameFile(old, new string) *IntegrationTestBui
absNewFilename := s.absFilename(new)
s.renamedFiles = append(s.renamedFiles, absOldFilename)
s.createdFiles = append(s.createdFiles, absNewFilename)
s.Assert(s.fs.Source.MkdirAll(filepath.Dir(absNewFilename), 0777), qt.IsNil)
s.Assert(s.fs.Source.Rename(absOldFilename, absNewFilename), qt.IsNil)
return s
}
@ -488,6 +493,9 @@ type IntegrationTestConfig struct {
// Whether it needs the real file system (e.g. for js.Build tests).
NeedsOsFS bool
// Whether to run GC after each build.
RunGC bool
// Do not remove the temp dir after the test.
PrintAndKeepTempDir bool

View file

@ -62,9 +62,8 @@ var (
var (
pageTypesProvider = resource.NewResourceTypesProvider(media.OctetType, pageResourceType)
nopPageOutput = &pageOutput{
pagePerOutputProviders: nopPagePerOutput,
ContentProvider: page.NopPage,
TableOfContentsProvider: page.NopPage,
pagePerOutputProviders: nopPagePerOutput,
ContentProvider: page.NopPage,
}
)
@ -151,14 +150,6 @@ func (p *pageState) GetIdentity() identity.Identity {
return identity.NewPathIdentity(files.ComponentFolderContent, filepath.FromSlash(p.Pathc()))
}
func (p *pageState) Fragments(ctx context.Context) *tableofcontents.Fragments {
p.s.initInit(ctx, p.cp.initToC, p)
if p.pageOutput.cp.tableOfContents == nil {
return tableofcontents.Empty
}
return p.pageOutput.cp.tableOfContents
}
func (p *pageState) HeadingsFiltered(context.Context) tableofcontents.Headings {
return nil
}
@ -951,8 +942,8 @@ func (p *pageState) shiftToOutputFormat(isRenderingSite bool, idx int) error {
})
p.pageOutput.contentRenderer = lcp
p.pageOutput.ContentProvider = lcp
p.pageOutput.TableOfContentsProvider = lcp
p.pageOutput.PageRenderProvider = lcp
p.pageOutput.TableOfContentsProvider = lcp
}
}

View file

@ -0,0 +1,69 @@
// Copyright 2023 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"
// #10794
func TestFragmentsAndToCCrossSiteAccess(t *testing.T) {
files := `
-- hugo.toml --
baseURL = "https://example.com"
disableKinds = ["taxonomy", "term", "home"]
defaultContentLanguage = "en"
defaultContentLanguageInSubdir = true
[languages]
[languages.en]
weight = 1
[languages.fr]
weight = 2
-- content/p1.en.md --
---
title: "P1"
outputs: ["HTML", "JSON"]
---
## Heading 1 EN
-- content/p1.fr.md --
---
title: "P1"
outputs: ["HTML", "JSON"]
---
## Heading 1 FR
-- layouts/_default/single.html --
HTML
-- layouts/_default/single.json --
{{ $secondSite := index .Sites 1 }}
{{ $p1 := $secondSite.GetPage "p1" }}
ToC: {{ $p1.TableOfContents }}
Fragments : {{ $p1.Fragments.Identifiers }}
`
b := NewIntegrationTestBuilder(
IntegrationTestConfig{
TxtarString: files,
T: t,
},
).Build()
b.AssertFileContent("public/en/p1/index.html", "HTML")
b.AssertFileContent("public/en/p1/index.json", "ToC: <nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#heading-1-fr\">Heading 1 FR</a></li>\n </ul>\n</nav>\nFragments : [heading-1-fr]")
}

View file

@ -57,8 +57,8 @@ func newPageOutput(
f: f,
pagePerOutputProviders: providers,
ContentProvider: page.NopPage,
TableOfContentsProvider: page.NopPage,
PageRenderProvider: page.NopPage,
TableOfContentsProvider: page.NopPage,
render: render,
paginator: pag,
}
@ -84,8 +84,8 @@ type pageOutput struct {
contentRenderer page.ContentRenderer
pagePerOutputProviders
page.ContentProvider
page.TableOfContentsProvider
page.PageRenderProvider
page.TableOfContentsProvider
// May be nil.
cp *pageContentOutput
@ -97,8 +97,8 @@ func (p *pageOutput) initContentProvider(cp *pageContentOutput) {
}
p.contentRenderer = cp
p.ContentProvider = cp
p.TableOfContentsProvider = cp
p.PageRenderProvider = cp
p.TableOfContentsProvider = cp
p.cp = cp
}

View file

@ -342,6 +342,19 @@ func (p *pageContentOutput) Reset() {
p.renderHooks = &renderHooks{}
}
func (p *pageContentOutput) Fragments(ctx context.Context) *tableofcontents.Fragments {
p.p.s.initInit(ctx, p.initToC, p.p)
if p.tableOfContents == nil {
return tableofcontents.Empty
}
return p.tableOfContents
}
func (p *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
p.p.s.initInit(ctx, p.initToC, p.p)
return p.tableOfContentsHTML
}
func (p *pageContentOutput) Content(ctx context.Context) (any, error) {
p.p.s.initInit(ctx, p.initMain, p.p)
return p.content, nil
@ -380,11 +393,6 @@ func (p *pageContentOutput) Summary(ctx context.Context) template.HTML {
return p.summary
}
func (p *pageContentOutput) TableOfContents(ctx context.Context) template.HTML {
p.p.s.initInit(ctx, p.initMain, p.p)
return p.tableOfContentsHTML
}
func (p *pageContentOutput) Truncated(ctx context.Context) bool {
if p.p.truncated {
return true

View file

@ -209,7 +209,7 @@ func (c *PageCollections) getSectionOrPage(ref string) (*contentNode, string) {
return m.getPage(s, name), name
}
// For Ref/Reflink and .Site.GetPage do simple name lookups for the potentially ambigous myarticle.md and /myarticle.md,
// For Ref/Reflink and .Site.GetPage do simple name lookups for the potentially ambiguous myarticle.md and /myarticle.md,
// but not when we get ./myarticle*, section/myarticle.
func shouldDoSimpleLookup(ref string) bool {
if ref[0] == '.' {
@ -325,7 +325,7 @@ func (c *PageCollections) getContentNode(context page.Page, isReflink bool, ref
return nil, nil
}
// Ref/relref supports this potentially ambigous lookup.
// Ref/relref supports this potentially ambiguous lookup.
return getByName(path.Base(name))
}

View file

@ -63,6 +63,7 @@ var zeroShortcode = prerenderedShortcode{}
// the best we can do.
type pageForShortcode struct {
page.PageWithoutContent
page.TableOfContentsProvider
page.ContentProvider
// We need to replace it after we have rendered it, so provide a
@ -74,10 +75,11 @@ type pageForShortcode struct {
func newPageForShortcode(p *pageState) page.Page {
return &pageForShortcode{
PageWithoutContent: p,
ContentProvider: page.NopPage,
toc: template.HTML(tocShortcodePlaceholder),
p: p,
PageWithoutContent: p,
TableOfContentsProvider: p,
ContentProvider: page.NopPage,
toc: template.HTML(tocShortcodePlaceholder),
p: p,
}
}
@ -105,7 +107,7 @@ func newPageForRenderHook(p *pageState) page.Page {
return &pageForRenderHooks{
PageWithoutContent: p,
ContentProvider: page.NopPage,
TableOfContentsProvider: page.NopPage,
TableOfContentsProvider: p,
}
}

View file

@ -428,7 +428,7 @@ func newSite(cfg deps.DepsCfg) (*Site, error) {
delete(disabledKinds, "taxonomyTerm")
} else if disabledKinds[page.KindTaxonomy] && !disabledKinds[page.KindTerm] {
// This is a potentially ambigous situation. It may be correct.
// This is a potentially ambiguous situation. It may be correct.
ignorableLogger.Errorsf(constants.ErrIDAmbigousDisableKindTaxonomy, `You have the value 'taxonomy' in the disabledKinds list. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
}
@ -487,7 +487,7 @@ But this also means that your site configuration may not do what you expect. If
siteOutputs[page.KindTerm] = v2
delete(siteOutputs, "taxonomyTerm")
} else if hasTaxonomy && !hasTerm {
// This is a potentially ambigous situation. It may be correct.
// This is a potentially ambiguous situation. It may be correct.
ignorableLogger.Errorsf(constants.ErrIDAmbigousOutputKindTaxonomy, `You have configured output formats for 'taxonomy' in your site configuration. In Hugo 0.73.0 we fixed these to be what most people expect (taxonomy and term).
But this also means that your site configuration may not do what you expect. If it is correct, you can suppress this message by following the instructions below.`)
}

View file

@ -1,7 +1,8 @@
# Release env.
# These will be replaced by script before release.
HUGORELEASER_TAG=v0.111.0
HUGORELEASER_COMMITISH=3fa8bb8318114cd69315eadd35bda169e6a8ca4b
HUGORELEASER_TAG=v0.111.1
HUGORELEASER_COMMITISH=39a4a3cf676533859217805c36181c7a3dfa321c

View file

@ -14,6 +14,7 @@
package i18n
import (
"context"
"fmt"
"reflect"
"strings"
@ -24,11 +25,12 @@ import (
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/go-i18n/v2/i18n"
)
type translateFunc func(translationID string, templateData any) string
type translateFunc func(ctx context.Context, translationID string, templateData any) string
var i18nWarningLogger = helpers.NewDistinctErrorLogger()
@ -58,7 +60,7 @@ func (t Translator) Func(lang string) translateFunc {
}
t.logger.Infoln("i18n not initialized; if you need string translations, check that you have a bundle in /i18n that matches the site language or the default language.")
return func(translationID string, args any) string {
return func(ctx context.Context, translationID string, args any) string {
return ""
}
}
@ -71,7 +73,7 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
// This may be pt-BR; make it case insensitive.
currentLangKey := strings.ToLower(strings.TrimPrefix(currentLangStr, artificialLangTagPrefix))
localizer := i18n.NewLocalizer(bndl, currentLangStr)
t.translateFuncs[currentLangKey] = func(translationID string, templateData any) string {
t.translateFuncs[currentLangKey] = func(ctx context.Context, translationID string, templateData any) string {
pluralCount := getPluralCount(templateData)
if templateData != nil {
@ -81,6 +83,16 @@ func (t Translator) initFuncs(bndl *i18n.Bundle) {
// and we keep it like this to avoid breaking
// lots of sites in the wild.
templateData = intCount(cast.ToInt(templateData))
} else {
if p, ok := templateData.(page.Page); ok {
// See issue 10782.
// The i18n has its own template handling and does not know about
// the context.Context.
// A common pattern is to pass Page to i18n, and use .ReadingTime etc.
// We need to improve this, but that requires some upstream changes.
// For now, just creata a wrepper.
templateData = page.PageWithContext{Page: p, Ctx: ctx}
}
}
}

View file

@ -14,6 +14,7 @@
package i18n
import (
"context"
"fmt"
"path/filepath"
"testing"
@ -408,9 +409,10 @@ other = "{{ . }} miesiąca"
c.Assert(d.LoadResources(), qt.IsNil)
f := tp.t.Func(test.lang)
ctx := context.Background()
for _, variant := range test.variants {
c.Assert(f(test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
c.Assert(f(ctx, test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
c.Assert(int(depsCfg.Logger.LogCounters().WarnCounter.Count()), qt.Equals, 0)
}
@ -422,7 +424,7 @@ other = "{{ . }} miesiąca"
func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) string {
tp := prepareTranslationProvider(t, test, cfg)
f := tp.t.Func(test.lang)
return f(test.id, test.args)
return f(context.Background(), test.id, test.args)
}
type countField struct {
@ -542,7 +544,7 @@ func BenchmarkI18nTranslate(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
f := tp.t.Func(test.lang)
actual := f(test.id, test.args)
actual := f(context.Background(), test.id, test.args)
if actual != test.expected {
b.Fatalf("expected %v got %v", test.expected, actual)
}

View file

@ -55,3 +55,51 @@ l1: {{ i18n "l1" }}|l2: {{ i18n "l2" }}|l3: {{ i18n "l3" }}
l1: l1main|l2: l2main|l3: l3theme
`)
}
func TestPassPageToI18n(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
-- content/_index.md --
---
title: "Home"
---
Duis quis irure id nisi sunt minim aliqua occaecat. Aliqua cillum labore consectetur quis culpa tempor quis non officia cupidatat in ad cillum. Velit irure pariatur nisi adipisicing officia reprehenderit commodo esse non.
Ullamco cupidatat nostrud ut reprehenderit. Consequat nisi culpa magna amet tempor velit reprehenderit. Ad minim eiusmod tempor nostrud eu aliquip consectetur commodo ut in aliqua enim. Cupidatat voluptate laborum consequat qui nulla laborum laborum aute ea culpa nulla dolor cillum veniam. Commodo esse tempor qui labore aute aliqua sint nulla do.
Ad deserunt esse nostrud labore. Amet reprehenderit fugiat nostrud eu reprehenderit sit reprehenderit minim deserunt esse id occaecat cillum. Ad qui Lorem cillum laboris ipsum anim in culpa ad dolor consectetur minim culpa.
Lorem cupidatat officia aute in eu commodo anim nulla deserunt occaecat reprehenderit dolore. Eu cupidatat reprehenderit ipsum sit laboris proident. Duis quis nulla tempor adipisicing. Adipisicing amet ad reprehenderit non mollit. Cupidatat proident tempor laborum sit ipsum adipisicing sunt magna labore. Eu irure nostrud cillum exercitation tempor proident. Laborum magna nisi consequat do sint occaecat magna incididunt.
Sit mollit amet esse dolore in labore aliquip eu duis officia incididunt. Esse veniam labore excepteur eiusmod occaecat ullamco magna sunt. Ipsum occaecat exercitation anim fugiat in amet excepteur excepteur aliquip laborum. Aliquip aliqua consequat officia sit sint amet aliqua ipsum eu veniam. Id enim quis ea in eu consequat exercitation occaecat veniam consequat anim nulla adipisicing minim. Ut duis cillum laboris duis non commodo eu aliquip tempor nisi aute do.
Ipsum nulla esse excepteur ut aliqua esse incididunt deserunt veniam dolore est laborum nisi veniam. Magna eiusmod Lorem do tempor incididunt ut aute aliquip ipsum ea laboris culpa. Occaecat do officia velit fugiat culpa eu minim magna sint occaecat sunt. Duis magna proident incididunt est cupidatat proident esse proident ut ipsum non dolor Lorem eiusmod. Officia quis irure id eu aliquip.
Duis anim elit in officia in in aliquip est. Aliquip nisi labore qui elit elit cupidatat ut labore incididunt eiusmod ipsum. Sit irure nulla non cupidatat exercitation sit culpa nisi ex dolore. Culpa nisi duis duis eiusmod commodo nulla.
Et magna aliqua amet qui mollit. Eiusmod aute ut anim ea est fugiat non nisi in laborum ullamco. Proident mollit sunt nostrud irure esse sunt eiusmod deserunt dolor. Irure aute ad magna est consequat duis cupidatat consequat. Enim tempor aute cillum quis ea do enim proident incididunt aliquip cillum tempor minim. Nulla minim tempor proident in excepteur consectetur veniam.
Exercitation tempor nulla incididunt deserunt laboris ad incididunt aliqua exercitation. Adipisicing laboris veniam aute eiusmod qui magna fugiat velit. Aute quis officia anim commodo id fugiat nostrud est. Quis ipsum amet velit adipisicing eu anim minim eu est in culpa aute. Esse in commodo irure enim proident reprehenderit ullamco in dolore aute cillum.
Irure excepteur ex occaecat ipsum laboris fugiat exercitation. Exercitation adipisicing velit excepteur eu culpa consequat exercitation dolore. In laboris aute quis qui mollit minim culpa. Magna velit ea aliquip veniam fugiat mollit veniam.
-- i18n/en.toml --
[a]
other = 'Reading time: {{ .ReadingTime }}'
-- layouts/index.html --
i18n: {{ i18n "a" . }}|
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/index.html", `
i18n: Reading time: 3|
`)
}

View file

@ -92,7 +92,7 @@ type Language struct {
collator *Collator
location *time.Location
// Error during initialization. Will fail the buld.
// Error during initialization. Will fail the build.
initErr error
}
@ -223,7 +223,7 @@ func (l Languages) AsOrdinalSet() map[string]int {
}
// IsMultihost returns whether there are more than one language and at least one of
// the languages has baseURL specificed on the language level.
// the languages has baseURL specified on the language level.
func (l Languages) IsMultihost() bool {
if len(l) <= 1 {
return false
@ -326,7 +326,7 @@ type Collator struct {
// CompareStrings compares a and b.
// It returns -1 if a < b, 1 if a > b and 0 if a == b.
// Note that the Collator is not thread safe, so you may want
// to aquire a lock on it before calling this method.
// to acquire a lock on it before calling this method.
func (c *Collator) CompareStrings(a, b string) int {
return c.c.CompareString(a, b)
}

View file

@ -180,14 +180,15 @@ func (ini *Init) checkDone() {
}
func (ini *Init) withTimeout(ctx context.Context, timeout time.Duration, f func(ctx context.Context) (any, error)) (any, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
// Create a new context with a timeout not connected to the incoming context.
waitCtx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
c := make(chan verr, 1)
go func() {
v, err := f(ctx)
select {
case <-ctx.Done():
case <-waitCtx.Done():
return
default:
c <- verr{v: v, err: err}
@ -195,7 +196,7 @@ func (ini *Init) withTimeout(ctx context.Context, timeout time.Duration, f func(
}()
select {
case <-ctx.Done():
case <-waitCtx.Done():
return nil, errors.New("timed out initializing value. You may have a circular loop in a shortcode, or your site may have resources that take longer to build than the `timeout` limit in your Hugo config file.")
case ve := <-c:
return ve.v, ve.err

View file

@ -126,12 +126,6 @@ func TestInitAddWithTimeoutTimeout(t *testing.T) {
init := New().AddWithTimeout(100*time.Millisecond, func(ctx context.Context) (any, error) {
time.Sleep(500 * time.Millisecond)
select {
case <-ctx.Done():
return nil, nil
default:
}
t.Fatal("slept")
return nil, nil
})

View file

@ -112,7 +112,7 @@ func (ids *idFactory) Generate(value []byte, kind ast.NodeKind) []byte {
}
if _, found := ids.vals[util.BytesToReadOnlyString(buf.Bytes())]; found {
// Append a hypen and a number, starting with 1.
// Append a hyphen and a number, starting with 1.
buf.WriteRune('-')
pos := buf.Len()
for i := 1; ; i++ {

View file

@ -200,8 +200,8 @@ func RenderASTAttributes(w hugio.FlexiWriter, attributes ...ast.Attribute) {
}
// Render writes the attributes to the given as attributes to an HTML element.
// This is used for the default codeblock renderering.
// This performs HTML esacaping of string attributes.
// This is used for the default codeblock rendering.
// This performs HTML escaping of string attributes.
func RenderAttributes(w hugio.FlexiWriter, skipClass bool, attributes ...Attribute) {
for _, attr := range attributes {
a := strings.ToLower(string(attr.Name))

View file

@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// Package media containes Media Type (MIME type) related types and functions.
// Package media contains Media Type (MIME type) related types and functions.
package media
import (

View file

@ -23,7 +23,7 @@ import (
type Format string
const (
// These are the supported metdata formats in Hugo. Most of these are also
// These are the supported metadata formats in Hugo. Most of these are also
// supported as /data formats.
ORG Format = "org"
JSON Format = "json"

View file

@ -334,9 +334,6 @@ type PageWithoutContent interface {
// Used in change/dependency tracking.
identity.Provider
// Fragments returns the fragments for this page.
Fragments(context.Context) *tableofcontents.Fragments
// Headings returns the headings for this page when a filter is set.
// This is currently only triggered with the Related content feature
// and the "fragments" type of index.
@ -407,6 +404,9 @@ type SitesProvider interface {
type TableOfContentsProvider interface {
// TableOfContents returns the table of contents for the page rendered as HTML.
TableOfContents(context.Context) template.HTML
// Fragments returns the fragments for this page.
Fragments(context.Context) *tableofcontents.Fragments
}
// TranslationsProvider provides access to any translations.
@ -471,3 +471,45 @@ type DeprecatedWarningPageMethods any // This was emptied in Hugo 0.93.0.
// Move here to trigger ERROR instead of WARNING.
// TODO(bep) create wrappers and put into the Page once it has some methods.
type DeprecatedErrorPageMethods any
// PageWithContext is a Page with a context.Context.
type PageWithContext struct {
Page
Ctx context.Context
}
func (p PageWithContext) Content() (any, error) {
return p.Page.Content(p.Ctx)
}
func (p PageWithContext) Plain() string {
return p.Page.Plain(p.Ctx)
}
func (p PageWithContext) PlainWords() []string {
return p.Page.PlainWords(p.Ctx)
}
func (p PageWithContext) Summary() template.HTML {
return p.Page.Summary(p.Ctx)
}
func (p PageWithContext) Truncated() bool {
return p.Page.Truncated(p.Ctx)
}
func (p PageWithContext) FuzzyWordCount() int {
return p.Page.FuzzyWordCount(p.Ctx)
}
func (p PageWithContext) WordCount() int {
return p.Page.WordCount(p.Ctx)
}
func (p PageWithContext) ReadingTime() int {
return p.Page.ReadingTime(p.Ctx)
}
func (p PageWithContext) Len() int {
return p.Page.Len(p.Ctx)
}

View file

@ -23,7 +23,7 @@ const (
KindHome = "home"
KindSection = "section"
// Note tha before Hugo 0.73 these were confusingly named
// Note that before Hugo 0.73 these were confusingly named
// taxonomy (now: term)
// taxonomyTerm (now: taxonomy)
KindTaxonomy = "taxonomy"

View file

@ -19,6 +19,7 @@ import (
"github.com/gohugoio/hugo/lazy"
"github.com/gohugoio/hugo/markup/converter"
"github.com/gohugoio/hugo/markup/tableofcontents"
)
// OutputFormatContentProvider represents the method set that is "outputFormat aware" and that we
@ -73,6 +74,17 @@ func (lcp *LazyContentProvider) Reset() {
lcp.init.Reset()
}
func (lcp *LazyContentProvider) TableOfContents(ctx context.Context) template.HTML {
lcp.init.Do(ctx)
return lcp.cp.TableOfContents(ctx)
}
func (lcp *LazyContentProvider) Fragments(ctx context.Context) *tableofcontents.Fragments {
lcp.init.Do(ctx)
return lcp.cp.Fragments(ctx)
}
func (lcp *LazyContentProvider) Content(ctx context.Context) (any, error) {
lcp.init.Do(ctx)
return lcp.cp.Content(ctx)
@ -128,11 +140,6 @@ func (lcp *LazyContentProvider) RenderString(ctx context.Context, args ...any) (
return lcp.cp.RenderString(ctx, args...)
}
func (lcp *LazyContentProvider) TableOfContents(ctx context.Context) template.HTML {
lcp.init.Do(ctx)
return lcp.cp.TableOfContents(ctx)
}
func (lcp *LazyContentProvider) ParseAndRenderContent(ctx context.Context, content []byte, renderTOC bool) (converter.ResultRender, error) {
lcp.init.Do(ctx)
return lcp.cp.ParseAndRenderContent(ctx, content, renderTOC)

View file

@ -22,7 +22,7 @@ func (p Pages) Next(cur Page) Page {
return p[x-1]
}
// Prev returns the previous page reletive to the given
// Prev returns the previous page relative to the given
func (p Pages) Prev(cur Page) Page {
x := searchPage(cur, p)

View file

@ -62,7 +62,7 @@ type Site interface {
// Returns the BaseURL for this Site.
BaseURL() template.URL
// Retuns a taxonomy map.
// Returns a taxonomy map.
Taxonomies() TaxonomyList
// Returns the last modification date of the content.

View file

@ -164,7 +164,7 @@ func resolveComponentInAssets(fs afero.Fs, impPath string) *hugofs.FileMeta {
var m *hugofs.FileMeta
// We need to check if this is a regular file imported without an extension.
// There may be ambigous situations where both foo.js and foo/index.js exists.
// There may be ambiguous situations where both foo.js and foo/index.js exists.
// This import order is in line with both how Node and ESBuild's native
// import resolver works.

View file

@ -164,12 +164,12 @@ type resourceAdapter struct {
*resourceAdapterInner
}
func (r *resourceAdapter) Content(context.Context) (any, error) {
func (r *resourceAdapter) Content(ctx context.Context) (any, error) {
r.init(false, true)
if r.transformationsErr != nil {
return nil, r.transformationsErr
}
return r.target.Content(context.Background())
return r.target.Content(ctx)
}
func (r *resourceAdapter) Err() resource.ResourceError {

View file

@ -152,9 +152,9 @@ parts:
if [[ -n $arch ]]; then
url=$(curl -s https://api.github.com/repos/sass/dart-sass-embedded/releases/latest | awk -F\" "/browser_download_url.*-linux-${arch}.tar.gz/{print \$(NF-1)}")
curl -LO --retry-connrefused --retry 10 "$url"
tar xf sass_embedded-*-linux-$arch.tar.gz sass_embedded/dart-sass-embedded
tar xf sass_embedded-*-linux-$arch.tar.gz
install -d $SNAPCRAFT_PART_INSTALL/bin
cp -av sass_embedded/dart-sass-embedded $SNAPCRAFT_PART_INSTALL/bin/
cp -av sass_embedded/* $SNAPCRAFT_PART_INSTALL/bin/
fi
node:

View file

@ -301,7 +301,7 @@ func NewGitInfo(info gitmap.GitInfo) GitInfo {
return GitInfo(info)
}
// GitInfo provides information about a version controled source file.
// GitInfo provides information about a version controlled source file.
type GitInfo struct {
// Commit hash.
Hash string `json:"hash"`

View file

@ -34,7 +34,7 @@ type Filesystem struct {
SourceSpec
}
// NewFilesystem returns a new filesytem for a given source spec.
// NewFilesystem returns a new filesystem for a given source spec.
func (sp SourceSpec) NewFilesystem(base string) *Filesystem {
return &Filesystem{SourceSpec: sp, Base: base}
}

View file

@ -72,7 +72,7 @@ const (
HasLockContextKey = hasLockContextKeyType("hasLock")
)
// Note: The context is currently not fully implemeted in Hugo. This is a work in progress.
// Note: The context is currently not fully implemented in Hugo. This is a work in progress.
func (t *executer) ExecuteWithContext(ctx context.Context, p Preparer, wr io.Writer, data any) error {
if ctx == nil {
panic("nil context")
@ -123,7 +123,7 @@ func (t *Template) executeWithState(state *state, value reflect.Value) (err erro
// can execute in parallel.
type state struct {
tmpl *Template
ctx context.Context // Added for Hugo. The orignal data context.
ctx context.Context // Added for Hugo. The original data context.
prep Preparer // Added for Hugo.
helper ExecHelper // Added for Hugo.
wr io.Writer

View file

@ -15,6 +15,7 @@
package lang
import (
"context"
"fmt"
"math"
"strconv"
@ -45,7 +46,7 @@ type Namespace struct {
}
// Translate returns a translated string for id.
func (ns *Namespace) Translate(id any, args ...any) (string, error) {
func (ns *Namespace) Translate(ctx context.Context, id any, args ...any) (string, error) {
var templateData any
if len(args) > 0 {
@ -60,7 +61,7 @@ func (ns *Namespace) Translate(id any, args ...any) (string, error) {
return "", nil
}
return ns.deps.Translate(sid, templateData), nil
return ns.deps.Translate(ctx, sid, templateData), nil
}
// FormatNumber formats number with the given precision for the current language.

View file

@ -177,3 +177,35 @@ Shortcode in bundled page OK.
}
}
// Issue 10791.
func TestPageTableOfContentsInShortcode(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
baseURL = 'http://example.com/'
disableKinds = ["taxonomy", "term"]
-- content/p1.md --
---
title: "P1"
---
{{< toc >}}
# Heading 1
-- layouts/shortcodes/toc.html --
{{ page.TableOfContents }}
-- layouts/_default/single.html --
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/p1/index.html", "<nav id=\"TableOfContents\"></nav> \n<h1 id=\"heading-1\">Heading 1</h1>")
}

View file

@ -324,3 +324,31 @@ timeout = '200ms'
b.Assert(err.Error(), qt.Contains, "timed out")
}
// See Issue #10789
func TestReturnExecuteFromTemplateInPartial(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
baseURL = 'http://example.com/'
-- layouts/index.html --
{{ $r := partial "foo" }}
FOO:{{ $r.Content }}
-- layouts/partials/foo.html --
{{ $r := §§{{ partial "bar" }}§§ | resources.FromString "bar.html" | resources.ExecuteAsTemplate "bar.html" . }}
{{ return $r }}
-- layouts/partials/bar.html --
BAR
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
b.AssertFileContent("public/index.html", "OO:BAR")
}

View file

@ -129,7 +129,8 @@ func (ns *Namespace) Include(ctx context.Context, name string, contextList ...an
}
func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataList ...any) includeResult {
ctx, cancel := context.WithTimeout(ctx, ns.deps.Timeout)
// Create a new context with a timeout not connected to the incoming context.
timeoutCtx, cancel := context.WithTimeout(context.Background(), ns.deps.Timeout)
defer cancel()
res := make(chan includeResult, 1)
@ -141,8 +142,8 @@ func (ns *Namespace) includWithTimeout(ctx context.Context, name string, dataLis
select {
case r := <-res:
return r
case <-ctx.Done():
err := ctx.Err()
case <-timeoutCtx.Done():
err := timeoutCtx.Err()
if err == context.DeadlineExceeded {
err = fmt.Errorf("partial %q timed out after %s. This is most likely due to infinite recursion. If this is just a slow template, you can try to increase the 'timeout' config setting.", name, ns.deps.Timeout)
}

View file

@ -774,8 +774,8 @@ func (t *templateHandler) loadEmbedded() error {
name := strings.TrimPrefix(filepath.ToSlash(path), "embedded/templates/")
templateName := name
// For the render hooks and the server templates it does not make sense to preseve the
// double _indternal double book-keeping,
// For the render hooks and the server templates it does not make sense to preserve the
// double _internal double book-keeping,
// just add it if its now provided by the user.
if !strings.Contains(path, "_default/_markup") && !strings.HasPrefix(name, "_server/") {
templateName = internalPathPrefix + name