Get bbcode working in Goldmark

This commit is contained in:
Ben Visness 2021-06-20 15:46:42 -05:00
parent 085bd46440
commit 540d1765db
5 changed files with 134 additions and 121 deletions

1
go.mod
View File

@ -6,6 +6,7 @@ require (
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 // indirect
github.com/go-stack/stack v1.8.0
github.com/google/uuid v1.2.0
github.com/huandu/xstrings v1.3.2 // indirect

2
go.sum
View File

@ -49,6 +49,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8 h1:sdIsYe6Vv7KIWZWp8KqSeTl+XlF17d+wHCC4lbxFcYs=
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8/go.mod h1:0QBxkXxN+o4FyZgLI9FHY/oUizheze3+bNY/kgCKL+4=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=

Binary file not shown.

View File

@ -1,67 +1,160 @@
package parsing
import (
"strings"
"regexp"
"github.com/frustra/bbcode"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
gast "github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/parser"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/renderer/html"
"github.com/yuin/goldmark/text"
"github.com/yuin/goldmark/util"
)
const BBCodePriority = 1 // TODO: Pick something more reasonable?
var BBCodePriority = 1 // TODO: This is maybe too high a priority?
type bParser struct{}
var reTag = regexp.MustCompile(`(?P<open>\[\s*(?P<opentagname>[a-zA-Z]+))|(?P<close>\[\s*\/\s*(?P<closetagname>[a-zA-Z]+)\s*\])`)
var _ parser.InlineParser = bParser{}
var bbcodeCompiler = bbcode.NewCompiler(false, false)
func (s bParser) Trigger() []byte {
// ----------------------
// Parser and delimiters
// ----------------------
type bbcodeParser struct{}
func NewBBCodeParser() parser.InlineParser {
return bbcodeParser{}
}
func (s bbcodeParser) Trigger() []byte {
return []byte{'['}
}
func (s bParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
// _, segment := block.PeekLine()
// start := segment.Start
func (s bbcodeParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
_, pos := block.Position()
restOfSource := block.Source()[pos.Start:]
// block.Advance(3)
// n := ast.NewTextSegment(text.NewSegment(start, start+4))
// bold := ast.NewText()
// bold.Segment
// link := ast.NewAutoLink(typ, n)
// link.Protocol = protocol
// return link
lineBytes, _ := block.PeekLine()
line := string(lineBytes)
if !strings.HasPrefix(line, "[b]") {
matches := reTag.FindAllSubmatchIndex(restOfSource, -1)
if matches == nil {
// No tags anywhere
return nil
}
start := 0
closingIndex := strings.Index(line, "[/b]")
if closingIndex < 0 {
otIndex := reTag.SubexpIndex("opentagname")
ctIndex := reTag.SubexpIndex("closetagname")
tagName := extractStringBySubmatchIndices(restOfSource, matches[0], otIndex)
if tagName == "" {
// Not an opening tag
return nil
}
end := closingIndex + 4
n := ast.NewEmphasis(2)
n.AppendChild(n, ast.NewString([]byte("wow bold text")))
depth := 0
endIndex := -1
for _, m := range matches {
if openName := extractStringBySubmatchIndices(restOfSource, m, otIndex); openName != "" {
if openName == tagName {
depth++
}
} else if closeName := extractStringBySubmatchIndices(restOfSource, m, ctIndex); closeName != "" {
if closeName == tagName {
depth--
if depth == 0 {
// We have balanced out!
endIndex = m[1] // the end index of this closing tag (exclusive)
}
}
}
}
if endIndex < 0 {
// Unbalanced, too many opening tags
return nil
}
block.Advance(end - start)
return n
unparsedBBCode := restOfSource[:endIndex]
block.Advance(len(unparsedBBCode))
return NewBBCode(bbcodeCompiler.Compile(string(unparsedBBCode)))
}
type bTag struct{}
func extractStringBySubmatchIndices(src []byte, m []int, subexpIndex int) string {
srcIndices := m[2*subexpIndex : 2*subexpIndex+1+1]
if srcIndices[0] < 0 {
return ""
}
return string(src[srcIndices[0]:srcIndices[1]])
}
func (e bTag) Extend(m goldmark.Markdown) {
// ----------------------
// AST node
// ----------------------
type BBCodeNode struct {
gast.BaseInline
HTML string
}
var _ gast.Node = &BBCodeNode{}
func (n *BBCodeNode) Dump(source []byte, level int) {
gast.DumpHelper(n, source, level, nil, nil)
}
var KindBBCode = gast.NewNodeKind("BBCode")
func (n *BBCodeNode) Kind() gast.NodeKind {
return KindBBCode
}
func NewBBCode(html string) gast.Node {
return &BBCodeNode{
HTML: html,
}
}
// ----------------------
// Renderer
// ----------------------
type BBCodeHTMLRenderer struct {
html.Config
}
func NewBBCodeHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
r := &BBCodeHTMLRenderer{
Config: html.NewConfig(),
}
for _, opt := range opts {
opt.SetHTMLOption(&r.Config)
}
return r
}
func (r *BBCodeHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
reg.Register(KindBBCode, r.renderBBCode)
}
func (r *BBCodeHTMLRenderer) renderBBCode(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
if entering {
w.WriteString(n.(*BBCodeNode).HTML)
}
return gast.WalkContinue, nil
}
// ----------------------
// Extension
// ----------------------
type BBCodeExtension struct{}
func (e BBCodeExtension) Extend(m goldmark.Markdown) {
m.Parser().AddOptions(parser.WithInlineParsers(
util.Prioritized(bParser{}, BBCodePriority),
util.Prioritized(NewBBCodeParser(), BBCodePriority),
))
m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(NewBBCodeHTMLRenderer(), BBCodePriority),
))
// m.Renderer().AddOptions(renderer.WithNodeRenderers(
// util.Prioritized(NewStrikethroughHTMLRenderer(), 500),
// ))
}

View File

@ -2,94 +2,11 @@ package parsing
import (
"bytes"
"regexp"
"sort"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
)
func StripNames(regexStr string) string {
return regexp.MustCompile(`\(\?P<[a-zA-Z0-9_]+>`).ReplaceAllString(regexStr, `(?:`)
}
var reArgStr = `(?P<name>[a-zA-Z0-9]+)(?:\s*=\s*(?:'(?P<single_quoted_val>.*?)'|"(?P<double_quoted_val>.*?)"|(?P<bare_val>[^\s\]]+)))?`
var reTagOpenStr = `\[\s*(?P<args>(?:` + StripNames(reArgStr) + `)(?:\s+(?:` + StripNames(reArgStr) + `))*)\s*\]`
var reTagCloseStr = `\[/\s*(?P<name>[a-zA-Z0-9]+)?\s*\]`
var reArg = regexp.MustCompile(reArgStr)
var reTagOpen = regexp.MustCompile(reTagOpenStr)
var reTagClose = regexp.MustCompile(reTagCloseStr)
const tokenTypeString = "string"
const tokenTypeOpenTag = "openTag"
const tokenTypeCloseTag = "closeTag"
type token struct {
Type string
StartIndex int
EndIndex int
Contents string
}
func ParseBBCode(input string) string {
return input
}
func tokenizeBBCode(input string) []token {
openMatches := reTagOpen.FindAllStringIndex(input, -1)
closeMatches := reTagClose.FindAllStringIndex(input, -1)
// Build tokens from regex matches
var tagTokens []token
for _, match := range openMatches {
tagTokens = append(tagTokens, token{
Type: tokenTypeOpenTag,
StartIndex: match[0],
EndIndex: match[1],
Contents: input[match[0]:match[1]],
})
}
for _, match := range closeMatches {
tagTokens = append(tagTokens, token{
Type: tokenTypeCloseTag,
StartIndex: match[0],
EndIndex: match[1],
Contents: input[match[0]:match[1]],
})
}
// Sort those tokens together
sort.Slice(tagTokens, func(i, j int) bool {
return tagTokens[i].StartIndex < tagTokens[j].StartIndex
})
// Make a new list of tokens that fills in the gaps with plain old text
var tokens []token
for i, tagToken := range tagTokens {
prevEnd := 0
if i > 0 {
prevEnd = tagTokens[i-1].EndIndex
}
tokens = append(tokens, token{
Type: tokenTypeString,
StartIndex: prevEnd,
EndIndex: tagToken.StartIndex,
Contents: input[prevEnd:tagToken.StartIndex],
})
tokens = append(tokens, tagToken)
}
tokens = append(tokens, token{
Type: tokenTypeString,
StartIndex: tokens[len(tokens)-1].EndIndex,
EndIndex: len(input),
Contents: input[tokens[len(tokens)-1].EndIndex:],
})
return tokens
}
var previewMarkdown = goldmark.New(
goldmark.WithExtensions(
extension.GFM,
@ -98,7 +15,7 @@ var previewMarkdown = goldmark.New(
Preview: true,
},
MathjaxExtension{},
bTag{},
BBCodeExtension{},
),
)
var realMarkdown = goldmark.New(
@ -107,7 +24,7 @@ var realMarkdown = goldmark.New(
SpoilerExtension{},
EmbedExtension{},
MathjaxExtension{},
bTag{},
BBCodeExtension{},
),
)