conf ConfigProvider
deps []*Dependency
+ sitesProvider SitesProvider
+
store *hstore.Scratch
// Context gives access to some of the context scoped variables.
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 (
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 (
}
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,
}
}
"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|")
+}
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()))
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)
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()
"fmt"
"io"
"iter"
+ "slices"
"strings"
"sync"
"sync/atomic"
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
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
+++ /dev/null
-// 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|")
-}
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
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
--- /dev/null
+// 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")
+}
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{}
// 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
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.
return s.s.Current()
}
+func (s *siteWrapper) IsDefault() bool {
+ return s.s.IsDefault()
+}
+
func (s *siteWrapper) Config() SiteConfig {
return s.s.Config()
}
return t
}
+func (t testSite) IsDefault() bool {
+ return true
+}
+
func (s testSite) LanguagePrefix() string {
return ""
}
// 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",
},