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 }