okami.mom/glamour
git clone https://git.nkp.pet/okami.mom/glamour
052c391
main
1package ansi 2 3import ( 4"bytes" 5"html" 6"io" 7"strings" 8 9"charm.land/glamour/v2/internal/autolink" 10 east"github.com/yuin/goldmark-emoji/ast" 11"github.com/yuin/goldmark/ast" 12 astext"github.com/yuin/goldmark/extension/ast" 13) 14 15// ElementRenderer is called when entering a markdown node. 16type ElementRenderer interface { 17Render (w io.Writer ,ctx RenderContext )error 18} 19 20// StyleOverriderElementRenderer is called when entering a markdown node with a specific style. 21type StyleOverriderElementRenderer interface { 22StyleOverrideRender (w io.Writer ,ctx RenderContext ,style StylePrimitive )error 23} 24 25// ElementFinisher is called when leaving a markdown node. 26type ElementFinisher interface { 27Finish (w io.Writer ,ctx RenderContext )error 28} 29 30// An Element is used to instruct the renderer how to handle individual markdown 31// nodes. 32type Element struct { 33Entering string 34Exiting string 35Renderer ElementRenderer 36Finisher ElementFinisher 37} 38 39// NewElement returns the appropriate render Element for a given node. 40func (tr * ANSIRenderer )NewElement (node ast.Node ,source []byte )Element { 41ctx := tr .context 42if element ,ok := ctx .options .Elements [node .Kind ()];ok { 43return element (node ,source ,ctx ) 44 } 45 46switch node .Kind () { 47// Document 48case ast .KindDocument : 49e := & BlockElement { 50Block :& bytes.Buffer {}, 51Style :ctx .options .Styles .Document , 52Margin :true , 53 } 54return Element { 55Renderer :e , 56Finisher :e , 57 } 58 59// Heading 60case ast .KindHeading : 61n := node .(* ast.Heading ) 62he := & HeadingElement { 63Level :n .Level , 64First :node .PreviousSibling ()== nil , 65 } 66return Element { 67Exiting :"" , 68Renderer :he , 69Finisher :he , 70 } 71 72// Paragraph 73case ast .KindParagraph : 74return Element { 75Renderer :& ParagraphElement { 76First :node .PreviousSibling ()== nil , 77 }, 78Finisher :& ParagraphElement {}, 79 } 80 81// Blockquote 82case ast .KindBlockquote : 83e := & BlockElement { 84Block :& bytes.Buffer {}, 85Style :cascadeStyle (ctx .blockStack .Current ().Style ,ctx .options .Styles .BlockQuote ,false ), 86Margin :true , 87 } 88return Element { 89Entering :"\n" , 90Renderer :e , 91Finisher :e , 92 } 93 94// Lists 95case ast .KindList : 96s := ctx .options .Styles .List .StyleBlock 97if s .Indent == nil { 98var i uint 99s .Indent = & i 100 } 101n := node .Parent () 102for n != nil { 103if n .Kind ()== ast .KindList { 104i := ctx .options .Styles .List .LevelIndent 105s .Indent = & i 106break 107 } 108n = n .Parent () 109 } 110 111e := & BlockElement { 112Block :& bytes.Buffer {}, 113Style :cascadeStyle (ctx .blockStack .Current ().Style ,s ,false ), 114Margin :true , 115Newline :true , 116 } 117return Element { 118Entering :"\n" , 119Renderer :e , 120Finisher :e , 121 } 122 123case ast .KindListItem : 124var l uint 125var e uint 126l = 1 127n := node 128for n .PreviousSibling ()!= nil && (n .PreviousSibling ().Kind ()== ast .KindListItem ) { 129l ++ 130n = n .PreviousSibling () 131 } 132if node .Parent ().(* ast.List ).IsOrdered () { 133e = l 134if node .Parent ().(* ast.List ).Start != 1 { 135e += uint (node .Parent ().(* ast.List ).Start )- 1 136 } 137 } 138 139post := "\n" 140if (node .LastChild ()!= nil && node .LastChild ().Kind ()== ast .KindList )|| 141node .NextSibling ()== nil { 142post = "" 143 } 144 145var marker ElementRenderer = & ItemElement { 146IsOrdered :node .Parent ().(* ast.List ).IsOrdered (), 147Enumeration :e , 148 } 149if node .FirstChild ()!= nil && 150node .FirstChild ().FirstChild ()!= nil && 151node .FirstChild ().FirstChild ().Kind ()== astext .KindTaskCheckBox { 152nc := node .FirstChild ().FirstChild ().(* astext.TaskCheckBox ) 153marker = & TaskElement {Checked :nc .IsChecked } 154 } 155item := & ListItemElement {Marker :marker } 156return Element { 157Exiting :post , 158Renderer :item , 159Finisher :item , 160 } 161 162// Text Elements 163case ast .KindText : 164n := node .(* ast.Text ) 165s := string (n .Segment .Value (source )) 166 167if n .HardLineBreak ()|| (n .SoftLineBreak ()) { 168s += "\n" 169 } 170return Element { 171Renderer :& BaseElement { 172Token :html .UnescapeString (s ), 173Style :ctx .options .Styles .Text , 174 }, 175 } 176 177case ast .KindEmphasis : 178n := node .(* ast.Emphasis ) 179var children []ElementRenderer 180nn := n .FirstChild () 181for nn != nil { 182children = append (children ,tr .NewElement (nn ,source ).Renderer ) 183nn = nn .NextSibling () 184 } 185return Element { 186Renderer :& EmphasisElement { 187Level :n .Level , 188Children :children , 189 }, 190 } 191 192case astext .KindStrikethrough : 193n := node .(* astext.Strikethrough ) 194s := string (n .Text (source ))//nolint: staticcheck 195style := ctx .options .Styles .Strikethrough 196 197return Element { 198Renderer :& BaseElement { 199Token :html .UnescapeString (s ), 200Style :style , 201 }, 202 } 203 204case ast .KindThematicBreak : 205return Element { 206Entering :"" , 207Exiting :"" , 208Renderer :& BaseElement { 209Style :ctx .options .Styles .HorizontalRule , 210 }, 211 } 212 213// Links 214case ast .KindLink : 215n := node .(* ast.Link ) 216isFooterLinks := ! ctx .options .InlineTableLinks && isInsideTable (node ) 217 218var children []ElementRenderer 219content ,err := nodeContent (node ,source ) 220 221if isFooterLinks && err == nil { 222text := string (content ) 223tl := tableLink { 224content :text , 225href :string (n .Destination ), 226title :string (n .Title ), 227linkType :linkTypeRegular , 228 } 229text = linkWithSuffix (tl ,ctx .table .tableLinks ) 230children = []ElementRenderer {& BaseElement {Token :text }} 231 }else { 232nn := n .FirstChild () 233for nn != nil { 234children = append (children ,tr .NewElement (nn ,source ).Renderer ) 235nn = nn .NextSibling () 236 } 237 } 238 239return Element { 240Renderer :& LinkElement { 241BaseURL :ctx .options .BaseURL , 242URL :string (n .Destination ), 243Children :children , 244SkipHref :isFooterLinks , 245 }, 246 } 247case ast .KindAutoLink : 248n := node .(* ast.AutoLink ) 249u := string (n .URL (source )) 250isFooterLinks := ! ctx .options .InlineTableLinks && isInsideTable (node ) 251 252var children []ElementRenderer 253nn := n .FirstChild () 254for nn != nil { 255children = append (children ,tr .NewElement (nn ,source ).Renderer ) 256nn = nn .NextSibling () 257 } 258 259if len (children )== 0 { 260children = append (children ,& BaseElement {Token :u }) 261 } 262 263if n .AutoLinkType == ast .AutoLinkEmail && ! strings .HasPrefix (strings .ToLower (u ),"mailto:" ) { 264u = "mailto:" + u 265 } 266 267var renderer ElementRenderer 268if isFooterLinks { 269domain := linkDomain (u ) 270tl := tableLink { 271content :domain , 272href :u , 273linkType :linkTypeAuto , 274 } 275if shortned ,ok := autolink .Detect (u );ok { 276tl .content = shortned 277 } 278text := linkWithSuffix (tl ,ctx .table .tableLinks ) 279 280renderer = & LinkElement { 281Children : []ElementRenderer {& BaseElement {Token :text }}, 282URL :u , 283SkipHref :true , 284 } 285 }else { 286isEmail := n .AutoLinkType == ast .AutoLinkEmail 287renderer = & LinkElement { 288Children :children , 289URL :u , 290SkipText :! isEmail ,// For non-email links, skip text (show only href) 291SkipHref :isEmail ,// For email links, skip href (hide mailto: URL) 292 } 293 } 294return Element {Renderer :renderer } 295 296// Images 297case ast .KindImage : 298n := node .(* ast.Image ) 299text := string (n .Text (source ))//nolint: staticcheck 300isFooterLinks := ! ctx .options .InlineTableLinks && isInsideTable (node ) 301 302if isFooterLinks { 303if text == "" { 304text = linkDomain (string (n .Destination )) 305 } 306tl := tableLink { 307title :string (n .Title ), 308content :text , 309href :string (n .Destination ), 310linkType :linkTypeImage , 311 } 312text = linkWithSuffix (tl ,ctx .table .tableImages ) 313 } 314 315return Element { 316Renderer :& ImageElement { 317Text :text , 318BaseURL :ctx .options .BaseURL , 319URL :string (n .Destination ), 320TextOnly :isFooterLinks , 321 }, 322 } 323 324// Code 325case ast .KindFencedCodeBlock : 326n := node .(* ast.FencedCodeBlock ) 327return Element { 328Entering :"\n" , 329Renderer :& CodeBlockElement { 330Code :string (n .Lines ().Value (source )), 331Language :string (n .Language (source )), 332 }, 333 } 334 335case ast .KindCodeBlock : 336n := node .(* ast.CodeBlock ) 337return Element { 338Entering :"\n" , 339Renderer :& CodeBlockElement { 340Code :string (n .Lines ().Value (source )), 341 }, 342 } 343 344case ast .KindCodeSpan : 345n := node .(* ast.CodeSpan ) 346s := string (n .Text (source ))//nolint: staticcheck 347return Element { 348Renderer :& CodeSpanElement { 349Text :html .UnescapeString (s ), 350Style :cascadeStyle (ctx .blockStack .Current ().Style ,ctx .options .Styles .Code ,false ).StylePrimitive , 351 }, 352 } 353 354// Tables 355case astext .KindTable : 356table := node .(* astext.Table ) 357te := & TableElement { 358table :table , 359source :source , 360 } 361return Element { 362Entering :"\n" , 363Exiting :"\n" , 364Renderer :te , 365Finisher :te , 366 } 367 368case astext .KindTableCell : 369n := node .(* astext.TableCell ) 370var children []ElementRenderer 371nn := n .FirstChild () 372for nn != nil { 373children = append (children ,tr .NewElement (nn ,source ).Renderer ) 374nn = nn .NextSibling () 375 } 376 377r := & TableCellElement { 378Children :children , 379Head :node .Parent ().Kind ()== astext .KindTableHeader , 380 } 381return Element { 382Renderer :r , 383 } 384 385case astext .KindTableHeader : 386return Element { 387Finisher :& TableHeadElement {}, 388 } 389case astext .KindTableRow : 390return Element { 391Finisher :& TableRowElement {}, 392 } 393 394// HTML Elements 395case ast .KindHTMLBlock : 396n := node .(* ast.HTMLBlock ) 397return Element { 398Renderer :& BaseElement { 399Token :ctx .SanitizeHTML (string (n .Text (source )),true ),//nolint: staticcheck 400Style :ctx .options .Styles .HTMLBlock .StylePrimitive , 401 }, 402 } 403case ast .KindRawHTML : 404n := node .(* ast.RawHTML ) 405return Element { 406Renderer :& BaseElement { 407Token :ctx .SanitizeHTML (string (n .Text (source )),true ),//nolint: staticcheck 408Style :ctx .options .Styles .HTMLSpan .StylePrimitive , 409 }, 410 } 411 412// Definition Lists 413case astext .KindDefinitionList : 414e := & BlockElement { 415Block :& bytes.Buffer {}, 416Style :cascadeStyle (ctx .blockStack .Current ().Style ,ctx .options .Styles .DefinitionList ,false ), 417Margin :true , 418Newline :true , 419 } 420return Element { 421Renderer :e , 422Finisher :e , 423 } 424 425case astext .KindDefinitionTerm : 426return Element { 427Entering :"\n" , 428Renderer :& BaseElement { 429Style :ctx .options .Styles .DefinitionTerm , 430 }, 431 } 432 433case astext .KindDefinitionDescription : 434return Element { 435Exiting :"\n" , 436Renderer :& BaseElement { 437Style :ctx .options .Styles .DefinitionDescription , 438 }, 439 } 440 441// Handled by parents 442case astext .KindTaskCheckBox : 443// handled by KindListItem 444return Element {} 445case ast .KindTextBlock : 446return Element {} 447 448case east .KindEmoji : 449n := node .(* east.Emoji ) 450return Element { 451Renderer :& BaseElement { 452Token :string (n .Value .Unicode ), 453 }, 454 } 455 456// Unknown kinds render nothing; their children are still walked. 457default : 458return Element {} 459 } 460}