github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/deps/deps.go (about)

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