2024-04-11 11:46:18 -04:00
// Copyright 2024 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugocontext
import (
"bytes"
"fmt"
2024-10-30 13:10:09 -04:00
"regexp"
2024-04-11 11:46:18 -04:00
"strconv"
"github.com/gohugoio/hugo/bufferpool"
2024-10-30 13:10:09 -04:00
"github.com/gohugoio/hugo/common/constants"
"github.com/gohugoio/hugo/common/loggers"
2024-04-11 11:46:18 -04:00
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
2024-10-30 13:10:09 -04:00
"github.com/yuin/goldmark/renderer/html"
2024-04-11 11:46:18 -04:00
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
2024-10-30 13:10:09 -04:00
func New ( logger loggers . Logger ) goldmark . Extender {
return & hugoContextExtension { logger : logger }
2024-04-11 11:46:18 -04:00
}
// Wrap wraps the given byte slice in a Hugo context that used to determine the correct Page
// in .RenderShortcodes.
2024-04-22 12:12:49 -04:00
func Wrap ( b [ ] byte , pid uint64 ) string {
2024-04-11 11:46:18 -04:00
buf := bufferpool . GetBuffer ( )
defer bufferpool . PutBuffer ( buf )
2024-10-30 13:10:09 -04:00
buf . Write ( hugoCtxPrefix )
2024-04-11 11:46:18 -04:00
buf . WriteString ( " pid=" )
buf . WriteString ( strconv . FormatUint ( pid , 10 ) )
2024-10-30 13:10:09 -04:00
buf . Write ( hugoCtxEndDelim )
2024-04-11 11:46:18 -04:00
buf . WriteByte ( '\n' )
buf . Write ( b )
2024-10-30 13:10:09 -04:00
// To make sure that we're able to parse it, make sure it ends with a newline.
if len ( b ) > 0 && b [ len ( b ) - 1 ] != '\n' {
buf . WriteByte ( '\n' )
}
buf . Write ( hugoCtxPrefix )
buf . Write ( hugoCtxClosingDelim )
buf . WriteByte ( '\n' )
2024-04-22 12:12:49 -04:00
return buf . String ( )
2024-04-11 11:46:18 -04:00
}
var kindHugoContext = ast . NewNodeKind ( "HugoContext" )
// HugoContext is a node that represents a Hugo context.
type HugoContext struct {
ast . BaseInline
Closing bool
// Internal page ID. Not persisted.
Pid uint64
}
// Dump implements Node.Dump.
func ( n * HugoContext ) Dump ( source [ ] byte , level int ) {
m := map [ string ] string { }
m [ "Pid" ] = fmt . Sprintf ( "%v" , n . Pid )
ast . DumpHelper ( n , source , level , m , nil )
}
func ( n * HugoContext ) parseAttrs ( attrBytes [ ] byte ) {
keyPairs := bytes . Split ( attrBytes , [ ] byte ( " " ) )
for _ , keyPair := range keyPairs {
kv := bytes . Split ( keyPair , [ ] byte ( "=" ) )
if len ( kv ) != 2 {
continue
}
key := string ( kv [ 0 ] )
val := string ( kv [ 1 ] )
switch key {
case "pid" :
pid , _ := strconv . ParseUint ( val , 10 , 64 )
n . Pid = pid
}
}
}
func ( h * HugoContext ) Kind ( ) ast . NodeKind {
return kindHugoContext
}
var (
2024-10-30 13:10:09 -04:00
hugoCtxPrefix = [ ] byte ( "{{__hugo_ctx" )
hugoCtxEndDelim = [ ] byte ( "}}" )
hugoCtxClosingDelim = [ ] byte ( "/}}" )
hugoCtxRe = regexp . MustCompile ( ` {{ __hugo_ctx ( pid = \ d + ) ? / ? }} \n? ` )
2024-04-11 11:46:18 -04:00
)
var _ parser . InlineParser = ( * hugoContextParser ) ( nil )
type hugoContextParser struct { }
2024-10-30 13:10:09 -04:00
func ( a * hugoContextParser ) Trigger ( ) [ ] byte {
return [ ] byte { '{' }
}
func ( s * hugoContextParser ) Parse ( parent ast . Node , reader text . Reader , pc parser . Context ) ast . Node {
line , _ := reader . PeekLine ( )
if ! bytes . HasPrefix ( line , hugoCtxPrefix ) {
2024-04-11 11:46:18 -04:00
return nil
}
2024-10-30 13:10:09 -04:00
end := bytes . Index ( line , hugoCtxEndDelim )
2024-04-11 11:46:18 -04:00
if end == - 1 {
return nil
}
2024-10-30 13:10:09 -04:00
reader . Advance ( end + len ( hugoCtxEndDelim ) + 1 ) // +1 for the newline
2024-04-11 11:46:18 -04:00
if line [ end - 1 ] == '/' {
return & HugoContext { Closing : true }
}
2024-10-30 13:10:09 -04:00
attrBytes := line [ len ( hugoCtxPrefix ) + 1 : end ]
2024-04-11 11:46:18 -04:00
h := & HugoContext { }
h . parseAttrs ( attrBytes )
return h
}
2024-10-30 13:10:09 -04:00
type hugoContextRenderer struct {
logger loggers . Logger
html . Config
2024-04-11 11:46:18 -04:00
}
2024-10-30 13:10:09 -04:00
func ( r * hugoContextRenderer ) SetOption ( name renderer . OptionName , value any ) {
r . Config . SetOption ( name , value )
}
2024-04-11 11:46:18 -04:00
func ( r * hugoContextRenderer ) RegisterFuncs ( reg renderer . NodeRendererFuncRegisterer ) {
reg . Register ( kindHugoContext , r . handleHugoContext )
2024-11-05 03:29:10 -05:00
reg . Register ( ast . KindRawHTML , r . renderRawHTML )
2024-10-30 13:10:09 -04:00
reg . Register ( ast . KindHTMLBlock , r . renderHTMLBlock )
}
func ( r * hugoContextRenderer ) stripHugoCtx ( b [ ] byte ) ( [ ] byte , bool ) {
if ! bytes . Contains ( b , hugoCtxPrefix ) {
return b , false
}
return hugoCtxRe . ReplaceAll ( b , nil ) , true
}
2024-11-05 03:29:10 -05:00
func ( r * hugoContextRenderer ) logRawHTMLEmittedWarn ( w util . BufWriter ) {
r . logger . Warnidf ( constants . WarnGoldmarkRawHTML , "Raw HTML omitted from %q; see https://gohugo.io/getting-started/configuration-markup/#rendererunsafe" , r . getPage ( w ) )
}
func ( r * hugoContextRenderer ) getPage ( w util . BufWriter ) any {
var p any
ctx , ok := w . ( * render . Context )
if ok {
p , _ = render . GetPageAndPageInner ( ctx )
}
return p
}
// HTML rendering based on Goldmark implementation.
2024-10-30 13:10:09 -04:00
func ( r * hugoContextRenderer ) renderHTMLBlock (
w util . BufWriter , source [ ] byte , node ast . Node , entering bool ,
) ( ast . WalkStatus , error ) {
n := node . ( * ast . HTMLBlock )
if entering {
if r . Unsafe {
l := n . Lines ( ) . Len ( )
for i := 0 ; i < l ; i ++ {
line := n . Lines ( ) . At ( i )
linev := line . Value ( source )
var stripped bool
linev , stripped = r . stripHugoCtx ( linev )
if stripped {
2024-11-05 03:29:10 -05:00
r . logger . Warnidf ( constants . WarnRenderShortcodesInHTML , ".RenderShortcodes detected inside HTML block in %q; this may not be what you intended, see https://gohugo.io/methods/page/rendershortcodes/#limitations" , r . getPage ( w ) )
2024-10-30 13:10:09 -04:00
}
r . Writer . SecureWrite ( w , linev )
}
} else {
2024-11-05 03:29:10 -05:00
r . logRawHTMLEmittedWarn ( w )
2024-10-30 13:10:09 -04:00
_ , _ = w . WriteString ( "<!-- raw HTML omitted -->\n" )
}
} else {
if n . HasClosure ( ) {
if r . Unsafe {
closure := n . ClosureLine
r . Writer . SecureWrite ( w , closure . Value ( source ) )
} else {
_ , _ = w . WriteString ( "<!-- raw HTML omitted -->\n" )
}
}
}
return ast . WalkContinue , nil
2024-04-11 11:46:18 -04:00
}
2024-11-05 03:29:10 -05:00
func ( r * hugoContextRenderer ) renderRawHTML (
w util . BufWriter , source [ ] byte , node ast . Node , entering bool ,
) ( ast . WalkStatus , error ) {
if ! entering {
return ast . WalkSkipChildren , nil
}
if r . Unsafe {
n := node . ( * ast . RawHTML )
l := n . Segments . Len ( )
for i := 0 ; i < l ; i ++ {
segment := n . Segments . At ( i )
_ , _ = w . Write ( segment . Value ( source ) )
}
return ast . WalkSkipChildren , nil
}
r . logRawHTMLEmittedWarn ( w )
_ , _ = w . WriteString ( "<!-- raw HTML omitted -->" )
return ast . WalkSkipChildren , nil
}
2024-04-11 11:46:18 -04:00
func ( r * hugoContextRenderer ) handleHugoContext ( w util . BufWriter , source [ ] byte , node ast . Node , entering bool ) ( ast . WalkStatus , error ) {
if ! entering {
return ast . WalkContinue , nil
}
hctx := node . ( * HugoContext )
ctx , ok := w . ( * render . Context )
if ! ok {
return ast . WalkContinue , nil
}
if hctx . Closing {
_ = ctx . PopPid ( )
} else {
ctx . PushPid ( hctx . Pid )
}
return ast . WalkContinue , nil
}
2024-10-30 13:10:09 -04:00
type hugoContextExtension struct {
logger loggers . Logger
}
2024-04-11 11:46:18 -04:00
func ( a * hugoContextExtension ) Extend ( m goldmark . Markdown ) {
m . Parser ( ) . AddOptions (
parser . WithInlineParsers (
util . Prioritized ( & hugoContextParser { } , 50 ) ,
) ,
)
m . Renderer ( ) . AddOptions (
renderer . WithNodeRenderers (
2024-10-30 13:10:09 -04:00
util . Prioritized ( & hugoContextRenderer {
logger : a . logger ,
Config : html . Config {
Writer : html . DefaultWriter ,
} ,
} , 50 ) ,
2024-04-11 11:46:18 -04:00
) ,
)
}