ggcode is online
This commit is contained in:
parent
5b1bf5951a
commit
7268b6011a
Binary file not shown.
|
@ -104,6 +104,38 @@ func extract(re *regexp.Regexp, src []byte, subexpName string) []byte {
|
||||||
return m[re.SubexpIndex(subexpName)]
|
return m[re.SubexpIndex(subexpName)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractMap(re *regexp.Regexp, src []byte, subexpNames ...string) map[string][]byte {
|
||||||
|
m := re.FindSubmatch(src)
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
res := make(map[string][]byte)
|
||||||
|
for _, name := range subexpNames {
|
||||||
|
res[name] = m[re.SubexpIndex(name)]
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAll(re *regexp.Regexp, src []byte, subexpName string) [][]byte {
|
||||||
|
m := re.FindAllSubmatch(src, -1)
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return m[re.SubexpIndex(subexpName)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAllMap(re *regexp.Regexp, src []byte, subexpNames ...string) map[string][][]byte {
|
||||||
|
m := re.FindAllSubmatch(src, -1)
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
res := make(map[string][][]byte)
|
||||||
|
for _, name := range subexpNames {
|
||||||
|
res[name] = m[re.SubexpIndex(name)]
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
func makeYoutubeEmbed(vid string, preview bool) string {
|
func makeYoutubeEmbed(vid string, preview bool) string {
|
||||||
if preview {
|
if preview {
|
||||||
return `
|
return `
|
||||||
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
package parsing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE(ben): ggcode is my cute name for our custom extension syntax because I got fed up with
|
||||||
|
// bbcode. It's designed to be a more natural fit for Goldmark's method of parsing, while still
|
||||||
|
// being a general-purpose tag-like syntax that's easy for us to add instances of without writing
|
||||||
|
// new Goldmark parsers.
|
||||||
|
//
|
||||||
|
// Inline ggcode is delimited by two exclamation marks. Block ggcode is delimited by three. Inline
|
||||||
|
// ggcode uses parentheses to delimit the start and end of the affected content. Block ggcode is
|
||||||
|
// like a fenced code block and ends with !!!. ggcode sections can optionally have named string
|
||||||
|
// arguments inside braces. Quotes around the value are mandatory.
|
||||||
|
//
|
||||||
|
// Inline example:
|
||||||
|
//
|
||||||
|
// See our article on !!glossary{slug="tcp"}(TCP) for more details.
|
||||||
|
//
|
||||||
|
// Block example:
|
||||||
|
//
|
||||||
|
// !!!resource{name="Beej's Guide to Network Programming" url="https://beej.us/guide/bgnet/html/"}
|
||||||
|
// This is a _fantastic_ resource on network programming, suitable for beginners.
|
||||||
|
// !!!
|
||||||
|
//
|
||||||
|
|
||||||
|
type ggcodeRenderer = func(w util.BufWriter, n *ggcodeNode, entering bool) error
|
||||||
|
|
||||||
|
var ggcodeTags = map[string]ggcodeRenderer{
|
||||||
|
"resource": func(w util.BufWriter, n *ggcodeNode, entering bool) error {
|
||||||
|
if entering {
|
||||||
|
w.WriteString("a node :o")
|
||||||
|
} else {
|
||||||
|
w.WriteString("bai")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Parsers and delimiters
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
var reGGCodeBlockOpen = regexp.MustCompile(`^!!!(?P<name>[a-zA-Z0-9-_]+)(\{(?P<args>.*?)\})?$`)
|
||||||
|
var reGGCodeBlockArgs = regexp.MustCompile(`(?P<arg>[a-zA-Z0-9-_]+)="(?P<val>.*?)"`)
|
||||||
|
|
||||||
|
type ggcodeParserBlock struct {
|
||||||
|
Preview bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ parser.BlockParser = ggcodeParserBlock{}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) Trigger() []byte {
|
||||||
|
return []byte("!!!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
|
||||||
|
restOfLine, _ := reader.PeekLine()
|
||||||
|
|
||||||
|
if match := extractMap(reGGCodeBlockOpen, bytes.TrimSpace(restOfLine), "name", "args"); match != nil {
|
||||||
|
name := string(match["name"])
|
||||||
|
var args map[string]string
|
||||||
|
if argsMatch := extractAllMap(reGGCodeBlockArgs, match["args"]); argsMatch != nil {
|
||||||
|
args = make(map[string]string)
|
||||||
|
for i := range argsMatch["arg"] {
|
||||||
|
arg := string(argsMatch["arg"][i])
|
||||||
|
val := string(argsMatch["val"][i])
|
||||||
|
args[arg] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader.Advance(len(restOfLine))
|
||||||
|
return &ggcodeNode{
|
||||||
|
Name: name,
|
||||||
|
Args: args,
|
||||||
|
}, parser.Continue | parser.HasChildren
|
||||||
|
} else {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
|
||||||
|
line, _ := reader.PeekLine()
|
||||||
|
if string(bytes.TrimSpace(line)) == "!!!" {
|
||||||
|
reader.Advance(3)
|
||||||
|
return parser.Close
|
||||||
|
}
|
||||||
|
return parser.Continue | parser.HasChildren
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) Close(node ast.Node, reader text.Reader, pc parser.Context) {}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) CanInterruptParagraph() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ggcodeParserBlock) CanAcceptIndentedLine() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// AST node
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type ggcodeNode struct {
|
||||||
|
gast.BaseBlock
|
||||||
|
Name string
|
||||||
|
Args map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ast.Node = &ggcodeNode{}
|
||||||
|
|
||||||
|
func (n *ggcodeNode) Dump(source []byte, level int) {
|
||||||
|
gast.DumpHelper(n, source, level, n.Args, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var kindGGCode = gast.NewNodeKind("ggcode")
|
||||||
|
|
||||||
|
func (n *ggcodeNode) Kind() gast.NodeKind {
|
||||||
|
return kindGGCode
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Renderer
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type ggcodeHTMLRenderer struct {
|
||||||
|
html.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGGCodeHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
|
||||||
|
r := &ggcodeHTMLRenderer{
|
||||||
|
Config: html.NewConfig(),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.SetHTMLOption(&r.Config)
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ggcodeHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(kindGGCode, r.render)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ggcodeHTMLRenderer) render(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
|
node := n.(*ggcodeNode)
|
||||||
|
var renderer ggcodeRenderer = defaultGGCodeRenderer
|
||||||
|
if tagRenderer, ok := ggcodeTags[node.Name]; ok {
|
||||||
|
renderer = tagRenderer
|
||||||
|
}
|
||||||
|
err := renderer(w, node, entering)
|
||||||
|
return gast.WalkContinue, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultGGCodeRenderer(w util.BufWriter, n *ggcodeNode, entering bool) error {
|
||||||
|
if entering {
|
||||||
|
w.WriteString("wat is this tag >:(")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Extension
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
type ggcodeExtension struct {
|
||||||
|
Preview bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ggcodeExtension) Extend(m goldmark.Markdown) {
|
||||||
|
m.Parser().AddOptions(parser.WithBlockParsers(
|
||||||
|
util.Prioritized(ggcodeParserBlock{Preview: e.Preview}, 500),
|
||||||
|
))
|
||||||
|
m.Renderer().AddOptions(renderer.WithNodeRenderers(
|
||||||
|
util.Prioritized(newGGCodeHTMLRenderer(), 500),
|
||||||
|
))
|
||||||
|
}
|
|
@ -136,6 +136,9 @@ func makeGoldmarkExtensions(opts MarkdownOptions) []goldmark.Extender {
|
||||||
Preview: opts.Previews,
|
Preview: opts.Previews,
|
||||||
Education: opts.Education,
|
Education: opts.Education,
|
||||||
},
|
},
|
||||||
|
ggcodeExtension{
|
||||||
|
Preview: opts.Previews,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
return extenders
|
return extenders
|
||||||
|
|
Loading…
Reference in New Issue