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 }