hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

deps.go (10433B)

    1 package deps
    2 
    3 import (
    4 	"fmt"
    5 	"sync"
    6 	"sync/atomic"
    7 	"time"
    8 
    9 	"github.com/gohugoio/hugo/cache/filecache"
   10 	"github.com/gohugoio/hugo/common/hexec"
   11 	"github.com/gohugoio/hugo/common/loggers"
   12 	"github.com/gohugoio/hugo/config"
   13 	"github.com/gohugoio/hugo/config/security"
   14 	"github.com/gohugoio/hugo/helpers"
   15 	"github.com/gohugoio/hugo/hugofs"
   16 	"github.com/gohugoio/hugo/langs"
   17 	"github.com/gohugoio/hugo/media"
   18 	"github.com/gohugoio/hugo/resources/page"
   19 
   20 	"github.com/gohugoio/hugo/metrics"
   21 	"github.com/gohugoio/hugo/output"
   22 	"github.com/gohugoio/hugo/resources"
   23 	"github.com/gohugoio/hugo/source"
   24 	"github.com/gohugoio/hugo/tpl"
   25 	"github.com/spf13/cast"
   26 	jww "github.com/spf13/jwalterweatherman"
   27 )
   28 
   29 // Deps holds dependencies used by many.
   30 // There will be normally only one instance of deps in play
   31 // at a given time, i.e. one per Site built.
   32 type Deps struct {
   33 
   34 	// The logger to use.
   35 	Log loggers.Logger `json:"-"`
   36 
   37 	// Used to log errors that may repeat itself many times.
   38 	LogDistinct loggers.Logger
   39 
   40 	ExecHelper *hexec.Exec
   41 
   42 	// The templates to use. This will usually implement the full tpl.TemplateManager.
   43 	tmpl tpl.TemplateHandler
   44 
   45 	// We use this to parse and execute ad-hoc text templates.
   46 	textTmpl tpl.TemplateParseFinder
   47 
   48 	// The file systems to use.
   49 	Fs *hugofs.Fs `json:"-"`
   50 
   51 	// The PathSpec to use
   52 	*helpers.PathSpec `json:"-"`
   53 
   54 	// The ContentSpec to use
   55 	*helpers.ContentSpec `json:"-"`
   56 
   57 	// The SourceSpec to use
   58 	SourceSpec *source.SourceSpec `json:"-"`
   59 
   60 	// The Resource Spec to use
   61 	ResourceSpec *resources.Spec
   62 
   63 	// The configuration to use
   64 	Cfg config.Provider `json:"-"`
   65 
   66 	// The file cache to use.
   67 	FileCaches filecache.Caches
   68 
   69 	// The translation func to use
   70 	Translate func(translationID string, templateData any) string `json:"-"`
   71 
   72 	// The language in use. TODO(bep) consolidate with site
   73 	Language *langs.Language
   74 
   75 	// The site building.
   76 	Site page.Site
   77 
   78 	// All the output formats available for the current site.
   79 	OutputFormatsConfig output.Formats
   80 
   81 	templateProvider ResourceProvider
   82 	WithTemplate     func(templ tpl.TemplateManager) error `json:"-"`
   83 
   84 	// Used in tests
   85 	OverloadedTemplateFuncs map[string]any
   86 
   87 	translationProvider ResourceProvider
   88 
   89 	Metrics metrics.Provider
   90 
   91 	// Timeout is configurable in site config.
   92 	Timeout time.Duration
   93 
   94 	// BuildStartListeners will be notified before a build starts.
   95 	BuildStartListeners *Listeners
   96 
   97 	// Resources that gets closed when the build is done or the server shuts down.
   98 	BuildClosers *Closers
   99 
  100 	// Atomic values set during a build.
  101 	// This is common/global for all sites.
  102 	BuildState *BuildState
  103 
  104 	// Whether we are in running (server) mode
  105 	Running bool
  106 
  107 	*globalErrHandler
  108 }
  109 
  110 type globalErrHandler struct {
  111 	// Channel for some "hard to get to" build errors
  112 	buildErrors chan error
  113 }
  114 
  115 // SendErr sends the error on a channel to be handled later.
  116 // This can be used in situations where returning and aborting the current
  117 // operation isn't practical.
  118 func (e *globalErrHandler) SendError(err error) {
  119 	if e.buildErrors != nil {
  120 		select {
  121 		case e.buildErrors <- err:
  122 		default:
  123 		}
  124 		return
  125 	}
  126 
  127 	jww.ERROR.Println(err)
  128 }
  129 
  130 func (e *globalErrHandler) StartErrorCollector() chan error {
  131 	e.buildErrors = make(chan error, 10)
  132 	return e.buildErrors
  133 }
  134 
  135 // Listeners represents an event listener.
  136 type Listeners struct {
  137 	sync.Mutex
  138 
  139 	// A list of funcs to be notified about an event.
  140 	listeners []func()
  141 }
  142 
  143 // Add adds a function to a Listeners instance.
  144 func (b *Listeners) Add(f func()) {
  145 	if b == nil {
  146 		return
  147 	}
  148 	b.Lock()
  149 	defer b.Unlock()
  150 	b.listeners = append(b.listeners, f)
  151 }
  152 
  153 // Notify executes all listener functions.
  154 func (b *Listeners) Notify() {
  155 	b.Lock()
  156 	defer b.Unlock()
  157 	for _, notify := range b.listeners {
  158 		notify()
  159 	}
  160 }
  161 
  162 // ResourceProvider is used to create and refresh, and clone resources needed.
  163 type ResourceProvider interface {
  164 	Update(deps *Deps) error
  165 	Clone(deps *Deps) error
  166 }
  167 
  168 func (d *Deps) Tmpl() tpl.TemplateHandler {
  169 	return d.tmpl
  170 }
  171 
  172 func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
  173 	return d.textTmpl
  174 }
  175 
  176 func (d *Deps) SetTmpl(tmpl tpl.TemplateHandler) {
  177 	d.tmpl = tmpl
  178 }
  179 
  180 func (d *Deps) SetTextTmpl(tmpl tpl.TemplateParseFinder) {
  181 	d.textTmpl = tmpl
  182 }
  183 
  184 // LoadResources loads translations and templates.
  185 func (d *Deps) LoadResources() error {
  186 	// Note that the translations need to be loaded before the templates.
  187 	if err := d.translationProvider.Update(d); err != nil {
  188 		return fmt.Errorf("loading translations: %w", err)
  189 	}
  190 
  191 	if err := d.templateProvider.Update(d); err != nil {
  192 		return fmt.Errorf("loading templates: %w", err)
  193 	}
  194 
  195 	return nil
  196 }
  197 
  198 // New initializes a Dep struct.
  199 // Defaults are set for nil values,
  200 // but TemplateProvider, TranslationProvider and Language are always required.
  201 func New(cfg DepsCfg) (*Deps, error) {
  202 	var (
  203 		logger = cfg.Logger
  204 		fs     = cfg.Fs
  205 	)
  206 
  207 	if cfg.TemplateProvider == nil {
  208 		panic("Must have a TemplateProvider")
  209 	}
  210 
  211 	if cfg.TranslationProvider == nil {
  212 		panic("Must have a TranslationProvider")
  213 	}
  214 
  215 	if cfg.Language == nil {
  216 		panic("Must have a Language")
  217 	}
  218 
  219 	if logger == nil {
  220 		logger = loggers.NewErrorLogger()
  221 	}
  222 
  223 	if fs == nil {
  224 		// Default to the production file system.
  225 		fs = hugofs.NewDefault(cfg.Language)
  226 	}
  227 
  228 	if cfg.MediaTypes == nil {
  229 		cfg.MediaTypes = media.DefaultTypes
  230 	}
  231 
  232 	if cfg.OutputFormats == nil {
  233 		cfg.OutputFormats = output.DefaultFormats
  234 	}
  235 
  236 	securityConfig, err := security.DecodeConfig(cfg.Cfg)
  237 	if err != nil {
  238 		return nil, fmt.Errorf("failed to create security config from configuration: %w", err)
  239 	}
  240 	execHelper := hexec.New(securityConfig)
  241 
  242 	ps, err := helpers.NewPathSpec(fs, cfg.Language, logger)
  243 	if err != nil {
  244 		return nil, fmt.Errorf("create PathSpec: %w", err)
  245 	}
  246 
  247 	fileCaches, err := filecache.NewCaches(ps)
  248 	if err != nil {
  249 		return nil, fmt.Errorf("failed to create file caches from configuration: %w", err)
  250 	}
  251 
  252 	errorHandler := &globalErrHandler{}
  253 	buildState := &BuildState{}
  254 
  255 	resourceSpec, err := resources.NewSpec(ps, fileCaches, buildState, logger, errorHandler, execHelper, cfg.OutputFormats, cfg.MediaTypes)
  256 	if err != nil {
  257 		return nil, err
  258 	}
  259 
  260 	contentSpec, err := helpers.NewContentSpec(cfg.Language, logger, ps.BaseFs.Content.Fs, execHelper)
  261 	if err != nil {
  262 		return nil, err
  263 	}
  264 
  265 	sp := source.NewSourceSpec(ps, nil, fs.Source)
  266 
  267 	timeoutms := cfg.Language.GetInt("timeout")
  268 	if timeoutms <= 0 {
  269 		timeoutms = 3000
  270 	}
  271 
  272 	ignoreErrors := cast.ToStringSlice(cfg.Cfg.Get("ignoreErrors"))
  273 	ignorableLogger := loggers.NewIgnorableLogger(logger, ignoreErrors...)
  274 
  275 	logDistinct := helpers.NewDistinctLogger(logger)
  276 
  277 	d := &Deps{
  278 		Fs:                      fs,
  279 		Log:                     ignorableLogger,
  280 		LogDistinct:             logDistinct,
  281 		ExecHelper:              execHelper,
  282 		templateProvider:        cfg.TemplateProvider,
  283 		translationProvider:     cfg.TranslationProvider,
  284 		WithTemplate:            cfg.WithTemplate,
  285 		OverloadedTemplateFuncs: cfg.OverloadedTemplateFuncs,
  286 		PathSpec:                ps,
  287 		ContentSpec:             contentSpec,
  288 		SourceSpec:              sp,
  289 		ResourceSpec:            resourceSpec,
  290 		Cfg:                     cfg.Language,
  291 		Language:                cfg.Language,
  292 		Site:                    cfg.Site,
  293 		FileCaches:              fileCaches,
  294 		BuildStartListeners:     &Listeners{},
  295 		BuildClosers:            &Closers{},
  296 		BuildState:              buildState,
  297 		Running:                 cfg.Running,
  298 		Timeout:                 time.Duration(timeoutms) * time.Millisecond,
  299 		globalErrHandler:        errorHandler,
  300 	}
  301 
  302 	if cfg.Cfg.GetBool("templateMetrics") {
  303 		d.Metrics = metrics.NewProvider(cfg.Cfg.GetBool("templateMetricsHints"))
  304 	}
  305 
  306 	return d, nil
  307 }
  308 
  309 func (d *Deps) Close() error {
  310 	return d.BuildClosers.Close()
  311 }
  312 
  313 // ForLanguage creates a copy of the Deps with the language dependent
  314 // parts switched out.
  315 func (d Deps) ForLanguage(cfg DepsCfg, onCreated func(d *Deps) error) (*Deps, error) {
  316 	l := cfg.Language
  317 	var err error
  318 
  319 	d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, l, d.Log, d.BaseFs)
  320 	if err != nil {
  321 		return nil, err
  322 	}
  323 
  324 	d.ContentSpec, err = helpers.NewContentSpec(l, d.Log, d.BaseFs.Content.Fs, d.ExecHelper)
  325 	if err != nil {
  326 		return nil, err
  327 	}
  328 
  329 	d.Site = cfg.Site
  330 
  331 	// These are common for all sites, so reuse.
  332 	// TODO(bep) clean up these inits.
  333 	resourceCache := d.ResourceSpec.ResourceCache
  334 	postBuildAssets := d.ResourceSpec.PostBuildAssets
  335 	d.ResourceSpec, err = resources.NewSpec(d.PathSpec, d.ResourceSpec.FileCaches, d.BuildState, d.Log, d.globalErrHandler, d.ExecHelper, cfg.OutputFormats, cfg.MediaTypes)
  336 	if err != nil {
  337 		return nil, err
  338 	}
  339 	d.ResourceSpec.ResourceCache = resourceCache
  340 	d.ResourceSpec.PostBuildAssets = postBuildAssets
  341 
  342 	d.Cfg = l
  343 	d.Language = l
  344 
  345 	if onCreated != nil {
  346 		if err = onCreated(&d); err != nil {
  347 			return nil, err
  348 		}
  349 	}
  350 
  351 	if err := d.translationProvider.Clone(&d); err != nil {
  352 		return nil, err
  353 	}
  354 
  355 	if err := d.templateProvider.Clone(&d); err != nil {
  356 		return nil, err
  357 	}
  358 
  359 	d.BuildStartListeners = &Listeners{}
  360 
  361 	return &d, nil
  362 }
  363 
  364 // DepsCfg contains configuration options that can be used to configure Hugo
  365 // on a global level, i.e. logging etc.
  366 // Nil values will be given default values.
  367 type DepsCfg struct {
  368 
  369 	// The Logger to use.
  370 	Logger loggers.Logger
  371 
  372 	// The file systems to use
  373 	Fs *hugofs.Fs
  374 
  375 	// The language to use.
  376 	Language *langs.Language
  377 
  378 	// The Site in use
  379 	Site page.Site
  380 
  381 	// The configuration to use.
  382 	Cfg config.Provider
  383 
  384 	// The media types configured.
  385 	MediaTypes media.Types
  386 
  387 	// The output formats configured.
  388 	OutputFormats output.Formats
  389 
  390 	// Template handling.
  391 	TemplateProvider ResourceProvider
  392 	WithTemplate     func(templ tpl.TemplateManager) error
  393 	// Used in tests
  394 	OverloadedTemplateFuncs map[string]any
  395 
  396 	// i18n handling.
  397 	TranslationProvider ResourceProvider
  398 
  399 	// Whether we are in running (server) mode
  400 	Running bool
  401 }
  402 
  403 // BuildState are flags that may be turned on during a build.
  404 type BuildState struct {
  405 	counter uint64
  406 }
  407 
  408 func (b *BuildState) Incr() int {
  409 	return int(atomic.AddUint64(&b.counter, uint64(1)))
  410 }
  411 
  412 func NewBuildState() BuildState {
  413 	return BuildState{}
  414 }
  415 
  416 type Closer interface {
  417 	Close() error
  418 }
  419 
  420 type Closers struct {
  421 	mu sync.Mutex
  422 	cs []Closer
  423 }
  424 
  425 func (cs *Closers) Add(c Closer) {
  426 	cs.mu.Lock()
  427 	defer cs.mu.Unlock()
  428 	cs.cs = append(cs.cs, c)
  429 }
  430 
  431 func (cs *Closers) Close() error {
  432 	cs.mu.Lock()
  433 	defer cs.mu.Unlock()
  434 	for _, c := range cs.cs {
  435 		c.Close()
  436 	}
  437 
  438 	cs.cs = cs.cs[:0]
  439 
  440 	return nil
  441 }