github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/output/layouts/layout.go (about) 1 // Copyright 2023 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 layouts 15 16 import ( 17 "strings" 18 "sync" 19 ) 20 21 // These may be used as content sections with potential conflicts. Avoid that. 22 var reservedSections = map[string]bool{ 23 "shortcodes": true, 24 "partials": true, 25 } 26 27 // LayoutDescriptor describes how a layout should be chosen. This is 28 // typically built from a Page. 29 type LayoutDescriptor struct { 30 Type string 31 Section string 32 33 // E.g. "page", but also used for the _markup render kinds, e.g. "render-image". 34 Kind string 35 36 // Comma-separated list of kind variants, e.g. "go,json" as variants which would find "render-codeblock-go.html" 37 KindVariants string 38 39 Lang string 40 Layout string 41 // LayoutOverride indicates what we should only look for the above layout. 42 LayoutOverride bool 43 44 // From OutputFormat and MediaType. 45 OutputFormatName string 46 Suffix string 47 48 RenderingHook bool 49 Baseof bool 50 } 51 52 func (d LayoutDescriptor) isList() bool { 53 return !d.RenderingHook && d.Kind != "page" && d.Kind != "404" 54 } 55 56 // LayoutHandler calculates the layout template to use to render a given output type. 57 type LayoutHandler struct { 58 mu sync.RWMutex 59 cache map[LayoutDescriptor][]string 60 } 61 62 // NewLayoutHandler creates a new LayoutHandler. 63 func NewLayoutHandler() *LayoutHandler { 64 return &LayoutHandler{cache: make(map[LayoutDescriptor][]string)} 65 } 66 67 // For returns a layout for the given LayoutDescriptor and options. 68 // Layouts are rendered and cached internally. 69 func (l *LayoutHandler) For(d LayoutDescriptor) ([]string, error) { 70 // We will get lots of requests for the same layouts, so avoid recalculations. 71 l.mu.RLock() 72 if cacheVal, found := l.cache[d]; found { 73 l.mu.RUnlock() 74 return cacheVal, nil 75 } 76 l.mu.RUnlock() 77 78 layouts := resolvePageTemplate(d) 79 80 layouts = uniqueStringsReuse(layouts) 81 82 l.mu.Lock() 83 l.cache[d] = layouts 84 l.mu.Unlock() 85 86 return layouts, nil 87 } 88 89 type layoutBuilder struct { 90 layoutVariations []string 91 typeVariations []string 92 d LayoutDescriptor 93 //f Format 94 } 95 96 func (l *layoutBuilder) addLayoutVariations(vars ...string) { 97 for _, layoutVar := range vars { 98 if l.d.Baseof && layoutVar != "baseof" { 99 l.layoutVariations = append(l.layoutVariations, layoutVar+"-baseof") 100 continue 101 } 102 if !l.d.RenderingHook && !l.d.Baseof && l.d.LayoutOverride && layoutVar != l.d.Layout { 103 continue 104 } 105 l.layoutVariations = append(l.layoutVariations, layoutVar) 106 } 107 } 108 109 func (l *layoutBuilder) addTypeVariations(vars ...string) { 110 for _, typeVar := range vars { 111 if !reservedSections[typeVar] { 112 if l.d.RenderingHook { 113 typeVar = typeVar + renderingHookRoot 114 } 115 l.typeVariations = append(l.typeVariations, typeVar) 116 } 117 } 118 } 119 120 func (l *layoutBuilder) addSectionType() { 121 if l.d.Section != "" { 122 l.addTypeVariations(l.d.Section) 123 } 124 } 125 126 func (l *layoutBuilder) addKind() { 127 l.addLayoutVariations(l.d.Kind) 128 l.addTypeVariations(l.d.Kind) 129 } 130 131 const renderingHookRoot = "/_markup" 132 133 func resolvePageTemplate(d LayoutDescriptor) []string { 134 b := &layoutBuilder{d: d} 135 136 if !d.RenderingHook && d.Layout != "" { 137 b.addLayoutVariations(d.Layout) 138 } 139 if d.Type != "" { 140 b.addTypeVariations(d.Type) 141 } 142 143 if d.RenderingHook { 144 if d.KindVariants != "" { 145 // Add the more specific variants first. 146 for _, variant := range strings.Split(d.KindVariants, ",") { 147 b.addLayoutVariations(d.Kind + "-" + variant) 148 } 149 } 150 b.addLayoutVariations(d.Kind) 151 b.addSectionType() 152 } 153 154 switch d.Kind { 155 case "page": 156 b.addLayoutVariations("single") 157 b.addSectionType() 158 case "home": 159 b.addLayoutVariations("index", "home") 160 // Also look in the root 161 b.addTypeVariations("") 162 case "section": 163 if d.Section != "" { 164 b.addLayoutVariations(d.Section) 165 } 166 b.addSectionType() 167 b.addKind() 168 case "term": 169 b.addKind() 170 if d.Section != "" { 171 b.addLayoutVariations(d.Section) 172 } 173 b.addLayoutVariations("taxonomy") 174 b.addTypeVariations("taxonomy") 175 b.addSectionType() 176 case "taxonomy": 177 if d.Section != "" { 178 b.addLayoutVariations(d.Section + ".terms") 179 } 180 b.addSectionType() 181 b.addLayoutVariations("terms") 182 // For legacy reasons this is deliberately put last. 183 b.addKind() 184 case "404": 185 b.addLayoutVariations("404") 186 b.addTypeVariations("") 187 } 188 189 isRSS := strings.EqualFold(d.OutputFormatName, "rss") 190 if !d.RenderingHook && !d.Baseof && isRSS { 191 // The historic and common rss.xml case 192 b.addLayoutVariations("") 193 } 194 195 if d.Baseof || d.Kind != "404" { 196 // Most have _default in their lookup path 197 b.addTypeVariations("_default") 198 } 199 200 if d.isList() { 201 // Add the common list type 202 b.addLayoutVariations("list") 203 } 204 205 if d.Baseof { 206 b.addLayoutVariations("baseof") 207 } 208 209 layouts := b.resolveVariations() 210 211 if !d.RenderingHook && !d.Baseof && isRSS { 212 layouts = append(layouts, "_internal/_default/rss.xml") 213 } 214 215 return layouts 216 } 217 218 func (l *layoutBuilder) resolveVariations() []string { 219 var layouts []string 220 221 var variations []string 222 name := strings.ToLower(l.d.OutputFormatName) 223 224 if l.d.Lang != "" { 225 // We prefer the most specific type before language. 226 variations = append(variations, []string{l.d.Lang + "." + name, name, l.d.Lang}...) 227 } else { 228 variations = append(variations, name) 229 } 230 231 variations = append(variations, "") 232 233 for _, typeVar := range l.typeVariations { 234 for _, variation := range variations { 235 for _, layoutVar := range l.layoutVariations { 236 if variation == "" && layoutVar == "" { 237 continue 238 } 239 240 s := constructLayoutPath(typeVar, layoutVar, variation, l.d.Suffix) 241 if s != "" { 242 layouts = append(layouts, s) 243 } 244 } 245 } 246 } 247 248 return layouts 249 } 250 251 // constructLayoutPath constructs a layout path given a type, layout, 252 // variations, and extension. The path constructed follows the pattern of 253 // type/layout.variations.extension. If any value is empty, it will be left out 254 // of the path construction. 255 // 256 // Path construction requires at least 2 of 3 out of layout, variations, and extension. 257 // If more than one of those is empty, an empty string is returned. 258 func constructLayoutPath(typ, layout, variations, extension string) string { 259 // we already know that layout and variations are not both empty because of 260 // checks in resolveVariants(). 261 if extension == "" && (layout == "" || variations == "") { 262 return "" 263 } 264 265 // Commence valid path construction... 266 267 var ( 268 p strings.Builder 269 needDot bool 270 ) 271 272 if typ != "" { 273 p.WriteString(typ) 274 p.WriteString("/") 275 } 276 277 if layout != "" { 278 p.WriteString(layout) 279 needDot = true 280 } 281 282 if variations != "" { 283 if needDot { 284 p.WriteString(".") 285 } 286 p.WriteString(variations) 287 needDot = true 288 } 289 290 if extension != "" { 291 if needDot { 292 p.WriteString(".") 293 } 294 p.WriteString(extension) 295 } 296 297 return p.String() 298 } 299 300 // Inline this here so we can use tinygo to compile a wasm binary of this package. 301 func uniqueStringsReuse(s []string) []string { 302 result := s[:0] 303 for i, val := range s { 304 var seen bool 305 306 for j := 0; j < i; j++ { 307 if s[j] == val { 308 seen = true 309 break 310 } 311 } 312 313 if !seen { 314 result = append(result, val) 315 } 316 } 317 return result 318 }