mirror of
https://github.com/gohugoio/hugo.git
synced 2026-08-25 07:48:52 +00:00
hugolib: Add hugo.Sites and .Site.IsDefault(), modify .Site.Sites
Changes: - Add hugo.Sites to return all sites for all dimensions - Modify .Site.Sites to return all sites for all dimensions - Deprecate .Site.Sites in favor of hugo.Sites - Add .Site.IsDefault() to report whether the current site is the default site across all dimensions - Consolidate tests Closes #14479 Closes #14481 Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Bjørn Erik Pedersen
parent
21be4afd49
commit
ab62320d6b
+31
-9
@@ -75,6 +75,8 @@ type HugoInfo struct {
|
||||
conf ConfigProvider
|
||||
deps []*Dependency
|
||||
|
||||
sitesProvider SitesProvider
|
||||
|
||||
store *hstore.Scratch
|
||||
|
||||
// Context gives access to some of the context scoped variables.
|
||||
@@ -141,6 +143,14 @@ func (i HugoInfo) IsMultilingual() bool {
|
||||
return i.conf.IsMultilingual()
|
||||
}
|
||||
|
||||
// Sites returns all sites for all dimensions.
|
||||
func (i HugoInfo) Sites() any {
|
||||
if i.sitesProvider == nil {
|
||||
return nil
|
||||
}
|
||||
return i.sitesProvider.Sites()
|
||||
}
|
||||
|
||||
type contextKey uint8
|
||||
|
||||
const (
|
||||
@@ -174,9 +184,20 @@ type ConfigProvider interface {
|
||||
IsMultilingual() bool
|
||||
}
|
||||
|
||||
// SitesProvider provides access to all sites.
|
||||
type SitesProvider interface {
|
||||
Sites() any
|
||||
}
|
||||
|
||||
// HugoInfoOptions defines the providers required to initialize HugoInfo.
|
||||
type HugoInfoOptions struct {
|
||||
Conf ConfigProvider
|
||||
SitesProvider SitesProvider
|
||||
}
|
||||
|
||||
// NewInfo creates a new Hugo Info object.
|
||||
func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
|
||||
if conf.Environment() == "" {
|
||||
func NewInfo(opts HugoInfoOptions, deps []*Dependency) HugoInfo {
|
||||
if opts.Conf.Environment() == "" {
|
||||
panic("environment not set")
|
||||
}
|
||||
var (
|
||||
@@ -193,13 +214,14 @@ func NewInfo(conf ConfigProvider, deps []*Dependency) HugoInfo {
|
||||
}
|
||||
|
||||
return HugoInfo{
|
||||
CommitHash: commitHash,
|
||||
BuildDate: buildDate,
|
||||
Environment: conf.Environment(),
|
||||
conf: conf,
|
||||
deps: deps,
|
||||
store: hstore.NewScratch(),
|
||||
GoVersion: goVersion,
|
||||
CommitHash: commitHash,
|
||||
BuildDate: buildDate,
|
||||
Environment: opts.Conf.Environment(),
|
||||
conf: opts.Conf,
|
||||
deps: deps,
|
||||
sitesProvider: opts.SitesProvider,
|
||||
store: hstore.NewScratch(),
|
||||
GoVersion: goVersion,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,3 +75,39 @@ multihost={{ hugo.IsMultihost }}
|
||||
"multihost=false",
|
||||
)
|
||||
}
|
||||
|
||||
func TestHugoSites(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'fr'
|
||||
defaultContentLanguageInSubdir = true
|
||||
defaultContentVersionInSubdir = true
|
||||
defaultContentRoleInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
[languages.de]
|
||||
weight = 3
|
||||
[roles]
|
||||
[roles.guest]
|
||||
weight = 1
|
||||
[roles.member]
|
||||
weight = 2
|
||||
[versions]
|
||||
[versions.'v1.0.0']
|
||||
weight = 1
|
||||
[versions.'v2.0.0']
|
||||
weight = 2
|
||||
-- layouts/home.html --
|
||||
{{ range hugo.Sites }}{{ .Language.Name }}-{{ .Role.Name }}-{{ .Version.Name }}|{{ end }}
|
||||
`
|
||||
|
||||
b := hugolib.Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/guest/v1.0.0/en/index.html", "en-guest-v1.0.0|en-member-v1.0.0|en-guest-v2.0.0|en-member-v2.0.0|fr-guest-v1.0.0|fr-member-v1.0.0|fr-guest-v2.0.0|fr-member-v2.0.0|de-guest-v1.0.0|de-member-v1.0.0|de-guest-v2.0.0|de-member-v2.0.0|")
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ import (
|
||||
func TestHugoInfo(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
|
||||
hugoInfo := NewInfo(conf, nil)
|
||||
opts := HugoInfoOptions{
|
||||
Conf: testConfig{environment: "production", workingDir: "/mywork", running: false},
|
||||
}
|
||||
hugoInfo := NewInfo(opts, nil)
|
||||
|
||||
c.Assert(hugoInfo.Version(), qt.Equals, CurrentVersion.Version())
|
||||
c.Assert(fmt.Sprintf("%T", version.VersionString("")), qt.Equals, fmt.Sprintf("%T", hugoInfo.Version()))
|
||||
@@ -46,7 +48,11 @@ func TestHugoInfo(t *testing.T) {
|
||||
c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
|
||||
c.Assert(hugoInfo.IsServer(), qt.Equals, false)
|
||||
|
||||
devHugoInfo := NewInfo(testConfig{environment: "development", running: true}, nil)
|
||||
opts = HugoInfoOptions{
|
||||
Conf: testConfig{environment: "development", running: true},
|
||||
}
|
||||
devHugoInfo := NewInfo(opts, nil)
|
||||
|
||||
c.Assert(devHugoInfo.IsDevelopment(), qt.Equals, true)
|
||||
c.Assert(devHugoInfo.IsProduction(), qt.Equals, false)
|
||||
c.Assert(devHugoInfo.IsServer(), qt.Equals, true)
|
||||
@@ -73,8 +79,10 @@ func TestDeprecationLogLevelFromVersion(t *testing.T) {
|
||||
func TestMarkupScope(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
conf := testConfig{environment: "production", workingDir: "/mywork", running: false}
|
||||
info := NewInfo(conf, nil)
|
||||
opts := HugoInfoOptions{
|
||||
Conf: testConfig{environment: "production", workingDir: "/mywork", running: false},
|
||||
}
|
||||
info := NewInfo(opts, nil)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
+14
-2
@@ -19,6 +19,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -102,8 +103,9 @@ type HugoSites struct {
|
||||
previousPageTreesWalkContext *doctree.WalkContext[contentNode] // Set for rebuilds only.
|
||||
previousSeenTerms *hmaps.Map[term, sitesmatrix.Vectors] // Set for rebuilds only.
|
||||
|
||||
printUnusedTemplatesInit sync.Once
|
||||
printPathWarningsInit sync.Once
|
||||
printUnusedTemplatesInit sync.Once
|
||||
printPathWarningsInit sync.Once
|
||||
printSiteSitesDeprecationInit sync.Once
|
||||
|
||||
// File change events with filename stored in this map will be skipped.
|
||||
skipRebuildForFilenamesMu sync.Mutex
|
||||
@@ -122,6 +124,16 @@ type HugoSites struct {
|
||||
buildCounter atomic.Uint64
|
||||
}
|
||||
|
||||
// hugoSitesSitesProvider is a wrapper that implements hugo.SitesProvider.
|
||||
// This avoids naming conflict with HugoSites.Sites field.
|
||||
type hugoSitesSitesProvider struct {
|
||||
h *HugoSites
|
||||
}
|
||||
|
||||
func (sp hugoSitesSitesProvider) Sites() any {
|
||||
return slices.Collect(sp.h.allSites(nil))
|
||||
}
|
||||
|
||||
type progressReporter struct {
|
||||
mu sync.Mutex
|
||||
t time.Time
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2024 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"
|
||||
|
||||
func TestSitesAndLanguageOrder(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "fr"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
[languages.de]
|
||||
weight = 3
|
||||
-- layouts/home.html --
|
||||
{{ $bundle := site.GetPage "bundle" }}
|
||||
Bundle all translations: {{ range $bundle.AllTranslations }}{{ .Lang }}|{{ end }}$
|
||||
Bundle translations: {{ range $bundle.Translations }}{{ .Lang }}|{{ end }}$
|
||||
Site languages: {{ range site.Languages }}{{ .Lang }}|{{ end }}$
|
||||
Sites: {{ range site.Sites }}{{ .Language.Lang }}|{{ end }}$
|
||||
-- content/bundle/index.fr.md --
|
||||
---
|
||||
title: "Bundle Fr"
|
||||
---
|
||||
-- content/bundle/index.en.md --
|
||||
---
|
||||
title: "Bundle En"
|
||||
---
|
||||
-- content/bundle/index.de.md --
|
||||
---
|
||||
title: "Bundle De"
|
||||
---
|
||||
|
||||
`
|
||||
b := Test(t, files)
|
||||
|
||||
b.AssertFileContent("public/en/index.html",
|
||||
"Bundle all translations: en|fr|de|$",
|
||||
"Bundle translations: fr|de|$",
|
||||
"Site languages: en|fr|de|$",
|
||||
"Sites: en|fr|de|$",
|
||||
)
|
||||
}
|
||||
|
||||
func TestSitesOrder(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
defaultContentLanguage = "fr"
|
||||
defaultContentLanguageInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
[languages.de]
|
||||
weight = 3
|
||||
[roles]
|
||||
[roles.guest]
|
||||
weight = 1
|
||||
[roles.member]
|
||||
weight = 2
|
||||
-- layouts/home.html --
|
||||
Sites: {{ range site.Sites }}{{ .Language.Lang }}|{{ end }}$
|
||||
Languages: {{ range site.Languages }}{{ .Lang }}|{{ end }}
|
||||
`
|
||||
b := Test(t, files)
|
||||
// Note that before v0.152.0 the order of these slices where different .Sites pulled the defaultContentLanguage first.
|
||||
b.AssertFileContent("public/en/index.html", "Sites: en|fr|de|$", "Languages: en|fr|de|")
|
||||
}
|
||||
+25
-5
@@ -517,7 +517,14 @@ func newHugoSites(
|
||||
dependencies = append(dependencies, depFromMod(m))
|
||||
}
|
||||
|
||||
h.hugoInfo = hugo.NewInfo(h.Configs.GetFirstLanguageConfig(), dependencies)
|
||||
// Create a sites provider that avoids naming conflict with HugoSites.Sites field.
|
||||
sp := hugoSitesSitesProvider{h: h}
|
||||
|
||||
opts := hugo.HugoInfoOptions{
|
||||
Conf: h.Configs.GetFirstLanguageConfig(),
|
||||
SitesProvider: sp,
|
||||
}
|
||||
h.hugoInfo = hugo.NewInfo(opts, dependencies)
|
||||
|
||||
var prototype *deps.Deps
|
||||
|
||||
@@ -627,15 +634,28 @@ func (s *Site) LanguageCode() string {
|
||||
return s.Language().LanguageCode()
|
||||
}
|
||||
|
||||
// Returns all Sites for all languages.
|
||||
// Returns all sites for all dimensions.
|
||||
// Deprecated: Use hugo.Sites instead.
|
||||
func (s *Site) Sites() page.Sites {
|
||||
sites := make(page.Sites, len(s.h.Sites))
|
||||
for i, s := range s.h.Sites {
|
||||
sites[i] = s.Site()
|
||||
s.h.printSiteSitesDeprecationInit.Do(func() {
|
||||
hugo.Deprecate(".Site.Sites", "Use hugo.Sites instead.", "v0.156.0")
|
||||
})
|
||||
var sites page.Sites
|
||||
for _, v1 := range s.h.sitesVersionsRoles {
|
||||
for _, v2 := range v1 {
|
||||
for _, s := range v2 {
|
||||
sites = append(sites, s.Site())
|
||||
}
|
||||
}
|
||||
}
|
||||
return sites
|
||||
}
|
||||
|
||||
// IsDefault reports whether this site is the default across all dimensions.
|
||||
func (s *Site) IsDefault() bool {
|
||||
return s.siteLanguageVersionRole.isDefault()
|
||||
}
|
||||
|
||||
// Returns Site currently rendering.
|
||||
func (s *Site) Current() page.Site {
|
||||
return s.h.currentSite
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2026 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"
|
||||
|
||||
func TestSiteIsDefault(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['rss','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'fr'
|
||||
defaultContentLanguageInSubdir = true
|
||||
defaultContentVersionInSubdir = true
|
||||
defaultContentRoleInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
title = 'English'
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
title = 'French'
|
||||
[languages.de]
|
||||
weight = 3
|
||||
title = 'German'
|
||||
[roles]
|
||||
[roles.guest]
|
||||
weight = 1
|
||||
[roles.member]
|
||||
weight = 2
|
||||
[versions]
|
||||
[versions.'v1.0.0']
|
||||
weight = 1
|
||||
[versions.'v2.0.0']
|
||||
weight = 2
|
||||
-- content/p1.en.md --
|
||||
---
|
||||
title: Page 1 EN
|
||||
---
|
||||
-- content/p1.fr.md --
|
||||
---
|
||||
title: Page 1 FR
|
||||
---
|
||||
-- content/p1.de.md --
|
||||
---
|
||||
title: Page 1 DE
|
||||
---
|
||||
-- layouts/_default/single.html --
|
||||
Current site is default: {{ .Site.IsDefault }}
|
||||
{{ range hugo.Sites }}
|
||||
{{ .Language.Name }}-{{ .Role.Name }}-{{ .Version.Name }}: IsDefault={{ .IsDefault }}
|
||||
{{ end }}
|
||||
`
|
||||
|
||||
b := Test(t, files)
|
||||
b.AssertFileContent("public/guest/v1.0.0/en/p1/index.html",
|
||||
"Current site is default: false",
|
||||
"en-guest-v1.0.0: IsDefault=false",
|
||||
"en-member-v1.0.0: IsDefault=false",
|
||||
"fr-guest-v1.0.0: IsDefault=true",
|
||||
"fr-member-v1.0.0: IsDefault=false",
|
||||
"de-guest-v1.0.0: IsDefault=false",
|
||||
"de-member-v1.0.0: IsDefault=false",
|
||||
)
|
||||
b.AssertFileContent("public/guest/v1.0.0/fr/p1/index.html",
|
||||
"Current site is default: true",
|
||||
"fr-guest-v1.0.0: IsDefault=true",
|
||||
)
|
||||
b.AssertFileContent("public/guest/v1.0.0/de/p1/index.html",
|
||||
"Current site is default: false",
|
||||
"fr-guest-v1.0.0: IsDefault=true",
|
||||
)
|
||||
}
|
||||
|
||||
func TestSiteSites(t *testing.T) {
|
||||
files := `
|
||||
-- hugo.toml --
|
||||
disableKinds = ['page','rss','section','sitemap','taxonomy','term']
|
||||
defaultContentLanguage = 'fr'
|
||||
defaultContentLanguageInSubdir = true
|
||||
defaultContentVersionInSubdir = true
|
||||
defaultContentRoleInSubdir = true
|
||||
[languages]
|
||||
[languages.en]
|
||||
weight = 1
|
||||
[languages.fr]
|
||||
weight = 2
|
||||
[languages.de]
|
||||
weight = 3
|
||||
[roles]
|
||||
[roles.guest]
|
||||
weight = 1
|
||||
[roles.member]
|
||||
weight = 2
|
||||
[versions]
|
||||
[versions.'v1.0.0']
|
||||
weight = 1
|
||||
[versions.'v2.0.0']
|
||||
weight = 2
|
||||
-- layouts/home.html --
|
||||
{{ range .Site.Sites }}{{ .Language.Name }}-{{ .Role.Name }}-{{ .Version.Name }}|{{ end }}
|
||||
`
|
||||
b := Test(t, files, TestOptInfo())
|
||||
|
||||
b.AssertFileContent("public/guest/v1.0.0/fr/index.html", "en-guest-v1.0.0|en-member-v1.0.0|en-guest-v2.0.0|en-member-v2.0.0|fr-guest-v1.0.0|fr-member-v1.0.0|fr-guest-v2.0.0|fr-member-v2.0.0|de-guest-v1.0.0|de-member-v1.0.0|de-guest-v2.0.0|de-member-v2.0.0|")
|
||||
b.AssertLogContains(".Site.Sites was deprecated")
|
||||
}
|
||||
@@ -27,8 +27,16 @@ import (
|
||||
|
||||
func TestPageMatcher(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
developmentTestSite := testSite{h: hugo.NewInfo(testConfig{environment: "development"}, nil)}
|
||||
productionTestSite := testSite{h: hugo.NewInfo(testConfig{environment: "production"}, nil)}
|
||||
|
||||
opts := hugo.HugoInfoOptions{
|
||||
Conf: testConfig{environment: "development"},
|
||||
}
|
||||
developmentTestSite := testSite{h: hugo.NewInfo(opts, nil)}
|
||||
|
||||
opts = hugo.HugoInfoOptions{
|
||||
Conf: testConfig{environment: "production"},
|
||||
}
|
||||
productionTestSite := testSite{h: hugo.NewInfo(opts, nil)}
|
||||
|
||||
dec := cascadeConfigDecoder{}
|
||||
|
||||
|
||||
+18
-3
@@ -77,12 +77,15 @@ type Site interface {
|
||||
// Returns the configured copyright information for this Site.
|
||||
Copyright() string
|
||||
|
||||
// Returns all Sites for all languages.
|
||||
// Returns all sites for all dimensions.
|
||||
Sites() Sites
|
||||
|
||||
// Returns Site currently rendering.
|
||||
Current() Site
|
||||
|
||||
// Reports whether this site is the default across all dimensions.
|
||||
IsDefault() bool
|
||||
|
||||
// Returns a struct with some information about the build.
|
||||
Hugo() hugo.HugoInfo
|
||||
|
||||
@@ -120,8 +123,9 @@ type Site interface {
|
||||
LanguagePrefix() string
|
||||
|
||||
hstore.StoreProvider
|
||||
|
||||
// String returns a string representation of the site.
|
||||
// Note that this represenetation may change in the future.
|
||||
// Note that this representation may change in the future.
|
||||
String() string
|
||||
|
||||
// For internal use only.
|
||||
@@ -239,6 +243,10 @@ func (s *siteWrapper) Current() Site {
|
||||
return s.s.Current()
|
||||
}
|
||||
|
||||
func (s *siteWrapper) IsDefault() bool {
|
||||
return s.s.IsDefault()
|
||||
}
|
||||
|
||||
func (s *siteWrapper) Config() SiteConfig {
|
||||
return s.s.Config()
|
||||
}
|
||||
@@ -350,6 +358,10 @@ func (t testSite) Current() Site {
|
||||
return t
|
||||
}
|
||||
|
||||
func (t testSite) IsDefault() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (s testSite) LanguagePrefix() string {
|
||||
return ""
|
||||
}
|
||||
@@ -439,8 +451,11 @@ func (s testSite) CheckReady() {
|
||||
|
||||
// NewDummyHugoSite creates a new minimal test site.
|
||||
func NewDummyHugoSite(conf config.AllProvider) Site {
|
||||
opts := hugo.HugoInfoOptions{
|
||||
Conf: conf,
|
||||
}
|
||||
return testSite{
|
||||
h: hugo.NewInfo(conf, nil),
|
||||
h: hugo.NewInfo(opts, nil),
|
||||
l: &langs.Language{
|
||||
Lang: "en",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user