template.go (5593B)
1 // Copyright 2019 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 tpl 15 16 import ( 17 "context" 18 "io" 19 "reflect" 20 "regexp" 21 "strings" 22 "unicode" 23 24 bp "github.com/gohugoio/hugo/bufferpool" 25 26 "github.com/gohugoio/hugo/output" 27 28 htmltemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/htmltemplate" 29 texttemplate "github.com/gohugoio/hugo/tpl/internal/go_templates/texttemplate" 30 ) 31 32 // TemplateManager manages the collection of templates. 33 type TemplateManager interface { 34 TemplateHandler 35 TemplateFuncGetter 36 AddTemplate(name, tpl string) error 37 MarkReady() error 38 } 39 40 // TemplateVariants describes the possible variants of a template. 41 // All of these may be empty. 42 type TemplateVariants struct { 43 Language string 44 OutputFormat output.Format 45 } 46 47 // TemplateFinder finds templates. 48 type TemplateFinder interface { 49 TemplateLookup 50 TemplateLookupVariant 51 } 52 53 // UnusedTemplatesProvider lists unused templates if the build is configured to track those. 54 type UnusedTemplatesProvider interface { 55 UnusedTemplates() []FileInfo 56 } 57 58 // TemplateHandler finds and executes templates. 59 type TemplateHandler interface { 60 TemplateFinder 61 Execute(t Template, wr io.Writer, data any) error 62 ExecuteWithContext(ctx context.Context, t Template, wr io.Writer, data any) error 63 LookupLayout(d output.LayoutDescriptor, f output.Format) (Template, bool, error) 64 HasTemplate(name string) bool 65 } 66 67 type TemplateLookup interface { 68 Lookup(name string) (Template, bool) 69 } 70 71 type TemplateLookupVariant interface { 72 // TODO(bep) this currently only works for shortcodes. 73 // We may unify and expand this variant pattern to the 74 // other templates, but we need this now for the shortcodes to 75 // quickly determine if a shortcode has a template for a given 76 // output format. 77 // It returns the template, if it was found or not and if there are 78 // alternative representations (output format, language). 79 // We are currently only interested in output formats, so we should improve 80 // this for speed. 81 LookupVariant(name string, variants TemplateVariants) (Template, bool, bool) 82 LookupVariants(name string) []Template 83 } 84 85 // Template is the common interface between text/template and html/template. 86 type Template interface { 87 Name() string 88 Prepare() (*texttemplate.Template, error) 89 } 90 91 // TemplateParser is used to parse ad-hoc templates, e.g. in the Resource chain. 92 type TemplateParser interface { 93 Parse(name, tpl string) (Template, error) 94 } 95 96 // TemplateParseFinder provides both parsing and finding. 97 type TemplateParseFinder interface { 98 TemplateParser 99 TemplateFinder 100 } 101 102 // TemplateDebugger prints some debug info to stdout. 103 type TemplateDebugger interface { 104 Debug() 105 } 106 107 // templateInfo wraps a Template with some additional information. 108 type templateInfo struct { 109 Template 110 Info 111 } 112 113 // templateInfo wraps a Template with some additional information. 114 type templateInfoManager struct { 115 Template 116 InfoManager 117 } 118 119 // TemplatesProvider as implemented by deps.Deps. 120 type TemplatesProvider interface { 121 Tmpl() TemplateHandler 122 TextTmpl() TemplateParseFinder 123 } 124 125 // WithInfo wraps the info in a template. 126 func WithInfo(templ Template, info Info) Template { 127 if manager, ok := info.(InfoManager); ok { 128 return &templateInfoManager{ 129 Template: templ, 130 InfoManager: manager, 131 } 132 } 133 134 return &templateInfo{ 135 Template: templ, 136 Info: info, 137 } 138 } 139 140 var baseOfRe = regexp.MustCompile("template: (.*?):") 141 142 func extractBaseOf(err string) string { 143 m := baseOfRe.FindStringSubmatch(err) 144 if len(m) == 2 { 145 return m[1] 146 } 147 return "" 148 } 149 150 // TemplateFuncGetter allows to find a template func by name. 151 type TemplateFuncGetter interface { 152 GetFunc(name string) (reflect.Value, bool) 153 } 154 155 // GetDataFromContext returns the template data context (usually .Page) from ctx if set. 156 // NOte: This is not fully implemented yet. 157 func GetDataFromContext(ctx context.Context) any { 158 return ctx.Value(texttemplate.DataContextKey) 159 } 160 161 func GetHasLockFromContext(ctx context.Context) bool { 162 if v := ctx.Value(texttemplate.HasLockContextKey); v != nil { 163 return v.(bool) 164 } 165 return false 166 } 167 168 func SetHasLockInContext(ctx context.Context, hasLock bool) context.Context { 169 return context.WithValue(ctx, texttemplate.HasLockContextKey, hasLock) 170 } 171 172 const hugoNewLinePlaceholder = "___hugonl_" 173 174 var ( 175 stripHTMLReplacerPre = strings.NewReplacer("\n", " ", "</p>", hugoNewLinePlaceholder, "<br>", hugoNewLinePlaceholder, "<br />", hugoNewLinePlaceholder) 176 whitespaceRe = regexp.MustCompile(`\s+`) 177 ) 178 179 // StripHTML strips out all HTML tags in s. 180 func StripHTML(s string) string { 181 // Shortcut strings with no tags in them 182 if !strings.ContainsAny(s, "<>") { 183 return s 184 } 185 186 pre := stripHTMLReplacerPre.Replace(s) 187 preReplaced := pre != s 188 189 s = htmltemplate.StripTags(pre) 190 191 if preReplaced { 192 s = strings.ReplaceAll(s, hugoNewLinePlaceholder, "\n") 193 } 194 195 var wasSpace bool 196 b := bp.GetBuffer() 197 defer bp.PutBuffer(b) 198 for _, r := range s { 199 isSpace := unicode.IsSpace(r) 200 if !(isSpace && wasSpace) { 201 b.WriteRune(r) 202 } 203 wasSpace = isSpace 204 } 205 206 if b.Len() > 0 { 207 s = b.String() 208 } 209 210 return s 211 }