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 }