mirror of
https://github.com/gohugoio/hugo.git
synced 2024-05-11 20:11:55 +00:00
Compare commits
12 commits
39a4a3cf67
...
4164f8fef9
Author | SHA1 | Date | |
---|---|---|---|
4164f8fef9 | |||
b83050cb40 | |||
df5608f8a0 | |||
f56ce01ae1 | |||
3bbeb5688c | |||
184a67ac47 | |||
6c798eba60 | |||
ec1c97e7e9 | |||
f10009e7f1 | |||
a950950f1b | |||
36ce3a4a9d | |||
17e60b77e1 |
|
@ -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
|
||||
|
|
2
cache/filecache/filecache.go
vendored
2
cache/filecache/filecache.go
vendored
|
@ -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)
|
||||
|
||||
|
|
10
cache/filecache/filecache_pruner.go
vendored
10
cache/filecache/filecache_pruner.go
vendored
|
@ -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
97
cache/filecache/integration_test.go
vendored
Normal 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)
|
||||
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -505,7 +505,7 @@ func removeErrorPrefixFromLog(content string) string {
|
|||
}
|
||||
|
||||
var logReplacer = strings.NewReplacer(
|
||||
"can't", "can’t", // Chroma lexer does'nt do well with "can't"
|
||||
"can't", "can’t", // 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 (
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -18,6 +18,6 @@ package hugo
|
|||
var CurrentVersion = Version{
|
||||
Major: 0,
|
||||
Minor: 111,
|
||||
PatchLevel: 1,
|
||||
PatchLevel: 2,
|
||||
Suffix: "",
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
3
deps/deps.go
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
69
hugolib/page__fragments_test.go
Normal file
69
hugolib/page__fragments_test.go
Normal 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]")
|
||||
|
||||
}
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.`)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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|
|
||||
`)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
@ -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++ {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"`
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>")
|
||||
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue