* This break the circular dependency between hugolib and resources/page, and allow HugoInfo to use the proper Page ans Site interfaces.
* It's a little misplaced, but it makes everything easier to maintain and test, so that's well worth it.
logger := f.c.r.logger
if i == 0 {
- r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment)
+ r.Printf("Environment: %q\n", f.c.hugoTry().Deps.Site.Hugo().Environment())
mainTarget := "disk"
if f.c.r.renderToMemory {
mainTarget = "memory"
import (
"context"
"fmt"
- "html/template"
"os"
"path/filepath"
"runtime/debug"
"sync"
"time"
+ "github.com/bep/helpers/contexthelpers"
"github.com/bep/logg"
"github.com/bep/godartsass/v2"
"github.com/gohugoio/hugo/common/hexec"
- "github.com/gohugoio/hugo/common/hstore"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/version"
"github.com/gohugoio/hugo/hugofs/files"
- "github.com/bep/helpers/contexthelpers"
"github.com/spf13/afero"
iofs "io/fs"
vendorInfo string
)
-var _ hstore.StoreProvider = (*HugoInfo)(nil)
-
-// HugoInfo contains information about the current Hugo environment
-type HugoInfo struct {
- CommitHash string
- BuildDate string
-
- // The build environment.
- // Defaults are "production" (hugo) and "development" (hugo server).
- // This can also be set by the user.
- // It can be any string, but it will be all lower case.
- Environment string
-
- // version of go that the Hugo binary was built with
- GoVersion string
-
- conf ConfigProvider
- deps []*Dependency
-
- sitesProvider SitesProvider
-
- store *hstore.Scratch
-
- // Context gives access to some of the context scoped variables.
- Context Context
-}
-
-// Version returns the current version as a comparable version string.
-func (i HugoInfo) Version() version.VersionString {
- return CurrentVersion.Version()
-}
-
-// Generator a Hugo meta generator HTML tag.
-func (i HugoInfo) Generator() template.HTML {
- return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, CurrentVersion.String()))
-}
-
-// IsDevelopment reports whether the current running environment is "development".
-func (i HugoInfo) IsDevelopment() bool {
- return i.Environment == EnvironmentDevelopment
-}
-
-// IsProduction reports whether the current running environment is "production".
-func (i HugoInfo) IsProduction() bool {
- return i.Environment == EnvironmentProduction
-}
-
-// IsServer reports whether the built-in server is running.
-func (i HugoInfo) IsServer() bool {
- return i.conf.Running()
-}
-
-// IsExtended reports whether the Hugo binary is the extended version.
-func (i HugoInfo) IsExtended() bool {
- return IsExtended
-}
-
-// WorkingDir returns the project working directory.
-func (i HugoInfo) WorkingDir() string {
- return i.conf.WorkingDir()
-}
-
-// Deps gets a list of dependencies for this Hugo build.
-func (i HugoInfo) Deps() []*Dependency {
- return i.deps
+// BuildInfo holds build information extracted from runtime/debug.
+type BuildInfo struct {
+ Revision string
+ RevisionTime string
+ GoVersion string
}
-func (i HugoInfo) Store() *hstore.Scratch {
- return i.store
-}
-
-// Deprecated: Use hugo.IsMultihost instead.
-func (i HugoInfo) IsMultiHost() bool {
- Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
- return i.conf.IsMultihost()
-}
-
-// IsMultihost reports whether each configured language has a unique baseURL.
-func (i HugoInfo) IsMultihost() bool {
- return i.conf.IsMultihost()
-}
-
-// IsMultilingual reports whether there are two or more configured languages.
-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 {
+// GetBuildInfo returns the build info for the current binary.
+func GetBuildInfo() *BuildInfo {
+ bi := getBuildInfo()
+ if bi == nil {
return nil
}
- return i.sitesProvider.Sites()
+ return &BuildInfo{
+ Revision: bi.Revision,
+ RevisionTime: bi.RevisionTime,
+ GoVersion: bi.GoVersion,
+ }
}
type contextKey uint8
var markupScope = contexthelpers.NewContextDispatcher[string](contextKeyMarkupScope)
+// Context gives access to some of the context scoped variables.
type Context struct{}
func (c Context) MarkupScope(ctx context.Context) string {
return markupScope.Get(ctx)
}
-// ConfigProvider represents the config options that are relevant for HugoInfo.
-type ConfigProvider interface {
- Environment() string
- Running() bool
- WorkingDir() string
- IsMultihost() bool
- 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(opts HugoInfoOptions, deps []*Dependency) HugoInfo {
- if opts.Conf.Environment() == "" {
- panic("environment not set")
- }
- var (
- commitHash string
- buildDate string
- goVersion string
- )
-
- bi := getBuildInfo()
- if bi != nil {
- commitHash = bi.Revision
- buildDate = bi.RevisionTime
- goVersion = bi.GoVersion
- }
-
- return HugoInfo{
- CommitHash: commitHash,
- BuildDate: buildDate,
- Environment: opts.Conf.Environment(),
- conf: opts.Conf,
- deps: deps,
- sitesProvider: opts.SitesProvider,
- store: hstore.NewScratch(),
- GoVersion: goVersion,
- }
-}
-
// GetExecEnviron creates and gets the common os/exec environment used in the
// external programs we interact with via os/exec, e.g. postcss.
func GetExecEnviron(workDir string, cfg config.AllProvider, fs afero.Fs) []string {
import (
"context"
- "fmt"
"testing"
"github.com/bep/logg"
qt "github.com/frankban/quicktest"
- "github.com/gohugoio/hugo/common/version"
)
-func TestHugoInfo(t *testing.T) {
- c := qt.New(t)
-
- 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.WorkingDir(), qt.Equals, "/mywork")
-
- bi := getBuildInfo()
- if bi != nil {
- c.Assert(hugoInfo.CommitHash, qt.Equals, bi.Revision)
- c.Assert(hugoInfo.BuildDate, qt.Equals, bi.RevisionTime)
- c.Assert(hugoInfo.GoVersion, qt.Equals, bi.GoVersion)
- }
- c.Assert(hugoInfo.Environment, qt.Equals, "production")
- c.Assert(string(hugoInfo.Generator()), qt.Contains, fmt.Sprintf("Hugo %s", hugoInfo.Version()))
- c.Assert(hugoInfo.IsDevelopment(), qt.Equals, false)
- c.Assert(hugoInfo.IsProduction(), qt.Equals, true)
- c.Assert(hugoInfo.IsExtended(), qt.Equals, IsExtended)
- c.Assert(hugoInfo.IsServer(), qt.Equals, false)
-
- 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 TestDeprecationLogLevelFromVersion(t *testing.T) {
c := qt.New(t)
func TestMarkupScope(t *testing.T) {
c := qt.New(t)
- opts := HugoInfoOptions{
- Conf: testConfig{environment: "production", workingDir: "/mywork", running: false},
- }
- info := NewInfo(opts, nil)
-
ctx := context.Background()
-
ctx = SetMarkupScope(ctx, "foo")
- c.Assert(info.Context.MarkupScope(ctx), qt.Equals, "foo")
-}
-
-type testConfig struct {
- environment string
- running bool
- workingDir string
- multihost bool
- multilingual bool
-}
-
-func (c testConfig) Environment() string {
- return c.environment
-}
-
-func (c testConfig) Running() bool {
- return c.running
+ var hugoCtx Context
+ c.Assert(hugoCtx.MarkupScope(ctx), qt.Equals, "foo")
+ c.Assert(GetMarkupScope(ctx), qt.Equals, "foo")
}
-func (c testConfig) WorkingDir() string {
- return c.workingDir
-}
-
-func (c testConfig) IsMultihost() bool {
- return c.multihost
-}
+func TestGetBuildInfo(t *testing.T) {
+ c := qt.New(t)
-func (c testConfig) IsMultilingual() bool {
- return c.multilingual
+ bi := GetBuildInfo()
+ // In test mode, build info may or may not be available.
+ if bi != nil {
+ c.Assert(bi.GoVersion, qt.Not(qt.Equals), "")
+ }
}
"fmt"
"io"
"iter"
- "slices"
"strings"
"sync"
"sync/atomic"
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hsync"
"github.com/gohugoio/hugo/common/htime"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/common/para"
"github.com/gohugoio/hugo/common/terminal"
Configs *allconfig.Configs
- hugoInfo hugo.HugoInfo
+ hugoInfo page.HugoInfo
// Render output formats for all sites.
renderFormats output.Formats
buildCounter atomic.Uint64
}
-// hugoSitesSitesProvider is a wrapper that implements hugo.SitesProvider.
+// hugoSitesSitesProvider is a wrapper that implements page.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))
+func (sp hugoSitesSitesProvider) Sites() page.Sites {
+ sites := make(page.Sites, 0)
+ for s := range sp.h.allSites(nil) {
+ sites = append(sites, s.Site())
+ }
+ return sites
}
type progressReporter struct {
ps.targetPathDescriptor = pp.targetPathDescriptor
ps.RefProvider = newPageRef(ps)
ps.SitesProvider = ps.s
+ ps.SiteProvider = ps
return nil
}
page.RawContentProvider
page.RefProvider
page.ShortcodeInfoProvider
+ page.SiteProvider
page.SitesProvider
page.TranslationsProvider
page.TreeProvider
// Create a sites provider that avoids naming conflict with HugoSites.Sites field.
sp := hugoSitesSitesProvider{h: h}
- opts := hugo.HugoInfoOptions{
+ opts := page.HugoInfoOptions{
Conf: h.Configs.GetFirstLanguageConfig(),
SitesProvider: sp,
+ Deps: dependencies,
}
- h.hugoInfo = hugo.NewInfo(opts, dependencies)
+
+ if bi := hugo.GetBuildInfo(); bi != nil {
+ opts.CommitHash = bi.Revision
+ opts.BuildDate = bi.RevisionTime
+ opts.GoVersion = bi.GoVersion
+ }
+
+ h.hugoInfo = page.NewHugoInfo(opts)
var prototype *deps.Deps
}
// Returns a struct with some information about the build.
-func (s *Site) Hugo() hugo.HugoInfo {
- if s.h == nil {
- panic("site: hugo: h not initialized")
- }
- if s.h.hugoInfo.Environment == "" {
- panic("site: hugo: hugoInfo not initialized")
- }
+func (s *Site) Hugo() page.HugoInfo {
return s.h.hugoInfo
}
--- /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 page
+
+import (
+ "fmt"
+ "html/template"
+
+ "github.com/gohugoio/hugo/common/hstore"
+ "github.com/gohugoio/hugo/common/hugo"
+ "github.com/gohugoio/hugo/common/version"
+)
+
+var _ hstore.StoreProvider = (*HugoInfo)(nil)
+
+type hugoInfoProviders struct {
+ SitesProvider
+}
+
+// HugoInfo contains information about the current Hugo environment.
+type HugoInfo struct {
+ CommitHash string
+ BuildDate string
+
+ // Version of Go that the Hugo binary was built with.
+ GoVersion string
+
+ // Avoid exporting the embedded providers directly, as they may be used by the template layer.
+ hugoInfoProviders
+
+ // Context gives access to some of the context scoped variables.
+ Context hugo.Context
+
+ opts HugoInfoOptions
+ store *hstore.Scratch
+}
+
+// Version returns the current version as a comparable version string.
+func (i HugoInfo) Version() version.VersionString {
+ return hugo.CurrentVersion.Version()
+}
+
+// Generator returns a Hugo meta generator HTML tag.
+func (i HugoInfo) Generator() template.HTML {
+ return template.HTML(fmt.Sprintf(`<meta name="generator" content="Hugo %s">`, hugo.CurrentVersion.String()))
+}
+
+// Environment returns the build environment.
+// Defaults are "production" (hugo) and "development" (hugo server).
+// This can also be set by the user.
+// It can be any string, but it will be all lower case.
+func (i HugoInfo) Environment() string {
+ return i.opts.Conf.Environment()
+}
+
+// IsDevelopment reports whether the current running environment is "development".
+func (i HugoInfo) IsDevelopment() bool {
+ return i.Environment() == hugo.EnvironmentDevelopment
+}
+
+// IsProduction reports whether the current running environment is "production".
+func (i HugoInfo) IsProduction() bool {
+ return i.Environment() == hugo.EnvironmentProduction
+}
+
+// IsServer reports whether the built-in server is running.
+func (i HugoInfo) IsServer() bool {
+ return i.opts.Conf.Running()
+}
+
+// IsExtended reports whether the Hugo binary is the extended version.
+func (i HugoInfo) IsExtended() bool {
+ return hugo.IsExtended
+}
+
+// WorkingDir returns the project working directory.
+func (i HugoInfo) WorkingDir() string {
+ return i.opts.Conf.WorkingDir()
+}
+
+// Deps gets a list of dependencies for this Hugo build.
+func (i HugoInfo) Deps() []*hugo.Dependency {
+ return i.opts.Deps
+}
+
+func (i HugoInfo) Store() *hstore.Scratch {
+ return i.store
+}
+
+// Deprecated: Use hugo.IsMultihost instead.
+func (i HugoInfo) IsMultiHost() bool {
+ hugo.Deprecate("hugo.IsMultiHost", "Use hugo.IsMultihost instead.", "v0.124.0")
+ return i.opts.Conf.IsMultihost()
+}
+
+// IsMultihost reports whether each configured language has a unique baseURL.
+func (i HugoInfo) IsMultihost() bool {
+ return i.opts.Conf.IsMultihost()
+}
+
+// IsMultilingual reports whether there are two or more configured languages.
+func (i HugoInfo) IsMultilingual() bool {
+ return i.opts.Conf.IsMultilingual()
+}
+
+// HugoInfoConfigProvider represents the config options that are relevant for HugoInfo.
+type HugoInfoConfigProvider interface {
+ Environment() string
+ Running() bool
+ WorkingDir() string
+ IsMultihost() bool
+ IsMultilingual() bool
+}
+
+// HugoInfoOptions defines the providers required to initialize HugoInfo.
+type HugoInfoOptions struct {
+ Conf HugoInfoConfigProvider
+ Deps []*hugo.Dependency
+ SitesProvider SitesProvider
+ CommitHash string
+ BuildDate string
+ GoVersion string
+}
+
+// NewHugoInfo creates a new Hugo Info object.
+func NewHugoInfo(opts HugoInfoOptions) HugoInfo {
+ if opts.Conf == nil {
+ panic("config provider not set")
+ }
+ if opts.Conf.Environment() == "" {
+ panic("environment not set")
+ }
+ if opts.SitesProvider == nil {
+ opts.SitesProvider = nopSitesProvider{}
+ }
+
+ return HugoInfo{
+ CommitHash: opts.CommitHash,
+ BuildDate: opts.BuildDate,
+ GoVersion: opts.GoVersion,
+
+ hugoInfoProviders: hugoInfoProviders{SitesProvider: opts.SitesProvider},
+
+ opts: opts,
+ store: hstore.NewScratch(),
+ }
+}
--- /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 page
+
+import (
+ "testing"
+
+ qt "github.com/frankban/quicktest"
+)
+
+func TestNewHugoInfo(t *testing.T) {
+ c := qt.New(t)
+
+ c.Assert(func() { NewHugoInfo(HugoInfoOptions{}) }, qt.PanicMatches, "config provider not set")
+
+ opts := HugoInfoOptions{
+ Conf: testConfig{environment: "development"},
+ }
+
+ h := NewHugoInfo(opts)
+
+ c.Assert(h.Environment(), qt.Equals, "development")
+ c.Assert(h.Sites(), qt.IsNil)
+}
resource.TranslationKeyProvider
TranslationsProvider
+ SiteProvider
SitesProvider
// Helper methods
HasShortcode(name string) bool
}
+type nopSitesProvider struct{}
+
+func (nopSitesProvider) Sites() Sites {
+ return nil
+}
+
// SitesProvider provide accessors to get sites.
type SitesProvider interface {
- // Site returns the current site.
- Site() Site
- // Sites returns all sites.
+ // Sites returns all sites for all dimensions.
Sites() Sites
}
+// SiteProvider provides access to the current site.
+type SiteProvider interface {
+ Site() Site
+}
+
// TableOfContentsProvider provides the table of contents for a Page.
type TableOfContentsProvider interface {
// TableOfContents returns the table of contents for the page rendered as HTML.
}
func (m PageMatcher) Matches(p Page) bool {
- return m.Match(p.Kind(), p.Path(), p.Site().Hugo().Environment, nil)
+ return m.Match(p.Kind(), p.Path(), p.Site().Hugo().Environment(), nil)
}
func (m PageMatcher) Match(kind, path, environment string, sitesMatrix sitesmatrix.VectorProvider) bool {
"testing"
"github.com/gohugoio/hugo/common/hmaps"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/hugolib/sitesmatrix"
func TestPageMatcher(t *testing.T) {
c := qt.New(t)
- opts := hugo.HugoInfoOptions{
+ opts := HugoInfoOptions{
Conf: testConfig{environment: "development"},
}
- developmentTestSite := testSite{h: hugo.NewInfo(opts, nil)}
+ developmentTestSite := &testSite{h: NewHugoInfo(opts)}
- opts = hugo.HugoInfoOptions{
+ opts = HugoInfoOptions{
Conf: testConfig{environment: "production"},
}
- productionTestSite := testSite{h: hugo.NewInfo(opts, nil)}
+ productionTestSite := &testSite{h: NewHugoInfo(opts)}
dec := cascadeConfigDecoder{}
p1, p2, p3 := &testPage{path: "/p1", kind: "section", lang: "en", site: developmentTestSite},
&testPage{path: "p2", kind: "page", lang: "no", site: productionTestSite},
- &testPage{path: "p3", kind: "page", lang: "en"}
+ &testPage{path: "p3", kind: "page", lang: "en", site: developmentTestSite}
c.Run("Matches", func(c *qt.C) {
m := PageMatcher{Kind: "section"}
m = PageMatcher{Environment: "development"}
c.Assert(m.Matches(p1), qt.Equals, true)
c.Assert(m.Matches(p2), qt.Equals, false)
- c.Assert(m.Matches(p3), qt.Equals, false)
+ c.Assert(m.Matches(p3), qt.Equals, true)
m = PageMatcher{Environment: "production"}
c.Assert(m.Matches(p1), qt.Equals, false)
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/source"
return false
}
-func (p *nopPage) Hugo() (h hugo.HugoInfo) {
+func (p *nopPage) Hugo() (h HugoInfo) {
return
}
"github.com/gohugoio/hugo/config"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/navigation"
)
IsDefault() bool
// Returns a struct with some information about the build.
- Hugo() hugo.HugoInfo
+ Hugo() HugoInfo
// Returns the BaseURL for this Site.
BaseURL() string
return s.s.Config()
}
-func (s *siteWrapper) Hugo() hugo.HugoInfo {
+func (s *siteWrapper) Hugo() HugoInfo {
return s.s.Hugo()
}
}
type testSite struct {
- h hugo.HugoInfo
+ h HugoInfo
l *langs.Language
}
-func (t testSite) Hugo() hugo.HugoInfo {
+func (t testSite) Hugo() HugoInfo {
return t.h
}
// NewDummyHugoSite creates a new minimal test site.
func NewDummyHugoSite(conf config.AllProvider) Site {
- opts := hugo.HugoInfoOptions{
+ opts := HugoInfoOptions{
Conf: conf,
}
return testSite{
- h: hugo.NewInfo(opts, nil),
+ h: NewHugoInfo(opts),
l: &langs.Language{
Lang: "en",
},
"github.com/gohugoio/hugo/common/hmaps"
"github.com/gohugoio/hugo/common/hstore"
- "github.com/gohugoio/hugo/common/hugo"
"github.com/gohugoio/hugo/common/paths"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/hugofs"
currentSection: &testPage{
sectionEntries: []string{"a", "b", "c"},
},
- site: testSite{l: l},
+ site: &testSite{l: l},
}
}
linkTitle string
lang string
section string
- site testSite
+ site *testSite
content string
panic("testpage: not implemented")
}
-func (p *testPage) Hugo() hugo.HugoInfo {
+func (p *testPage) Hugo() HugoInfo {
panic("testpage: not implemented")
}
}
func (p *testPage) Site() Site {
+ if p.site == nil {
+ panic(fmt.Sprintf("testpage: site is nil for %q", p.path))
+ }
return p.site
}