context.go - hugo - [fork] hugo port for 9front
(HTM) git clone https://git.drkhsh.at/hugo.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Submodules
(DIR) README
(DIR) LICENSE
---
context.go (7351B)
---
1 // Copyright 2025 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 render
15
16 import (
17 "bytes"
18 "math/bits"
19 "strings"
20 "sync"
21
22 "github.com/gohugoio/hugo-goldmark-extensions/passthrough"
23 bp "github.com/gohugoio/hugo/bufferpool"
24 east "github.com/yuin/goldmark-emoji/ast"
25
26 htext "github.com/gohugoio/hugo/common/text"
27 "github.com/gohugoio/hugo/tpl"
28
29 "github.com/gohugoio/hugo/markup/converter"
30 "github.com/gohugoio/hugo/markup/converter/hooks"
31 "github.com/yuin/goldmark/ast"
32 "github.com/yuin/goldmark/util"
33 )
34
35 type BufWriter struct {
36 *bytes.Buffer
37 }
38
39 const maxInt = 1<<(bits.UintSize-1) - 1
40
41 func (b *BufWriter) Available() int {
42 return maxInt
43 }
44
45 func (b *BufWriter) Buffered() int {
46 return b.Len()
47 }
48
49 func (b *BufWriter) Flush() error {
50 return nil
51 }
52
53 type Context struct {
54 *BufWriter
55 ContextData
56 positions []int
57 pids []uint64
58 ordinals map[ast.NodeKind]int
59 values map[ast.NodeKind][]any
60 }
61
62 func (ctx *Context) GetAndIncrementOrdinal(kind ast.NodeKind) int {
63 if ctx.ordinals == nil {
64 ctx.ordinals = make(map[ast.NodeKind]int)
65 }
66 i := ctx.ordinals[kind]
67 ctx.ordinals[kind]++
68 return i
69 }
70
71 func (ctx *Context) PushPos(n int) {
72 ctx.positions = append(ctx.positions, n)
73 }
74
75 func (ctx *Context) PopPos() int {
76 i := len(ctx.positions) - 1
77 p := ctx.positions[i]
78 ctx.positions = ctx.positions[:i]
79 return p
80 }
81
82 func (ctx *Context) PopRenderedString() string {
83 pos := ctx.PopPos()
84 text := string(ctx.Bytes()[pos:])
85 ctx.Truncate(pos)
86 return text
87 }
88
89 // PushPid pushes a new page ID to the stack.
90 func (ctx *Context) PushPid(pid uint64) {
91 ctx.pids = append(ctx.pids, pid)
92 }
93
94 // PeekPid returns the current page ID without removing it from the stack.
95 func (ctx *Context) PeekPid() uint64 {
96 if len(ctx.pids) == 0 {
97 return 0
98 }
99 return ctx.pids[len(ctx.pids)-1]
100 }
101
102 // PopPid pops the last page ID from the stack.
103 func (ctx *Context) PopPid() uint64 {
104 if len(ctx.pids) == 0 {
105 return 0
106 }
107 i := len(ctx.pids) - 1
108 p := ctx.pids[i]
109 ctx.pids = ctx.pids[:i]
110 return p
111 }
112
113 func (ctx *Context) PushValue(k ast.NodeKind, v any) {
114 if ctx.values == nil {
115 ctx.values = make(map[ast.NodeKind][]any)
116 }
117 ctx.values[k] = append(ctx.values[k], v)
118 }
119
120 func (ctx *Context) PopValue(k ast.NodeKind) any {
121 if ctx.values == nil {
122 return nil
123 }
124 v := ctx.values[k]
125 if len(v) == 0 {
126 return nil
127 }
128 i := len(v) - 1
129 r := v[i]
130 ctx.values[k] = v[:i]
131 return r
132 }
133
134 func (ctx *Context) PeekValue(k ast.NodeKind) any {
135 if ctx.values == nil {
136 return nil
137 }
138 v := ctx.values[k]
139 if len(v) == 0 {
140 return nil
141 }
142 return v[len(v)-1]
143 }
144
145 type ContextData interface {
146 RenderContext() converter.RenderContext
147 DocumentContext() converter.DocumentContext
148 }
149
150 type RenderContextDataHolder struct {
151 Rctx converter.RenderContext
152 Dctx converter.DocumentContext
153 }
154
155 func (ctx *RenderContextDataHolder) RenderContext() converter.RenderContext {
156 return ctx.Rctx
157 }
158
159 func (ctx *RenderContextDataHolder) DocumentContext() converter.DocumentContext {
160 return ctx.Dctx
161 }
162
163 // extractSourceSample returns a sample of the source for the given node.
164 // Note that this is not a copy of the source, but a slice of it,
165 // so it assumes that the source is not mutated.
166 func extractSourceSample(n ast.Node, src []byte) []byte {
167 if n.Type() == ast.TypeInline {
168 switch n := n.(type) {
169 case *passthrough.PassthroughInline:
170 return n.Segment.Value(src)
171 }
172
173 return nil
174 }
175
176 var sample []byte
177
178 getStartStop := func(n ast.Node) (int, int) {
179 if n == nil {
180 return 0, 0
181 }
182
183 var start, stop int
184 for i := 0; i < n.Lines().Len() && i < 2; i++ {
185 line := n.Lines().At(i)
186 if i == 0 {
187 start = line.Start
188 }
189 stop = line.Stop
190 }
191 return start, stop
192 }
193
194 start, stop := getStartStop(n)
195 if stop == 0 {
196 // Try first child.
197 start, stop = getStartStop(n.FirstChild())
198 }
199
200 if stop > 0 {
201 // We do not mutate the source, so this is safe.
202 sample = src[start:stop]
203 }
204
205 return sample
206 }
207
208 // GetPageAndPageInner returns the current page and the inner page for the given context.
209 func GetPageAndPageInner(rctx *Context) (any, any) {
210 p := rctx.DocumentContext().Document
211 pid := rctx.PeekPid()
212 if pid > 0 {
213 if lookup := rctx.DocumentContext().DocumentLookup; lookup != nil {
214 if v := rctx.DocumentContext().DocumentLookup(pid); v != nil {
215 return p, v
216 }
217 }
218 }
219 return p, p
220 }
221
222 // NewBaseContext creates a new BaseContext.
223 func NewBaseContext(rctx *Context, renderer any, n ast.Node, src []byte, getSourceSample func() []byte, ordinal int) hooks.BaseContext {
224 if getSourceSample == nil {
225 getSourceSample = func() []byte {
226 return extractSourceSample(n, src)
227 }
228 }
229 page, pageInner := GetPageAndPageInner(rctx)
230 b := &hookBase{
231 page: page,
232 pageInner: pageInner,
233
234 getSourceSample: getSourceSample,
235 ordinal: ordinal,
236 }
237
238 b.createPos = func() htext.Position {
239 if resolver, ok := renderer.(hooks.ElementPositionResolver); ok {
240 return resolver.ResolvePosition(b)
241 }
242
243 return htext.Position{
244 Filename: rctx.DocumentContext().Filename,
245 LineNumber: 1,
246 ColumnNumber: 1,
247 }
248 }
249
250 return b
251 }
252
253 var _ hooks.PositionerSourceTargetProvider = (*hookBase)(nil)
254
255 type hookBase struct {
256 page any
257 pageInner any
258 ordinal int
259
260 // This is only used in error situations and is expensive to create,
261 // so delay creation until needed.
262 pos htext.Position
263 posInit sync.Once
264 createPos func() htext.Position
265 getSourceSample func() []byte
266 }
267
268 func (c *hookBase) Page() any {
269 return c.page
270 }
271
272 func (c *hookBase) PageInner() any {
273 return c.pageInner
274 }
275
276 func (c *hookBase) Ordinal() int {
277 return c.ordinal
278 }
279
280 func (c *hookBase) Position() htext.Position {
281 c.posInit.Do(func() {
282 c.pos = c.createPos()
283 })
284 return c.pos
285 }
286
287 // For internal use.
288 func (c *hookBase) PositionerSourceTarget() []byte {
289 return c.getSourceSample()
290 }
291
292 // TextPlain returns a plain text representation of the given node.
293 // This will resolve any leftover HTML entities. This will typically be
294 // entities inserted by e.g. the typographer extension.
295 // Goldmark's Node.Text was deprecated in 1.7.8.
296 func TextPlain(n ast.Node, source []byte) string {
297 buf := bp.GetBuffer()
298 defer bp.PutBuffer(buf)
299
300 for c := n.FirstChild(); c != nil; c = c.NextSibling() {
301 textPlainTo(c, source, buf)
302 }
303 return string(util.ResolveEntityNames(buf.Bytes()))
304 }
305
306 func textPlainTo(c ast.Node, source []byte, buf *bytes.Buffer) {
307 if c == nil {
308 return
309 }
310
311 switch c := c.(type) {
312 case *ast.RawHTML:
313 s := strings.TrimSpace(tpl.StripHTML(string(c.Segments.Value(source))))
314 buf.WriteString(s)
315 case *ast.String:
316 buf.Write(c.Value)
317 case *ast.Text:
318 buf.Write(c.Segment.Value(source))
319 if c.HardLineBreak() || c.SoftLineBreak() {
320 buf.WriteByte('\n')
321 }
322 case *east.Emoji:
323 buf.WriteString(string(c.ShortName))
324 default:
325 textPlainTo(c.FirstChild(), source, buf)
326 }
327 }