mirror of
https://github.com/kovetskiy/mark.git
synced 2025-04-23 21:32:41 +08:00
190 lines
4.3 KiB
Go
190 lines
4.3 KiB
Go
package renderer
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/kovetskiy/mark/attachment"
|
|
"github.com/kovetskiy/mark/mermaid"
|
|
"github.com/kovetskiy/mark/stdlib"
|
|
"github.com/reconquest/pkg/log"
|
|
|
|
"github.com/yuin/goldmark/ast"
|
|
"github.com/yuin/goldmark/renderer"
|
|
"github.com/yuin/goldmark/renderer/html"
|
|
"github.com/yuin/goldmark/util"
|
|
)
|
|
|
|
type ConfluenceFencedCodeBlockRenderer struct {
|
|
html.Config
|
|
Stdlib *stdlib.Lib
|
|
MermaidProvider string
|
|
MermaidScale float64
|
|
Attachments attachment.Attacher
|
|
}
|
|
|
|
var reBlockDetails = regexp.MustCompile(
|
|
// (<Lang>|-) (collapse|<theme>|\d)* (title <title>)?
|
|
|
|
`^(?:(\w*)|-)\s*\b(\S.*?\S?)??\s*(?:\btitle\s+(\S.*\S?))?$`,
|
|
)
|
|
|
|
// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
|
|
func NewConfluenceFencedCodeBlockRenderer(stdlib *stdlib.Lib, attachments attachment.Attacher, mermaidProvider string, mermaidScale float64, opts ...html.Option) renderer.NodeRenderer {
|
|
return &ConfluenceFencedCodeBlockRenderer{
|
|
Config: html.NewConfig(),
|
|
Stdlib: stdlib,
|
|
MermaidProvider: mermaidProvider,
|
|
MermaidScale: mermaidScale,
|
|
Attachments: attachments,
|
|
}
|
|
}
|
|
|
|
// RegisterFuncs implements NodeRenderer.RegisterFuncs .
|
|
func (r *ConfluenceFencedCodeBlockRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
|
reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
|
|
}
|
|
|
|
func ParseLanguage(lang string) string {
|
|
// lang takes the following form: language? "collapse"? ("title"? <any string>*)?
|
|
// let's split it by spaces
|
|
paramlist := strings.Fields(lang)
|
|
|
|
// get the word in question, aka the first one
|
|
first := lang
|
|
if len(paramlist) > 0 {
|
|
first = paramlist[0]
|
|
}
|
|
|
|
if first == "collapse" || first == "title" {
|
|
// collapsing or including a title without a language
|
|
return ""
|
|
}
|
|
// the default case with language being the first one
|
|
return first
|
|
}
|
|
|
|
func ParseTitle(lang string) string {
|
|
index := strings.Index(lang, "title")
|
|
if index >= 0 {
|
|
// it's found, check if title is given and return it
|
|
start := index + 6
|
|
if len(lang) > start {
|
|
return lang[start:]
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// renderFencedCodeBlock renders a FencedCodeBlock
|
|
func (r *ConfluenceFencedCodeBlockRenderer) renderFencedCodeBlock(writer util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
|
if !entering {
|
|
return ast.WalkContinue, nil
|
|
}
|
|
var info []byte
|
|
nodeFencedCodeBlock := node.(*ast.FencedCodeBlock)
|
|
if nodeFencedCodeBlock.Info != nil {
|
|
segment := nodeFencedCodeBlock.Info.Segment
|
|
info = segment.Value(source)
|
|
}
|
|
groups := reBlockDetails.FindStringSubmatch(string(info))
|
|
linenumbers := false
|
|
firstline := 0
|
|
theme := ""
|
|
collapse := false
|
|
lang := ""
|
|
var options []string
|
|
title := ""
|
|
if len(groups) > 0 {
|
|
lang, options, title = groups[1], strings.Fields(groups[2]), groups[3]
|
|
for _, option := range options {
|
|
if option == "collapse" {
|
|
collapse = true
|
|
continue
|
|
}
|
|
if option == "nocollapse" {
|
|
collapse = false
|
|
continue
|
|
}
|
|
var i int
|
|
if _, err := fmt.Sscanf(option, "%d", &i); err == nil {
|
|
linenumbers = i > 0
|
|
firstline = i
|
|
continue
|
|
}
|
|
theme = option
|
|
}
|
|
|
|
}
|
|
|
|
var lval []byte
|
|
|
|
lines := node.Lines().Len()
|
|
for i := 0; i < lines; i++ {
|
|
line := node.Lines().At(i)
|
|
lval = append(lval, line.Value(source)...)
|
|
}
|
|
|
|
if lang == "mermaid" && r.MermaidProvider == "mermaid-go" {
|
|
attachment, err := mermaid.ProcessMermaidLocally(title, lval, r.MermaidScale)
|
|
if err != nil {
|
|
log.Debugf(nil, "error: %v", err)
|
|
return ast.WalkStop, err
|
|
}
|
|
r.Attachments.Attach(attachment)
|
|
err = r.Stdlib.Templates.ExecuteTemplate(
|
|
writer,
|
|
"ac:image",
|
|
struct {
|
|
Width string
|
|
Height string
|
|
Title string
|
|
Alt string
|
|
Attachment string
|
|
Url string
|
|
}{
|
|
attachment.Width,
|
|
attachment.Height,
|
|
attachment.Name,
|
|
"",
|
|
attachment.Filename,
|
|
"",
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
|
|
} else {
|
|
err := r.Stdlib.Templates.ExecuteTemplate(
|
|
writer,
|
|
"ac:code",
|
|
struct {
|
|
Language string
|
|
Collapse bool
|
|
Title string
|
|
Theme string
|
|
Linenumbers bool
|
|
Firstline int
|
|
Text string
|
|
}{
|
|
lang,
|
|
collapse,
|
|
title,
|
|
theme,
|
|
linenumbers,
|
|
firstline,
|
|
strings.TrimSuffix(string(lval), "\n"),
|
|
},
|
|
)
|
|
|
|
if err != nil {
|
|
return ast.WalkStop, err
|
|
}
|
|
}
|
|
|
|
return ast.WalkContinue, nil
|
|
}
|