github.com/neohugo/neohugo@v0.123.8/deps/deps.go (about)

     1  package deps
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"path/filepath"
     8  	"sort"
     9  	"strings"
    10  	"sync"
    11  	"sync/atomic"
    12  
    13  	"github.com/bep/logg"
    14  	"github.com/neohugo/neohugo/cache/dynacache"
    15  	"github.com/neohugo/neohugo/cache/filecache"
    16  	"github.com/neohugo/neohugo/common/hexec"
    17  	"github.com/neohugo/neohugo/common/loggers"
    18  	"github.com/neohugo/neohugo/config"
    19  	"github.com/neohugo/neohugo/config/allconfig"
    20  	"github.com/neohugo/neohugo/config/security"
    21  	"github.com/neohugo/neohugo/helpers"
    22  	"github.com/neohugo/neohugo/hugofs"
    23  	"github.com/neohugo/neohugo/media"
    24  	"github.com/neohugo/neohugo/resources/page"
    25  	"github.com/neohugo/neohugo/resources/postpub"
    26  
    27  	"github.com/neohugo/neohugo/metrics"
    28  	"github.com/neohugo/neohugo/resources"
    29  	"github.com/neohugo/neohugo/source"
    30  	"github.com/neohugo/neohugo/tpl"
    31  	"github.com/spf13/afero"
    32  )
    33  
    34  // Deps holds dependencies used by many.
    35  // There will be normally only one instance of deps in play
    36  // at a given time, i.e. one per Site built.
    37  type Deps struct {
    38  	// The logger to use.
    39  	Log loggers.Logger `json:"-"`
    40  
    41  	ExecHelper *hexec.Exec
    42  
    43  	// The templates to use. This will usually implement the full tpl.TemplateManager.
    44  	tmplHandlers *tpl.TemplateHandlers
    45  
    46  	// The file systems to use.
    47  	Fs *hugofs.Fs `json:"-"`
    48  
    49  	// The PathSpec to use
    50  	*helpers.PathSpec `json:"-"`
    51  
    52  	// The ContentSpec to use
    53  	*helpers.ContentSpec `json:"-"`
    54  
    55  	// The SourceSpec to use
    56  	SourceSpec *source.SourceSpec `json:"-"`
    57  
    58  	// The Resource Spec to use
    59  	ResourceSpec *resources.Spec
    60  
    61  	// The configuration to use
    62  	Conf config.AllProvider `json:"-"`
    63  
    64  	// The memory cache to use.
    65  	MemCache *dynacache.Cache
    66  
    67  	// The translation func to use
    68  	Translate func(ctx context.Context, translationID string, templateData any) string `json:"-"`
    69  
    70  	// The site building.
    71  	Site page.Site
    72  
    73  	TemplateProvider ResourceProvider
    74  	// Used in tests
    75  	OverloadedTemplateFuncs map[string]any
    76  
    77  	TranslationProvider ResourceProvider
    78  
    79  	Metrics metrics.Provider
    80  
    81  	// BuildStartListeners will be notified before a build starts.
    82  	BuildStartListeners *Listeners
    83  
    84  	// BuildEndListeners will be notified after a build finishes.
    85  	BuildEndListeners *Listeners
    86  
    87  	// Resources that gets closed when the build is done or the server shuts down.
    88  	BuildClosers *Closers
    89  
    90  	// This is common/global for all sites.
    91  	BuildState *BuildState
    92  
    93  	*globalErrHandler
    94  }
    95  
    96  func (d Deps) Clone(s page.Site, conf config.AllProvider) (*Deps, error) {
    97  	d.Conf = conf
    98  	d.Site = s
    99  	d.ExecHelper = nil
   100  	d.ContentSpec = nil
   101  
   102  	if err := d.Init(); err != nil {
   103  		return nil, err
   104  	}
   105  
   106  	return &d, nil
   107  }
   108  
   109  func (d *Deps) SetTempl(t *tpl.TemplateHandlers) {
   110  	d.tmplHandlers = t
   111  }
   112  
   113  func (d *Deps) Init() error {
   114  	if d.Conf == nil {
   115  		panic("conf is nil")
   116  	}
   117  
   118  	if d.Fs == nil {
   119  		// For tests.
   120  		d.Fs = hugofs.NewFrom(afero.NewMemMapFs(), d.Conf.BaseConfig())
   121  	}
   122  
   123  	if d.Log == nil {
   124  		d.Log = loggers.NewDefault()
   125  	}
   126  
   127  	if d.globalErrHandler == nil {
   128  		d.globalErrHandler = &globalErrHandler{
   129  			logger: d.Log,
   130  		}
   131  	}
   132  
   133  	if d.BuildState == nil {
   134  		d.BuildState = &BuildState{}
   135  	}
   136  
   137  	if d.BuildStartListeners == nil {
   138  		d.BuildStartListeners = &Listeners{}
   139  	}
   140  
   141  	if d.BuildEndListeners == nil {
   142  		d.BuildEndListeners = &Listeners{}
   143  	}
   144  
   145  	if d.BuildClosers == nil {
   146  		d.BuildClosers = &Closers{}
   147  	}
   148  
   149  	if d.Metrics == nil && d.Conf.TemplateMetrics() {
   150  		d.Metrics = metrics.NewProvider(d.Conf.TemplateMetricsHints())
   151  	}
   152  
   153  	if d.ExecHelper == nil {
   154  		d.ExecHelper = hexec.New(d.Conf.GetConfigSection("security").(security.Config))
   155  	}
   156  
   157  	if d.MemCache == nil {
   158  		d.MemCache = dynacache.New(dynacache.Options{Running: d.Conf.Running(), Log: d.Log})
   159  	}
   160  
   161  	if d.PathSpec == nil {
   162  		hashBytesReceiverFunc := func(name string, match bool) {
   163  			if !match {
   164  				return
   165  			}
   166  			d.BuildState.AddFilenameWithPostPrefix(name)
   167  		}
   168  
   169  		// Skip binary files.
   170  		mediaTypes := d.Conf.GetConfigSection("mediaTypes").(media.Types)
   171  		hashBytesSHouldCheck := func(name string) bool {
   172  			ext := strings.TrimPrefix(filepath.Ext(name), ".")
   173  			return mediaTypes.IsTextSuffix(ext)
   174  		}
   175  		d.Fs.PublishDir = hugofs.NewHasBytesReceiver(d.Fs.PublishDir, hashBytesSHouldCheck, hashBytesReceiverFunc, []byte(postpub.PostProcessPrefix))
   176  		pathSpec, err := helpers.NewPathSpec(d.Fs, d.Conf, d.Log)
   177  		if err != nil {
   178  			return err
   179  		}
   180  		d.PathSpec = pathSpec
   181  	} else {
   182  		var err error
   183  		d.PathSpec, err = helpers.NewPathSpecWithBaseBaseFsProvided(d.Fs, d.Conf, d.Log, d.PathSpec.BaseFs)
   184  		if err != nil {
   185  			return err
   186  		}
   187  	}
   188  
   189  	if d.ContentSpec == nil {
   190  		contentSpec, err := helpers.NewContentSpec(d.Conf, d.Log, d.Content.Fs, d.ExecHelper)
   191  		if err != nil {
   192  			return err
   193  		}
   194  		d.ContentSpec = contentSpec
   195  	}
   196  
   197  	if d.SourceSpec == nil {
   198  		d.SourceSpec = source.NewSourceSpec(d.PathSpec, nil, d.Fs.Source)
   199  	}
   200  
   201  	var common *resources.SpecCommon
   202  	if d.ResourceSpec != nil {
   203  		common = d.ResourceSpec.SpecCommon
   204  	}
   205  
   206  	fileCaches, err := filecache.NewCaches(d.PathSpec)
   207  	if err != nil {
   208  		return fmt.Errorf("failed to create file caches from configuration: %w", err)
   209  	}
   210  
   211  	resourceSpec, err := resources.NewSpec(d.PathSpec, common, fileCaches, d.MemCache, d.BuildState, d.Log, d, d.ExecHelper)
   212  	if err != nil {
   213  		return fmt.Errorf("failed to create resource spec: %w", err)
   214  	}
   215  	d.ResourceSpec = resourceSpec
   216  
   217  	return nil
   218  }
   219  
   220  func (d *Deps) Compile(prototype *Deps) error {
   221  	var err error
   222  	if prototype == nil {
   223  		if err = d.TemplateProvider.NewResource(d); err != nil {
   224  			return err
   225  		}
   226  		if err = d.TranslationProvider.NewResource(d); err != nil {
   227  			return err
   228  		}
   229  		return nil
   230  	}
   231  
   232  	if err = d.TemplateProvider.CloneResource(d, prototype); err != nil {
   233  		return err
   234  	}
   235  
   236  	if err = d.TranslationProvider.CloneResource(d, prototype); err != nil {
   237  		return err
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  type globalErrHandler struct {
   244  	logger loggers.Logger
   245  
   246  	// Channel for some "hard to get to" build errors
   247  	buildErrors chan error
   248  	// Used to signal that the build is done.
   249  	quit chan struct{}
   250  }
   251  
   252  // SendError sends the error on a channel to be handled later.
   253  // This can be used in situations where returning and aborting the current
   254  // operation isn't practical.
   255  func (e *globalErrHandler) SendError(err error) {
   256  	if e.buildErrors != nil {
   257  		select {
   258  		case <-e.quit:
   259  		case e.buildErrors <- err:
   260  		default:
   261  		}
   262  		return
   263  	}
   264  	e.logger.Errorln(err)
   265  }
   266  
   267  func (e *globalErrHandler) StartErrorCollector() chan error {
   268  	e.quit = make(chan struct{})
   269  	e.buildErrors = make(chan error, 10)
   270  	return e.buildErrors
   271  }
   272  
   273  func (e *globalErrHandler) StopErrorCollector() {
   274  	if e.buildErrors != nil {
   275  		close(e.quit)
   276  		close(e.buildErrors)
   277  	}
   278  }
   279  
   280  // Listeners represents an event listener.
   281  type Listeners struct {
   282  	sync.Mutex
   283  
   284  	// A list of funcs to be notified about an event.
   285  	listeners []func()
   286  }
   287  
   288  // Add adds a function to a Listeners instance.
   289  func (b *Listeners) Add(f func()) {
   290  	if b == nil {
   291  		return
   292  	}
   293  	b.Lock()
   294  	defer b.Unlock()
   295  	b.listeners = append(b.listeners, f)
   296  }
   297  
   298  // Notify executes all listener functions.
   299  func (b *Listeners) Notify() {
   300  	b.Lock()
   301  	defer b.Unlock()
   302  	for _, notify := range b.listeners {
   303  		notify()
   304  	}
   305  }
   306  
   307  // ResourceProvider is used to create and refresh, and clone resources needed.
   308  type ResourceProvider interface {
   309  	NewResource(dst *Deps) error
   310  	CloneResource(dst, src *Deps) error
   311  }
   312  
   313  func (d *Deps) Tmpl() tpl.TemplateHandler {
   314  	return d.tmplHandlers.Tmpl
   315  }
   316  
   317  func (d *Deps) TextTmpl() tpl.TemplateParseFinder {
   318  	return d.tmplHandlers.TxtTmpl
   319  }
   320  
   321  func (d *Deps) Close() error {
   322  	if d.MemCache != nil {
   323  		d.MemCache.Stop()
   324  	}
   325  	return d.BuildClosers.Close()
   326  }
   327  
   328  // DepsCfg contains configuration options that can be used to configure Hugo
   329  // on a global level, i.e. logging etc.
   330  // Nil values will be given default values.
   331  type DepsCfg struct {
   332  	// The logger to use. Only set in some tests.
   333  	// TODO(bep) get rid of this.
   334  	TestLogger loggers.Logger
   335  
   336  	// The logging level to use.
   337  	LogLevel logg.Level
   338  
   339  	// Where to write the logs.
   340  	// Currently we typically write everything to stdout.
   341  	LogOut io.Writer
   342  
   343  	// The file systems to use
   344  	Fs *hugofs.Fs
   345  
   346  	// The Site in use
   347  	Site page.Site
   348  
   349  	Configs *allconfig.Configs
   350  
   351  	// Template handling.
   352  	TemplateProvider ResourceProvider
   353  
   354  	// i18n handling.
   355  	TranslationProvider ResourceProvider
   356  }
   357  
   358  // BuildState are state used during a build.
   359  type BuildState struct {
   360  	counter uint64
   361  
   362  	mu sync.Mutex // protects state below.
   363  
   364  	// A set of ilenames in /public that
   365  	// contains a post-processing prefix.
   366  	filenamesWithPostPrefix map[string]bool
   367  }
   368  
   369  func (b *BuildState) AddFilenameWithPostPrefix(filename string) {
   370  	b.mu.Lock()
   371  	defer b.mu.Unlock()
   372  	if b.filenamesWithPostPrefix == nil {
   373  		b.filenamesWithPostPrefix = make(map[string]bool)
   374  	}
   375  	b.filenamesWithPostPrefix[filename] = true
   376  }
   377  
   378  func (b *BuildState) GetFilenamesWithPostPrefix() []string {
   379  	b.mu.Lock()
   380  	defer b.mu.Unlock()
   381  	var filenames []string
   382  	for filename := range b.filenamesWithPostPrefix {
   383  		filenames = append(filenames, filename)
   384  	}
   385  	sort.Strings(filenames)
   386  	return filenames
   387  }
   388  
   389  func (b *BuildState) Incr() int {
   390  	return int(atomic.AddUint64(&b.counter, uint64(1)))
   391  }
   392  
   393  type Closer interface {
   394  	Close() error
   395  }
   396  
   397  type Closers struct {
   398  	mu sync.Mutex
   399  	cs []Closer
   400  }
   401  
   402  func (cs *Closers) Add(c Closer) {
   403  	cs.mu.Lock()
   404  	defer cs.mu.Unlock()
   405  	cs.cs = append(cs.cs, c)
   406  }
   407  
   408  func (cs *Closers) Close() error {
   409  	cs.mu.Lock()
   410  	defer cs.mu.Unlock()
   411  	for _, c := range cs.cs {
   412  		c.Close()
   413  	}
   414  
   415  	cs.cs = cs.cs[:0]
   416  
   417  	return nil
   418  }