aboutsummaryrefslogtreecommitdiffhomepage
path: root/deps
diff options
context:
space:
mode:
authorBjørn Erik Pedersen <[email protected]>2023-01-04 18:24:36 +0100
committerBjørn Erik Pedersen <[email protected]>2023-05-16 18:01:29 +0200
commit241b21b0fd34d91fccb2ce69874110dceae6f926 (patch)
treed4e0118eac7e9c42f065815447a70805f8d6ad3e /deps
parent6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff)
downloadhugo-241b21b0fd34d91fccb2ce69874110dceae6f926.tar.gz
hugo-241b21b0fd34d91fccb2ce69874110dceae6f926.zip
Create a struct with all of Hugo's config options
Primary motivation is documentation, but it will also hopefully simplify the code. Also, * Lower case the default output format names; this is in line with the custom ones (map keys) and how it's treated all the places. This avoids doing `stringds.EqualFold` everywhere. Closes #10896 Closes #10620
Diffstat (limited to 'deps')
-rw-r--r--deps/deps.go450
-rw-r--r--deps/deps_test.go5
2 files changed, 178 insertions, 277 deletions
diff --git a/deps/deps.go b/deps/deps.go
index 511ee885c..9cb8557a5 100644
--- a/deps/deps.go
+++ b/deps/deps.go
@@ -4,30 +4,27 @@ import (
"context"
"fmt"
"path/filepath"
+ "sort"
"strings"
"sync"
"sync/atomic"
- "time"
- "github.com/gohugoio/hugo/cache/filecache"
"github.com/gohugoio/hugo/common/hexec"
"github.com/gohugoio/hugo/common/loggers"
- "github.com/gohugoio/hugo/common/types"
"github.com/gohugoio/hugo/config"
+ "github.com/gohugoio/hugo/config/allconfig"
"github.com/gohugoio/hugo/config/security"
"github.com/gohugoio/hugo/helpers"
"github.com/gohugoio/hugo/hugofs"
- "github.com/gohugoio/hugo/langs"
"github.com/gohugoio/hugo/media"
"github.com/gohugoio/hugo/resources/page"
"github.com/gohugoio/hugo/resources/postpub"
"github.com/gohugoio/hugo/metrics"
- "github.com/gohugoio/hugo/output"
"github.com/gohugoio/hugo/resources"
"github.com/gohugoio/hugo/source"
"github.com/gohugoio/hugo/tpl"
- "github.com/spf13/cast"
+ "github.com/spf13/afero"
jww "github.com/spf13/jwalterweatherman"
)
@@ -45,10 +42,7 @@ type Deps struct {
ExecHelper *hexec.Exec
// The templates to use. This will usually implement the full tpl.TemplateManager.
- tmpl tpl.TemplateHandler
-
- // We use this to parse and execute ad-hoc text templates.
- textTmpl tpl.TemplateParseFinder
+ tmplHandlers *tpl.TemplateHandlers
// The file systems to use.
Fs *hugofs.Fs `json:"-"`
@@ -66,56 +60,170 @@ type Deps struct {
ResourceSpec *resources.Spec
// The configuration to use
- Cfg config.Provider `json:"-"`
-
- // The file cache to use.
- FileCaches filecache.Caches
+ Conf config.AllProvider `json:"-"`
// The translation func to use
Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"`
- // The language in use. TODO(bep) consolidate with site
- Language *langs.Language
-
// The site building.
Site page.Site
- // All the output formats available for the current site.
- OutputFormatsConfig output.Formats
-
- // FilenameHasPostProcessPrefix is a set of filenames in /public that
- // contains a post-processing prefix.
- FilenameHasPostProcessPrefix []string
-
- templateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateManager) error `json:"-"`
-
+ TemplateProvider ResourceProvider
// Used in tests
OverloadedTemplateFuncs map[string]any
- translationProvider ResourceProvider
+ TranslationProvider ResourceProvider
Metrics metrics.Provider
- // Timeout is configurable in site config.
- Timeout time.Duration
-
// BuildStartListeners will be notified before a build starts.
BuildStartListeners *Listeners
// Resources that gets closed when the build is done or the server shuts down.
BuildClosers *Closers
- // Atomic values set during a build.
// This is common/global for all sites.
BuildState *BuildState
- // Whether we are in running (server) mode
- Running bool
-
*globalErrHandler
}
+func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
+ d.Conf = conf
+ d.Site = s
+ d.ExecHelper = nil
+ d.ContentSpec = nil
+
+ if err := d.Init(); err != nil {
+ return nil, err
+ }
+
+ return &d, nil
+
+}
+
+func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
+ d.tmplHandlers = t
+}
+
+func (d *Deps) Init() error {
+ if d.Conf == nil {
+ panic("conf is nil")
+ }
+
+ if d.Fs == nil {
+ // For tests.
+ d.Fs = hugofs.NewFrom(afero.NewMemMapFs(), d.Conf.BaseConfig())
+ }
+
+ if d.Log == nil {
+ d.Log = loggers.NewErrorLogger()
+ }
+
+ if d.LogDistinct == nil {
+ d.LogDistinct = helpers.NewDistinctLogger(d.Log)
+ }
+
+ if d.globalErrHandler == nil {
+ d.globalErrHandler = &globalErrHandler{}
+ }
+
+ if d.BuildState == nil {
+ d.BuildState = &BuildState{}
+ }
+
+ if d.BuildStartListeners == nil {
+ d.BuildStartListeners = &Listeners{}
+ }
+
+ if d.BuildClosers == nil {
+ d.BuildClosers = &Closers{}
+ }
+
+ if d.Metrics == nil && d.Conf.TemplateMetrics() {
+ d.Metrics = metrics.NewProvider(d.Conf.TemplateMetricsHints())
+ }
+
+ if d.ExecHelper == nil {
+ d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
+ }
+
+ if d.PathSpec == nil {
+ hashBytesReceiverFunc := func(name string, match bool) {
+ if !match {
+ return
+ }
+ d.BuildState.AddFilenameWithPostPrefix(name)
+ }
+
+ // Skip binary files.
+ mediaTypes := d.Conf.GetConfigSection("mediaTypes").(media.Types)
+ hashBytesSHouldCheck := func(name string) bool {
+ ext := strings.TrimPrefix(filepath.Ext(name), ".")
+ return mediaTypes.IsTextSuffix(ext)
+ }
+ d.Fs.PublishDir = hugofs.NewHasBytesReceiver(d.Fs.PublishDir, hashBytesSHouldCheck, hashBytesReceiverFunc, []byte(postpub.PostProcessPrefix))
+ pathSpec, err := helpers.NewPathSpec(d.Fs, d.Conf, d.Log)
+ if err != nil {
+ return err
+ }
+ d.PathSpec = pathSpec
+ } else {
+ var err error
+ d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, d.Conf, d.Log, d.PathSpec.BaseFs)
+ if err != nil {
+ return err
+ }
+ }
+
+ if d.ContentSpec == nil {
+ contentSpec, err := helpers.NewContentSpec(d.Conf, d.Log, d.Content.Fs, d.ExecHelper)
+ if err != nil {
+ return err
+ }
+ d.ContentSpec = contentSpec
+ }
+
+ if d.SourceSpec == nil {
+ d.SourceSpec = source.NewSourceSpec(d.PathSpec, nil, d.Fs.Source)
+ }
+
+ var common *resources.SpecCommon
+ if d.ResourceSpec != nil {
+ common = d.ResourceSpec.SpecCommon
+ }
+ resourceSpec, err := resources.NewSpec(d.PathSpec, common, d.BuildState, d.Log, d, d.ExecHelper)
+ if err != nil {
+ return fmt.Errorf("failed to create resource spec: %w", err)
+ }
+ d.ResourceSpec = resourceSpec
+
+ return nil
+}
+
+func (d *Deps) Compile(prototype *Deps) error {
+ var err error
+ if prototype == nil {
+ if err = d.TemplateProvider.NewResource(d); err != nil {
+ return err
+ }
+ if err = d.TranslationProvider.NewResource(d); err != nil {
+ return err
+ }
+ return nil
+ }
+
+ if err = d.TemplateProvider.CloneResource(d, prototype); err != nil {
+ return err
+ }
+
+ if err = d.TranslationProvider.CloneResource(d, prototype); err != nil {
+ return err
+ }
+
+ return nil
+}
+
type globalErrHandler struct {
// Channel for some "hard to get to" build errors
buildErrors chan error
@@ -181,236 +289,22 @@ func (b *Listeners) Notify() {
// ResourceProvider is used to create and refresh, and clone resources needed.
type ResourceProvider interface {
- Update(deps *Deps) error
- Clone(deps *Deps) error
+ NewResource(dst *Deps) error
+ CloneResource(dst, src *Deps) error
}
func (d *Deps) Tmpl() tpl.TemplateHandler {
- return d.tmpl
+ return d.tmplHandlers.Tmpl
}
func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
- return d.textTmpl
-}
-
-func (d *Deps) SetTmpl(tmpl tpl.TemplateHandler) {
- d.tmpl = tmpl
-}
-
-func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) {
- d.textTmpl = tmpl
-}
-
-// LoadResources loads translations and templates.
-func (d *Deps) LoadResources() error {
- // Note that the translations need to be loaded before the templates.
- if err := d.translationProvider.Update(d); err != nil {
- return fmt.Errorf("loading translations: %w", err)
- }
-
- if err := d.templateProvider.Update(d); err != nil {
- return fmt.Errorf("loading templates: %w", err)
- }
-
- return nil
-}
-
-// New initializes a Dep struct.
-// Defaults are set for nil values,
-// but TemplateProvider, TranslationProvider and Language are always required.
-func New(cfg DepsCfg) (*Deps, error) {
- var (
- logger = cfg.Logger
- fs = cfg.Fs
- d *Deps
- )
-
- if cfg.TemplateProvider == nil {
- panic("Must have a TemplateProvider")
- }
-
- if cfg.TranslationProvider == nil {
- panic("Must have a TranslationProvider")
- }
-
- if cfg.Language == nil {
- panic("Must have a Language")
- }
-
- if logger == nil {
- logger = loggers.NewErrorLogger()
- }
-
- if fs == nil {
- // Default to the production file system.
- fs = hugofs.NewDefault(cfg.Language)
- }
-
- if cfg.MediaTypes == nil {
- cfg.MediaTypes = media.DefaultTypes
- }
-
- if cfg.OutputFormats == nil {
- cfg.OutputFormats = output.DefaultFormats
- }
-
- securityConfig, err := security.DecodeConfig(cfg.Cfg)
- if err != nil {
- return nil, fmt.Errorf("failed to create security config from configuration: %w", err)
- }
- execHelper := hexec.New(securityConfig)
-
- var filenameHasPostProcessPrefixMu sync.Mutex
- hashBytesReceiverFunc := func(name string, match bool) {
- if !match {
- return
- }
- filenameHasPostProcessPrefixMu.Lock()
- d.FilenameHasPostProcessPrefix = append(d.FilenameHasPostProcessPrefix, name)
- filenameHasPostProcessPrefixMu.Unlock()
- }
-
- // Skip binary files.
- hashBytesSHouldCheck := func(name string) bool {
- ext := strings.TrimPrefix(filepath.Ext(name), ".")
- mime, _, found := cfg.MediaTypes.GetBySuffix(ext)
- if !found {
- return false
- }
- switch mime.MainType {
- case "text", "application":
- return true
- default:
- return false
- }
- }
- fs.PublishDir = hugofs.NewHasBytesReceiver(fs.PublishDir, hashBytesSHouldCheck, hashBytesReceiverFunc, []byte(postpub.PostProcessPrefix))
-
- ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
- if err != nil {
- return nil, fmt.Errorf("create PathSpec: %w", err)
- }
-
- fileCaches, err := filecache.NewCaches(ps)
- if err != nil {
- return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
- }
-
- errorHandler := &globalErrHandler{}
- buildState := &BuildState{}
-
- resourceSpec, err := resources.NewSpec(ps, fileCaches, buildState, logger, errorHandler, execHelper, cfg.OutputFormats, cfg.MediaTypes)
- if err != nil {
- return nil, err
- }
-
- contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs, execHelper)
- if err != nil {
- return nil, err
- }
-
- sp := source.NewSourceSpec(ps, nil, fs.Source)
-
- timeout := 30 * time.Second
- if cfg.Cfg.IsSet("timeout") {
- v := cfg.Cfg.Get("timeout")
- d, err := types.ToDurationE(v)
- if err == nil {
- timeout = d
- }
- }
- ignoreErrors := cast.ToStringSlice(cfg.Cfg.Get("ignoreErrors"))
- ignorableLogger := loggers.NewIgnorableLogger(logger, ignoreErrors...)
-
- logDistinct := helpers.NewDistinctLogger(logger)
-
- d = &Deps{
- Fs: fs,
- Log: ignorableLogger,
- LogDistinct: logDistinct,
- ExecHelper: execHelper,
- templateProvider: cfg.TemplateProvider,
- translationProvider: cfg.TranslationProvider,
- WithTemplate: cfg.WithTemplate,
- OverloadedTemplateFuncs: cfg.OverloadedTemplateFuncs,
- PathSpec: ps,
- ContentSpec: contentSpec,
- SourceSpec: sp,
- ResourceSpec: resourceSpec,
- Cfg: cfg.Language,
- Language: cfg.Language,
- Site: cfg.Site,
- FileCaches: fileCaches,
- BuildStartListeners: &Listeners{},
- BuildClosers: &Closers{},
- BuildState: buildState,
- Running: cfg.Running,
- Timeout: timeout,
- globalErrHandler: errorHandler,
- }
-
- if cfg.Cfg.GetBool("templateMetrics") {
- d.Metrics = metrics.NewProvider(cfg.Cfg.GetBool("templateMetricsHints"))
- }
-
- return d, nil
+ return d.tmplHandlers.TxtTmpl
}
func (d *Deps) Close() error {
return d.BuildClosers.Close()
}
-// ForLanguage creates a copy of the Deps with the language dependent
-// parts switched out.
-func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, error) {
- l := cfg.Language
- var err error
-
- d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, l, d.Log, d.BaseFs)
- if err != nil {
- return nil, err
- }
-
- d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs, d.ExecHelper)
- if err != nil {
- return nil, err
- }
-
- d.Site = cfg.Site
-
- // These are common for all sites, so reuse.
- // TODO(bep) clean up these inits.
- resourceCache := d.ResourceSpec.ResourceCache
- postBuildAssets := d.ResourceSpec.PostBuildAssets
- d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, d.ExecHelper, cfg.OutputFormats, cfg.MediaTypes)
- if err != nil {
- return nil, err
- }
- d.ResourceSpec.ResourceCache = resourceCache
- d.ResourceSpec.PostBuildAssets = postBuildAssets
-
- d.Cfg = l
- d.Language = l
-
- if onCreated != nil {
- if err = onCreated(&d); err != nil {
- return nil, err
- }
- }
-
- if err := d.translationProvider.Clone(&d); err != nil {
- return nil, err
- }
-
- if err := d.templateProvider.Clone(&d); err != nil {
- return nil, err
- }
-
- d.BuildStartListeners = &Listeners{}
-
- return &d, nil
-}
-
// DepsCfg contains configuration options that can be used to configure Hugo
// on a global level, i.e. logging etc.
// Nil values will be given default values.
@@ -422,45 +316,51 @@ type DepsCfg struct {
// The file systems to use
Fs *hugofs.Fs
- // The language to use.
- Language *langs.Language
-
// The Site in use
Site page.Site
- // The configuration to use.
- Cfg config.Provider
-
- // The media types configured.
- MediaTypes media.Types
-
- // The output formats configured.
- OutputFormats output.Formats
+ Configs *allconfig.Configs
// Template handling.
TemplateProvider ResourceProvider
- WithTemplate func(templ tpl.TemplateManager) error
- // Used in tests
- OverloadedTemplateFuncs map[string]any
// i18n handling.
TranslationProvider ResourceProvider
-
- // Whether we are in running (server) mode
- Running bool
}
-// BuildState are flags that may be turned on during a build.
+// BuildState are state used during a build.
type BuildState struct {
counter uint64
+
+ mu sync.Mutex // protects state below.
+
+ // A set of ilenames in /public that
+ // contains a post-processing prefix.
+ filenamesWithPostPrefix map[string]bool
}
-func (b *BuildState) Incr() int {
- return int(atomic.AddUint64(&b.counter, uint64(1)))
+func (b *BuildState) AddFilenameWithPostPrefix(filename string) {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ if b.filenamesWithPostPrefix == nil {
+ b.filenamesWithPostPrefix = make(map[string]bool)
+ }
+ b.filenamesWithPostPrefix[filename] = true
}
-func NewBuildState() BuildState {
- return BuildState{}
+func (b *BuildState) GetFilenamesWithPostPrefix() []string {
+ b.mu.Lock()
+ defer b.mu.Unlock()
+ var filenames []string
+ for filename := range b.filenamesWithPostPrefix {
+ filenames = append(filenames, filename)
+ }
+ sort.Strings(filenames)
+ return filenames
+}
+
+func (b *BuildState) Incr() int {
+ return int(atomic.AddUint64(&b.counter, uint64(1)))
}
type Closer interface {
diff --git a/deps/deps_test.go b/deps/deps_test.go
index d68276732..e92ed2327 100644
--- a/deps/deps_test.go
+++ b/deps/deps_test.go
@@ -11,17 +11,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package deps
+package deps_test
import (
"testing"
qt "github.com/frankban/quicktest"
+ "github.com/gohugoio/hugo/deps"
)
func TestBuildFlags(t *testing.T) {
c := qt.New(t)
- var bf BuildState
+ var bf deps.BuildState
bf.Incr()
bf.Incr()
bf.Incr()