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 }