diff options
author | Bjørn Erik Pedersen <[email protected]> | 2023-01-04 18:24:36 +0100 |
---|---|---|
committer | Bjørn Erik Pedersen <[email protected]> | 2023-05-16 18:01:29 +0200 |
commit | 241b21b0fd34d91fccb2ce69874110dceae6f926 (patch) | |
tree | d4e0118eac7e9c42f065815447a70805f8d6ad3e /deps | |
parent | 6aededf6b42011c3039f5f66487a89a8dd65e0e7 (diff) | |
download | hugo-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.go | 450 | ||||
-rw-r--r-- | deps/deps_test.go | 5 |
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() |