commonConfig.go (4957B)
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 "sort" 19 "strings" 20 "sync" 21 22 "github.com/gohugoio/hugo/common/types" 23 24 "github.com/gobwas/glob" 25 "github.com/gohugoio/hugo/common/herrors" 26 "github.com/mitchellh/mapstructure" 27 "github.com/spf13/cast" 28 jww "github.com/spf13/jwalterweatherman" 29 ) 30 31 var DefaultBuild = Build{ 32 UseResourceCacheWhen: "fallback", 33 WriteStats: false, 34 } 35 36 // Build holds some build related configuration. 37 type Build struct { 38 UseResourceCacheWhen string // never, fallback, always. Default is fallback 39 40 // When enabled, will collect and write a hugo_stats.json with some build 41 // related aggregated data (e.g. CSS class names). 42 WriteStats bool 43 44 // Can be used to toggle off writing of the intellinsense /assets/jsconfig.js 45 // file. 46 NoJSConfigInAssets bool 47 } 48 49 func (b Build) UseResourceCache(err error) bool { 50 if b.UseResourceCacheWhen == "never" { 51 return false 52 } 53 54 if b.UseResourceCacheWhen == "fallback" { 55 return err == herrors.ErrFeatureNotAvailable 56 } 57 58 return true 59 } 60 61 func DecodeBuild(cfg Provider) Build { 62 m := cfg.GetStringMap("build") 63 b := DefaultBuild 64 if m == nil { 65 return b 66 } 67 68 err := mapstructure.WeakDecode(m, &b) 69 if err != nil { 70 return DefaultBuild 71 } 72 73 b.UseResourceCacheWhen = strings.ToLower(b.UseResourceCacheWhen) 74 when := b.UseResourceCacheWhen 75 if when != "never" && when != "always" && when != "fallback" { 76 b.UseResourceCacheWhen = "fallback" 77 } 78 79 return b 80 } 81 82 // Sitemap configures the sitemap to be generated. 83 type Sitemap struct { 84 ChangeFreq string 85 Priority float64 86 Filename string 87 } 88 89 func DecodeSitemap(prototype Sitemap, input map[string]any) Sitemap { 90 for key, value := range input { 91 switch key { 92 case "changefreq": 93 prototype.ChangeFreq = cast.ToString(value) 94 case "priority": 95 prototype.Priority = cast.ToFloat64(value) 96 case "filename": 97 prototype.Filename = cast.ToString(value) 98 default: 99 jww.WARN.Printf("Unknown Sitemap field: %s\n", key) 100 } 101 } 102 103 return prototype 104 } 105 106 // Config for the dev server. 107 type Server struct { 108 Headers []Headers 109 Redirects []Redirect 110 111 compiledInit sync.Once 112 compiledHeaders []glob.Glob 113 compiledRedirects []glob.Glob 114 } 115 116 func (s *Server) init() { 117 s.compiledInit.Do(func() { 118 for _, h := range s.Headers { 119 s.compiledHeaders = append(s.compiledHeaders, glob.MustCompile(h.For)) 120 } 121 for _, r := range s.Redirects { 122 s.compiledRedirects = append(s.compiledRedirects, glob.MustCompile(r.From)) 123 } 124 }) 125 } 126 127 func (s *Server) MatchHeaders(pattern string) []types.KeyValueStr { 128 s.init() 129 130 if s.compiledHeaders == nil { 131 return nil 132 } 133 134 var matches []types.KeyValueStr 135 136 for i, g := range s.compiledHeaders { 137 if g.Match(pattern) { 138 h := s.Headers[i] 139 for k, v := range h.Values { 140 matches = append(matches, types.KeyValueStr{Key: k, Value: cast.ToString(v)}) 141 } 142 } 143 } 144 145 sort.Slice(matches, func(i, j int) bool { 146 return matches[i].Key < matches[j].Key 147 }) 148 149 return matches 150 } 151 152 func (s *Server) MatchRedirect(pattern string) Redirect { 153 s.init() 154 155 if s.compiledRedirects == nil { 156 return Redirect{} 157 } 158 159 pattern = strings.TrimSuffix(pattern, "index.html") 160 161 for i, g := range s.compiledRedirects { 162 redir := s.Redirects[i] 163 164 // No redirect to self. 165 if redir.To == pattern { 166 return Redirect{} 167 } 168 169 if g.Match(pattern) { 170 return redir 171 } 172 } 173 174 return Redirect{} 175 } 176 177 type Headers struct { 178 For string 179 Values map[string]any 180 } 181 182 type Redirect struct { 183 From string 184 To string 185 Status int 186 Force bool 187 } 188 189 func (r Redirect) IsZero() bool { 190 return r.From == "" 191 } 192 193 func DecodeServer(cfg Provider) (*Server, error) { 194 m := cfg.GetStringMap("server") 195 s := &Server{} 196 if m == nil { 197 return s, nil 198 } 199 200 _ = mapstructure.WeakDecode(m, s) 201 202 for i, redir := range s.Redirects { 203 // Get it in line with the Hugo server. 204 redir.To = strings.TrimSuffix(redir.To, "index.html") 205 if !strings.HasPrefix(redir.To, "https") && !strings.HasSuffix(redir.To, "/") { 206 // There are some tricky infinite loop situations when dealing 207 // when the target does not have a trailing slash. 208 // This can certainly be handled better, but not time for that now. 209 return nil, 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) 210 } 211 s.Redirects[i] = redir 212 } 213 214 return s, nil 215 }