github.com/pietrocarrara/hugo@v0.47.1/output/layout.go (about) 1 // Copyright 2017-present 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 output 15 16 import ( 17 "fmt" 18 "strings" 19 "sync" 20 21 "github.com/gohugoio/hugo/helpers" 22 ) 23 24 // These may be used as content sections with potential conflicts. Avoid that. 25 var reservedSections = map[string]bool{ 26 "shortcodes": true, 27 "partials": true, 28 } 29 30 // LayoutDescriptor describes how a layout should be chosen. This is 31 // typically built from a Page. 32 type LayoutDescriptor struct { 33 Type string 34 Section string 35 Kind string 36 Lang string 37 Layout string 38 // LayoutOverride indicates what we should only look for the above layout. 39 LayoutOverride bool 40 } 41 42 // LayoutHandler calculates the layout template to use to render a given output type. 43 type LayoutHandler struct { 44 mu sync.RWMutex 45 cache map[layoutCacheKey][]string 46 } 47 48 type layoutCacheKey struct { 49 d LayoutDescriptor 50 f string 51 } 52 53 // NewLayoutHandler creates a new LayoutHandler. 54 func NewLayoutHandler() *LayoutHandler { 55 return &LayoutHandler{cache: make(map[layoutCacheKey][]string)} 56 } 57 58 // For returns a layout for the given LayoutDescriptor and options. 59 // Layouts are rendered and cached internally. 60 func (l *LayoutHandler) For(d LayoutDescriptor, f Format) ([]string, error) { 61 62 // We will get lots of requests for the same layouts, so avoid recalculations. 63 key := layoutCacheKey{d, f.Name} 64 l.mu.RLock() 65 if cacheVal, found := l.cache[key]; found { 66 l.mu.RUnlock() 67 return cacheVal, nil 68 } 69 l.mu.RUnlock() 70 71 layouts := resolvePageTemplate(d, f) 72 73 layouts = prependTextPrefixIfNeeded(f, layouts...) 74 layouts = helpers.UniqueStrings(layouts) 75 76 l.mu.Lock() 77 l.cache[key] = layouts 78 l.mu.Unlock() 79 80 return layouts, nil 81 } 82 83 type layoutBuilder struct { 84 layoutVariations []string 85 typeVariations []string 86 d LayoutDescriptor 87 f Format 88 } 89 90 func (l *layoutBuilder) addLayoutVariations(vars ...string) { 91 for _, layoutVar := range vars { 92 if l.d.LayoutOverride && layoutVar != l.d.Layout { 93 continue 94 } 95 l.layoutVariations = append(l.layoutVariations, layoutVar) 96 } 97 } 98 99 func (l *layoutBuilder) addTypeVariations(vars ...string) { 100 for _, typeVar := range vars { 101 if !reservedSections[typeVar] { 102 l.typeVariations = append(l.typeVariations, typeVar) 103 } 104 } 105 } 106 107 func (l *layoutBuilder) addSectionType() { 108 if l.d.Section != "" { 109 l.addTypeVariations(l.d.Section) 110 } 111 } 112 113 func (l *layoutBuilder) addKind() { 114 l.addLayoutVariations(l.d.Kind) 115 l.addTypeVariations(l.d.Kind) 116 } 117 118 func resolvePageTemplate(d LayoutDescriptor, f Format) []string { 119 120 b := &layoutBuilder{d: d, f: f} 121 122 if d.Layout != "" { 123 b.addLayoutVariations(d.Layout) 124 } 125 126 if d.Type != "" { 127 b.addTypeVariations(d.Type) 128 } 129 130 switch d.Kind { 131 case "page": 132 b.addLayoutVariations("single") 133 b.addSectionType() 134 case "home": 135 b.addLayoutVariations("index", "home") 136 // Also look in the root 137 b.addTypeVariations("") 138 case "section": 139 if d.Section != "" { 140 b.addLayoutVariations(d.Section) 141 } 142 b.addSectionType() 143 b.addKind() 144 case "taxonomy": 145 if d.Section != "" { 146 b.addLayoutVariations(d.Section) 147 } 148 b.addKind() 149 b.addSectionType() 150 151 case "taxonomyTerm": 152 if d.Section != "" { 153 b.addLayoutVariations(d.Section + ".terms") 154 } 155 b.addTypeVariations("taxonomy") 156 b.addSectionType() 157 b.addLayoutVariations("terms") 158 159 } 160 161 isRSS := f.Name == RSSFormat.Name 162 if isRSS { 163 // The historic and common rss.xml case 164 b.addLayoutVariations("") 165 } 166 167 // All have _default in their lookup path 168 b.addTypeVariations("_default") 169 170 if d.Kind != "page" { 171 // Add the common list type 172 b.addLayoutVariations("list") 173 } 174 175 layouts := b.resolveVariations() 176 177 if isRSS { 178 layouts = append(layouts, "_internal/_default/rss.xml") 179 } 180 181 return layouts 182 183 } 184 185 func (l *layoutBuilder) resolveVariations() []string { 186 187 var layouts []string 188 189 var variations []string 190 name := strings.ToLower(l.f.Name) 191 192 if l.d.Lang != "" { 193 // We prefer the most specific type before language. 194 variations = append(variations, []string{fmt.Sprintf("%s.%s", l.d.Lang, name), name, l.d.Lang}...) 195 } else { 196 variations = append(variations, name) 197 } 198 199 variations = append(variations, "") 200 201 for _, typeVar := range l.typeVariations { 202 for _, variation := range variations { 203 for _, layoutVar := range l.layoutVariations { 204 if variation == "" && layoutVar == "" { 205 continue 206 } 207 template := layoutTemplate(typeVar, layoutVar) 208 layouts = append(layouts, replaceKeyValues(template, 209 "TYPE", typeVar, 210 "LAYOUT", layoutVar, 211 "VARIATIONS", variation, 212 "EXTENSION", l.f.MediaType.Suffix(), 213 )) 214 } 215 } 216 217 } 218 219 return filterDotLess(layouts) 220 } 221 222 func layoutTemplate(typeVar, layoutVar string) string { 223 224 var l string 225 226 if typeVar != "" { 227 l = "TYPE/" 228 } 229 230 if layoutVar != "" { 231 l += "LAYOUT.VARIATIONS.EXTENSION" 232 } else { 233 l += "VARIATIONS.EXTENSION" 234 } 235 236 return l 237 } 238 239 func filterDotLess(layouts []string) []string { 240 var filteredLayouts []string 241 242 for _, l := range layouts { 243 l = strings.Replace(l, "..", ".", -1) 244 l = strings.Trim(l, ".") 245 // If media type has no suffix, we have "index" type of layouts in this list, which 246 // doesn't make much sense. 247 if strings.Contains(l, ".") { 248 filteredLayouts = append(filteredLayouts, l) 249 } 250 } 251 252 return filteredLayouts 253 } 254 255 func prependTextPrefixIfNeeded(f Format, layouts ...string) []string { 256 if !f.IsPlainText { 257 return layouts 258 } 259 260 newLayouts := make([]string, len(layouts)) 261 262 for i, l := range layouts { 263 newLayouts[i] = "_text/" + l 264 } 265 266 return newLayouts 267 } 268 269 func replaceKeyValues(s string, oldNew ...string) string { 270 replacer := strings.NewReplacer(oldNew...) 271 return replacer.Replace(s) 272 }