hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

layout.go (7336B)

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