github.com/shohhei1126/hugo@v0.42.2-0.20180623210752-3d5928889ad7/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 CreateTemplateNames(d TemplateLookupDescriptor) (TemplateNames, error) {
    62  
    63  	name := filepath.ToSlash(d.RelPath)
    64  	name = strings.TrimPrefix(name, "/")
    65  
    66  	if d.Prefix != "" {
    67  		name = strings.Trim(d.Prefix, "/") + "/" + name
    68  	}
    69  
    70  	var (
    71  		id TemplateNames
    72  	)
    73  
    74  	// The filename will have a suffix with an optional type indicator.
    75  	// Examples:
    76  	// index.html
    77  	// index.amp.html
    78  	// index.json
    79  	filename := filepath.Base(d.RelPath)
    80  	isPlainText := false
    81  	outputFormat, found := d.OutputFormats.FromFilename(filename)
    82  
    83  	if found && outputFormat.IsPlainText {
    84  		isPlainText = true
    85  	}
    86  
    87  	var ext, outFormat string
    88  
    89  	parts := strings.Split(filename, ".")
    90  	if len(parts) > 2 {
    91  		outFormat = parts[1]
    92  		ext = parts[2]
    93  	} else if len(parts) > 1 {
    94  		ext = parts[1]
    95  	}
    96  
    97  	filenameNoSuffix := parts[0]
    98  
    99  	id.OverlayFilename = d.RelPath
   100  	id.Name = name
   101  
   102  	if isPlainText {
   103  		id.Name = "_text/" + id.Name
   104  	}
   105  
   106  	// Ace and Go templates may have both a base and inner template.
   107  	pathDir := filepath.Dir(d.RelPath)
   108  
   109  	if ext == "amber" || strings.HasSuffix(pathDir, "partials") || strings.HasSuffix(pathDir, "shortcodes") {
   110  		// No base template support
   111  		return id, nil
   112  	}
   113  
   114  	innerMarkers := goTemplateInnerMarkers
   115  
   116  	var baseFilename string
   117  
   118  	if outFormat != "" {
   119  		baseFilename = fmt.Sprintf("%s.%s.%s", baseFileBase, outFormat, ext)
   120  	} else {
   121  		baseFilename = fmt.Sprintf("%s.%s", baseFileBase, ext)
   122  	}
   123  
   124  	if ext == "ace" {
   125  		innerMarkers = aceTemplateInnerMarkers
   126  	}
   127  
   128  	// This may be a view that shouldn't have base template
   129  	// Have to look inside it to make sure
   130  	needsBase, err := d.ContainsAny(d.RelPath, innerMarkers)
   131  	if err != nil {
   132  		return id, err
   133  	}
   134  
   135  	if needsBase {
   136  		currBaseFilename := fmt.Sprintf("%s-%s", filenameNoSuffix, baseFilename)
   137  
   138  		// Look for base template in the follwing order:
   139  		//   1. <current-path>/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
   140  		//   2. <current-path>/baseof.<outputFormat>(optional).<suffix>
   141  		//   3. _default/<template-name>-baseof.<outputFormat>(optional).<suffix>, e.g. list-baseof.<outputFormat>(optional).<suffix>.
   142  		//   4. _default/baseof.<outputFormat>(optional).<suffix>
   143  		//
   144  		// The filesystem it looks in a a composite of the project and potential theme(s).
   145  		pathsToCheck := createPathsToCheck(pathDir, baseFilename, currBaseFilename)
   146  
   147  		// We may have language code and/or "terms" in the template name. We want the most specific,
   148  		// but need to fall back to the baseof.html or baseof.ace if needed.
   149  		// E.g. list-baseof.en.html and list-baseof.terms.en.html
   150  		// See #3893, #3856.
   151  		baseBaseFilename, currBaseBaseFilename := helpers.Filename(baseFilename), helpers.Filename(currBaseFilename)
   152  		p1, p2 := strings.Split(baseBaseFilename, "."), strings.Split(currBaseBaseFilename, ".")
   153  		if len(p1) > 0 && len(p1) == len(p2) {
   154  			for i := len(p1); i > 0; i-- {
   155  				v1, v2 := strings.Join(p1[:i], ".")+"."+ext, strings.Join(p2[:i], ".")+"."+ext
   156  				pathsToCheck = append(pathsToCheck, createPathsToCheck(pathDir, v1, v2)...)
   157  
   158  			}
   159  		}
   160  
   161  		for _, p := range pathsToCheck {
   162  			if ok, err := d.FileExists(p); err == nil && ok {
   163  				id.MasterFilename = p
   164  				break
   165  			}
   166  		}
   167  	}
   168  
   169  	return id, nil
   170  
   171  }
   172  
   173  func createPathsToCheck(baseTemplatedDir, baseFilename, currBaseFilename string) []string {
   174  	return []string{
   175  		filepath.Join(baseTemplatedDir, currBaseFilename),
   176  		filepath.Join(baseTemplatedDir, baseFilename),
   177  		filepath.Join("_default", currBaseFilename),
   178  		filepath.Join("_default", baseFilename),
   179  	}
   180  }