github.com/SDLMoe/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 }