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  }