github.com/schumacherfm/hugo@v0.47.1/output/layout_base.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  	"path/filepath"
    19  	"strings"
    20  
    21  	"github.com/gohugoio/hugo/helpers"
    22  )
    23  
    24  const (
    25  	baseFileBase = "baseof"
    26  )
    27  
    28  var (
    29  	aceTemplateInnerMarkers = [][]byte{[]byte("= content")}
    30  	goTemplateInnerMarkers  = [][]byte{[]byte("{{define"), []byte("{{ define"), []byte("{{- define"), []byte("{{-define")}
    31  )
    32  
    33  type TemplateNames struct {
    34  	// The name used as key in the template map. Note that this will be
    35  	// prefixed with "_text/" if it should be parsed with text/template.
    36  	Name string
    37  
    38  	OverlayFilename string
    39  	MasterFilename  string
    40  }
    41  
    42  type TemplateLookupDescriptor struct {
    43  	// The full path to the site root.
    44  	WorkingDir string
    45  
    46  	// The path to the template relative the the base.
    47  	//  I.e. shortcodes/youtube.html
    48  	RelPath string
    49  
    50  	// The template name prefix to look for.
    51  	Prefix string
    52  
    53  	// All the output formats in play. This is used to decide if text/template or
    54  	// html/template.
    55  	OutputFormats Formats
    56  
    57  	FileExists  func(filename string) (bool, error)
    58  	ContainsAny func(filename string, subslices [][]byte) (bool, error)
    59  }
    60  
    61  func isShorthCodeOrPartial(name string) bool {
    62  	return strings.HasPrefix(name, "shortcodes/") || strings.HasPrefix(name, "partials/")
    63  }
    64  
    65  func CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
    66  
    67  	name := filepath.ToSlash(d.RelPath)
    68  	name = strings.TrimPrefix(name, "/")
    69  
    70  	if d.Prefix != "" {
    71  		name = strings.Trim(d.Prefix, "/") + "/" + name
    72  	}
    73  
    74  	var (
    75  		id TemplateNames
    76  	)
    77  
    78  	// The filename will have a suffix with an optional type indicator.
    79  	// Examples:
    80  	// index.html
    81  	// index.amp.html
    82  	// index.json
    83  	filename := filepath.Base(d.RelPath)
    84  	isPlainText := false
    85  	outputFormat, found := d.OutputFormats.FromFilename(filename)
    86  
    87  	if found && outputFormat.IsPlainText {
    88  		isPlainText = true
    89  	}
    90  
    91  	var ext, outFormat string
    92  
    93  	parts := strings.Split(filename, ".")
    94  	if len(parts) > 2 {
    95  		outFormat = parts[1]
    96  		ext = parts[2]
    97  	} else if len(parts) > 1 {
    98  		ext = parts[1]
    99  	}
   100  
   101  	filenameNoSuffix := parts[0]
   102  
   103  	id.OverlayFilename = d.RelPath
   104  	id.Name = name
   105  
   106  	if isPlainText {
   107  		id.Name = "_text/" + id.Name
   108  	}
   109  
   110  	// Ace and Go templates may have both a base and inner template.
   111  	if ext == "amber" || isShorthCodeOrPartial(name) {
   112  		// No base template support
   113  		return id, nil
   114  	}
   115  
   116  	pathDir := filepath.Dir(d.RelPath)
   117  
   118  	innerMarkers := goTemplateInnerMarkers
   119  
   120  	var baseFilename string
   121  
   122  	if outFormat != "" {
   123  		baseFilename = fmt.Sprintf("%s.%s.%s", baseFileBase, outFormat, ext)
   124  	} else {
   125  		baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext)
   126  	}
   127  
   128  	if ext == "ace" {
   129  		innerMarkers = aceTemplateInnerMarkers
   130  	}
   131  
   132  	// This may be a view that shouldn't have base template
   133  	// Have to look inside it to make sure
   134  	needsBase, err := d.ContainsAny(d.RelPath, innerMarkers)
   135  	if err != nil {
   136  		return id, err
   137  	}
   138  
   139  	if needsBase {
   140  		currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename)
   141  
   142  		// Look for base template in the follwing order:
   143  		//   1. <current-path>/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
   144  		//   2. <current-path>/baseof.<outputFormat>(optional).<suffix>
   145  		//   3. _default/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
   146  		//   4. _default/baseof.<outputFormat>(optional).<suffix>
   147  		//
   148  		// The filesystem it looks in a a composite of the project and potential theme(s).
   149  		pathsToCheck := createPathsToCheck(pathDir, baseFilename, currBaseFilename)
   150  
   151  		// We may have language code and/or "terms" in the template name. We want the most specific,
   152  		// but need to fall back to the baseof.html or baseof.ace if needed.
   153  		// E.g. list-baseof.en.html and list-baseof.terms.en.html
   154  		// See #3893, #3856.
   155  		baseBaseFilename, currBaseBaseFilename := helpers.Filename(baseFilename), helpers.Filename(currBaseFilename)
   156  		p1, p2 := strings.Split(baseBaseFilename, "."), strings.Split(currBaseBaseFilename, ".")
   157  		if len(p1) > 0 && len(p1) == len(p2) {
   158  			for i := len(p1); i > 0; i-- {
   159  				v1, v2 := strings.Join(p1[:i], ".")+"."+ext, strings.Join(p2[:i], ".")+"."+ext
   160  				pathsToCheck = append(pathsToCheck, createPathsToCheck(pathDir, v1, v2)...)
   161  
   162  			}
   163  		}
   164  
   165  		for _, p := range pathsToCheck {
   166  			if ok, err := d.FileExists(p); err == nil && ok {
   167  				id.MasterFilename = p
   168  				break
   169  			}
   170  		}
   171  	}
   172  
   173  	return id, nil
   174  
   175  }
   176  
   177  func createPathsToCheck(baseTemplatedDir, baseFilename, currBaseFilename string) []string {
   178  	return []string{
   179  		filepath.Join(baseTemplatedDir, currBaseFilename),
   180  		filepath.Join(baseTemplatedDir, baseFilename),
   181  		filepath.Join("_default", currBaseFilename),
   182  		filepath.Join("_default", baseFilename),
   183  	}
   184  }