github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/config/commonConfig.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package config 15 16 import ( 17 "fmt" 18 "regexp" 19 "sort" 20 "strings" 21 22 "github.com/bep/logg" 23 "github.com/gobwas/glob" 24 "github.com/gohugoio/hugo/common/loggers" 25 "github.com/gohugoio/hugo/common/types" 26 27 "github.com/gohugoio/hugo/common/herrors" 28 "github.com/mitchellh/mapstructure" 29 "github.com/spf13/cast" 30 ) 31 32 type BaseConfig struct { 33 WorkingDir string 34 CacheDir string 35 ThemesDir string 36 PublishDir string 37 } 38 39 type CommonDirs struct { 40 // The directory where Hugo will look for themes. 41 ThemesDir string 42 43 // Where to put the generated files. 44 PublishDir string 45 46 // The directory to put the generated resources files. This directory should in most situations be considered temporary 47 // and not be committed to version control. But there may be cached content in here that you want to keep, 48 // e.g. resources/_gen/images for performance reasons or CSS built from SASS when your CI server doesn't have the full setup. 49 ResourceDir string 50 51 // The project root directory. 52 WorkingDir string 53 54 // The root directory for all cache files. 55 CacheDir string 56 57 // The content source directory. 58 // Deprecated: Use module mounts. 59 ContentDir string 60 // Deprecated: Use module mounts. 61 // The data source directory. 62 DataDir string 63 // Deprecated: Use module mounts. 64 // The layout source directory. 65 LayoutDir string 66 // Deprecated: Use module mounts. 67 // The i18n source directory. 68 I18nDir string 69 // Deprecated: Use module mounts. 70 // The archetypes source directory. 71 ArcheTypeDir string 72 // Deprecated: Use module mounts. 73 // The assets source directory. 74 AssetDir string 75 } 76 77 type LoadConfigResult struct { 78 Cfg Provider 79 ConfigFiles []string 80 BaseConfig BaseConfig 81 } 82 83 var defaultBuild = BuildConfig{ 84 UseResourceCacheWhen: "fallback", 85 BuildStats: BuildStats{}, 86 87 CacheBusters: []CacheBuster{ 88 { 89 Source: `assets/.*\.(js|ts|jsx|tsx)`, 90 Target: `(js|scripts|javascript)`, 91 }, 92 { 93 Source: `assets/.*\.(css|sass|scss)$`, 94 Target: cssTargetCachebusterRe, 95 }, 96 { 97 Source: `(postcss|tailwind)\.config\.js`, 98 Target: cssTargetCachebusterRe, 99 }, 100 // This is deliberately coarse grained; it will cache bust resources with "json" in the cache key when js files changes, which is good. 101 { 102 Source: `assets/.*\.(.*)$`, 103 Target: `$1`, 104 }, 105 }, 106 } 107 108 // BuildConfig holds some build related configuration. 109 type BuildConfig struct { 110 UseResourceCacheWhen string // never, fallback, always. Default is fallback 111 112 // When enabled, will collect and write a hugo_stats.json with some build 113 // related aggregated data (e.g. CSS class names). 114 // Note that this was a bool <= v0.115.0. 115 BuildStats BuildStats 116 117 // Can be used to toggle off writing of the IntelliSense /assets/jsconfig.js 118 // file. 119 NoJSConfigInAssets bool 120 121 // Can used to control how the resource cache gets evicted on rebuilds. 122 CacheBusters []CacheBuster 123 } 124 125 // BuildStats configures if and what to write to the hugo_stats.json file. 126 type BuildStats struct { 127 Enable bool 128 DisableTags bool 129 DisableClasses bool 130 DisableIDs bool 131 } 132 133 func (w BuildStats) Enabled() bool { 134 if !w.Enable { 135 return false 136 } 137 return !w.DisableTags || !w.DisableClasses || !w.DisableIDs 138 } 139 140 func (b BuildConfig) clone() BuildConfig { 141 b.CacheBusters = append([]CacheBuster{}, b.CacheBusters...) 142 return b 143 } 144 145 func (b BuildConfig) UseResourceCache(err error) bool { 146 if b.UseResourceCacheWhen == "never" { 147 return false 148 } 149 150 if b.UseResourceCacheWhen == "fallback" { 151 return herrors.IsFeatureNotAvailableError(err) 152 } 153 154 return true 155 } 156 157 // MatchCacheBuster returns the cache buster for the given path p, nil if none. 158 func (s BuildConfig) MatchCacheBuster(logger loggers.Logger, p string) (func(string) bool, error) { 159 var matchers []func(string) bool 160 for _, cb := range s.CacheBusters { 161 if matcher := cb.compiledSource(p); matcher != nil { 162 matchers = append(matchers, matcher) 163 } 164 } 165 if len(matchers) > 0 { 166 return (func(cacheKey string) bool { 167 for _, m := range matchers { 168 if m(cacheKey) { 169 return true 170 } 171 } 172 return false 173 }), nil 174 } 175 return nil, nil 176 } 177 178 func (b *BuildConfig) CompileConfig(logger loggers.Logger) error { 179 for i, cb := range b.CacheBusters { 180 if err := cb.CompileConfig(logger); err != nil { 181 return fmt.Errorf("failed to compile cache buster %q: %w", cb.Source, err) 182 } 183 b.CacheBusters[i] = cb 184 } 185 return nil 186 } 187 188 func DecodeBuildConfig(cfg Provider) BuildConfig { 189 m := cfg.GetStringMap("build") 190 191 b := defaultBuild.clone() 192 if m == nil { 193 return b 194 } 195 196 // writeStats was a bool <= v0.115.0. 197 if writeStats, ok := m["writestats"]; ok { 198 if bb, ok := writeStats.(bool); ok { 199 m["buildstats"] = BuildStats{Enable: bb} 200 } 201 } 202 203 err := mapstructure.WeakDecode(m, &b) 204 if err != nil { 205 return b 206 } 207 208 b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen) 209 when := b.UseResourceCacheWhen 210 if when != "never" && when != "always" && when != "fallback" { 211 b.UseResourceCacheWhen = "fallback" 212 } 213 214 return b 215 } 216 217 // SitemapConfig configures the sitemap to be generated. 218 type SitemapConfig struct { 219 // The page change frequency. 220 ChangeFreq string 221 // The priority of the page. 222 Priority float64 223 // The sitemap filename. 224 Filename string 225 } 226 227 func DecodeSitemap(prototype SitemapConfig, input map[string]any) (SitemapConfig, error) { 228 err := mapstructure.WeakDecode(input, &prototype) 229 return prototype, err 230 } 231 232 // Config for the dev server. 233 type Server struct { 234 Headers []Headers 235 Redirects []Redirect 236 237 compiledHeaders []glob.Glob 238 compiledRedirects []glob.Glob 239 } 240 241 func (s *Server) CompileConfig(logger loggers.Logger) error { 242 if s.compiledHeaders != nil { 243 return nil 244 } 245 for _, h := range s.Headers { 246 s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For)) 247 } 248 for _, r := range s.Redirects { 249 s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From)) 250 } 251 252 return nil 253 } 254 255 func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr { 256 if s.compiledHeaders == nil { 257 return nil 258 } 259 260 var matches []types.KeyValueStr 261 262 for i, g := range s.compiledHeaders { 263 if g.Match(pattern) { 264 h := s.Headers[i] 265 for k, v := range h.Values { 266 matches = append(matches, types.KeyValueStr{Key: k, Value: cast.ToString(v)}) 267 } 268 } 269 } 270 271 sort.Slice(matches, func(i, j int) bool { 272 return matches[i].Key < matches[j].Key 273 }) 274 275 return matches 276 } 277 278 func (s *Server) MatchRedirect(pattern string) Redirect { 279 if s.compiledRedirects == nil { 280 return Redirect{} 281 } 282 283 pattern = strings.TrimSuffix(pattern, "index.html") 284 285 for i, g := range s.compiledRedirects { 286 redir := s.Redirects[i] 287 288 // No redirect to self. 289 if redir.To == pattern { 290 return Redirect{} 291 } 292 293 if g.Match(pattern) { 294 return redir 295 } 296 } 297 298 return Redirect{} 299 } 300 301 type Headers struct { 302 For string 303 Values map[string]any 304 } 305 306 type Redirect struct { 307 From string 308 To string 309 310 // HTTP status code to use for the redirect. 311 // A status code of 200 will trigger a URL rewrite. 312 Status int 313 314 // Forcode redirect, even if original request path exists. 315 Force bool 316 } 317 318 // CacheBuster configures cache busting for assets. 319 type CacheBuster struct { 320 // Trigger for files matching this regexp. 321 Source string 322 323 // Cache bust targets matching this regexp. 324 // This regexp can contain group matches (e.g. $1) from the source regexp. 325 Target string 326 327 compiledSource func(string) func(string) bool 328 } 329 330 func (c *CacheBuster) CompileConfig(logger loggers.Logger) error { 331 if c.compiledSource != nil { 332 return nil 333 } 334 335 source := c.Source 336 sourceRe, err := regexp.Compile(source) 337 if err != nil { 338 return fmt.Errorf("failed to compile cache buster source %q: %w", c.Source, err) 339 } 340 target := c.Target 341 var compileErr error 342 debugl := logger.Logger().WithLevel(logg.LevelDebug).WithField(loggers.FieldNameCmd, "cachebuster") 343 344 c.compiledSource = func(s string) func(string) bool { 345 m := sourceRe.FindStringSubmatch(s) 346 matchString := "no match" 347 match := m != nil 348 if match { 349 matchString = "match!" 350 } 351 debugl.Logf("Matching %q with source %q: %s", s, source, matchString) 352 if !match { 353 return nil 354 } 355 groups := m[1:] 356 currentTarget := target 357 // Replace $1, $2 etc. in target. 358 for i, g := range groups { 359 currentTarget = strings.ReplaceAll(target, fmt.Sprintf("$%d", i+1), g) 360 } 361 targetRe, err := regexp.Compile(currentTarget) 362 if err != nil { 363 compileErr = fmt.Errorf("failed to compile cache buster target %q: %w", currentTarget, err) 364 return nil 365 } 366 return func(ss string) bool { 367 match = targetRe.MatchString(ss) 368 matchString := "no match" 369 if match { 370 matchString = "match!" 371 } 372 logger.Debugf("Matching %q with target %q: %s", ss, currentTarget, matchString) 373 374 return match 375 } 376 377 } 378 return compileErr 379 } 380 381 func (r Redirect) IsZero() bool { 382 return r.From == "" 383 } 384 385 const ( 386 // Keep this a little coarse grained, some false positives are OK. 387 cssTargetCachebusterRe = `(css|styles|scss|sass)` 388 ) 389 390 func DecodeServer(cfg Provider) (Server, error) { 391 s := &Server{} 392 393 _ = mapstructure.WeakDecode(cfg.GetStringMap("server"), s) 394 395 for i, redir := range s.Redirects { 396 // Get it in line with the Hugo server for OK responses. 397 // We currently treat the 404 as a special case, they are always "ugly", so keep them as is. 398 if redir.Status != 404 { 399 redir.To = strings.TrimSuffix(redir.To, "index.html") 400 if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") { 401 // There are some tricky infinite loop situations when dealing 402 // when the target does not have a trailing slash. 403 // This can certainly be handled better, but not time for that now. 404 return Server{}, fmt.Errorf("unsupported redirect to value %q in server config; currently this must be either a remote destination or a local folder, e.g. \"/blog/\" or \"/blog/index.html\"", redir.To) 405 } 406 } 407 s.Redirects[i] = redir 408 } 409 410 if len(s.Redirects) == 0 { 411 // Set up a default redirect for 404s. 412 s.Redirects = []Redirect{ 413 { 414 From: "**", 415 To: "/404.html", 416 Status: 404, 417 }, 418 } 419 420 } 421 422 return *s, nil 423 }