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