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 }