Get bbcode working in Goldmark
This commit is contained in:
parent
085bd46440
commit
540d1765db
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require (
|
||||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||||
github.com/Masterminds/semver v1.5.0 // indirect
|
github.com/Masterminds/semver v1.5.0 // indirect
|
||||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
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/go-stack/stack v1.8.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/huandu/xstrings v1.3.2 // indirect
|
github.com/huandu/xstrings v1.3.2 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -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/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/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/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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
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=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
|
Binary file not shown.
|
@ -1,67 +1,160 @@
|
||||||
package parsing
|
package parsing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/frustra/bbcode"
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"github.com/yuin/goldmark/ast"
|
|
||||||
gast "github.com/yuin/goldmark/ast"
|
gast "github.com/yuin/goldmark/ast"
|
||||||
"github.com/yuin/goldmark/parser"
|
"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/text"
|
||||||
"github.com/yuin/goldmark/util"
|
"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{'['}
|
return []byte{'['}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s bParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
func (s bbcodeParser) Parse(parent gast.Node, block text.Reader, pc parser.Context) gast.Node {
|
||||||
// _, segment := block.PeekLine()
|
_, pos := block.Position()
|
||||||
// start := segment.Start
|
restOfSource := block.Source()[pos.Start:]
|
||||||
|
|
||||||
// block.Advance(3)
|
matches := reTag.FindAllSubmatchIndex(restOfSource, -1)
|
||||||
// n := ast.NewTextSegment(text.NewSegment(start, start+4))
|
if matches == nil {
|
||||||
// bold := ast.NewText()
|
// No tags anywhere
|
||||||
// bold.Segment
|
|
||||||
// link := ast.NewAutoLink(typ, n)
|
|
||||||
// link.Protocol = protocol
|
|
||||||
// return link
|
|
||||||
|
|
||||||
lineBytes, _ := block.PeekLine()
|
|
||||||
|
|
||||||
line := string(lineBytes)
|
|
||||||
|
|
||||||
if !strings.HasPrefix(line, "[b]") {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
start := 0
|
|
||||||
|
|
||||||
closingIndex := strings.Index(line, "[/b]")
|
otIndex := reTag.SubexpIndex("opentagname")
|
||||||
if closingIndex < 0 {
|
ctIndex := reTag.SubexpIndex("closetagname")
|
||||||
|
|
||||||
|
tagName := extractStringBySubmatchIndices(restOfSource, matches[0], otIndex)
|
||||||
|
if tagName == "" {
|
||||||
|
// Not an opening tag
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
end := closingIndex + 4
|
|
||||||
|
|
||||||
n := ast.NewEmphasis(2)
|
depth := 0
|
||||||
n.AppendChild(n, ast.NewString([]byte("wow bold text")))
|
endIndex := -1
|
||||||
|
for _, m := range matches {
|
||||||
block.Advance(end - start)
|
if openName := extractStringBySubmatchIndices(restOfSource, m, otIndex); openName != "" {
|
||||||
return n
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
type bTag struct{}
|
unparsedBBCode := restOfSource[:endIndex]
|
||||||
|
block.Advance(len(unparsedBBCode))
|
||||||
|
|
||||||
func (e bTag) Extend(m goldmark.Markdown) {
|
return NewBBCode(bbcodeCompiler.Compile(string(unparsedBBCode)))
|
||||||
|
}
|
||||||
|
|
||||||
|
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]])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// 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(
|
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),
|
|
||||||
// ))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,94 +2,11 @@ package parsing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/yuin/goldmark"
|
"github.com/yuin/goldmark"
|
||||||
"github.com/yuin/goldmark/extension"
|
"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(
|
var previewMarkdown = goldmark.New(
|
||||||
goldmark.WithExtensions(
|
goldmark.WithExtensions(
|
||||||
extension.GFM,
|
extension.GFM,
|
||||||
|
@ -98,7 +15,7 @@ var previewMarkdown = goldmark.New(
|
||||||
Preview: true,
|
Preview: true,
|
||||||
},
|
},
|
||||||
MathjaxExtension{},
|
MathjaxExtension{},
|
||||||
bTag{},
|
BBCodeExtension{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var realMarkdown = goldmark.New(
|
var realMarkdown = goldmark.New(
|
||||||
|
@ -107,7 +24,7 @@ var realMarkdown = goldmark.New(
|
||||||
SpoilerExtension{},
|
SpoilerExtension{},
|
||||||
EmbedExtension{},
|
EmbedExtension{},
|
||||||
MathjaxExtension{},
|
MathjaxExtension{},
|
||||||
bTag{},
|
BBCodeExtension{},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue