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 }