From f1700b7c8898dc349ed5f1af2d140818f83674b0 Mon Sep 17 00:00:00 2001 From: Miek Gieben Date: Mon, 30 Jul 2018 17:19:39 +0100 Subject: [PATCH] WIP: Mmark: implement asides This implements asides, blockquote like paragraph prexifed with `A>` instead of just a '>' Adds the extension MmarkAsides Signed-off-by: Miek Gieben --- ast/node.go | 5 ++++ html/renderer.go | 11 ++++++-- mmark_test.go | 7 +++-- parser/aside.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++ parser/block.go | 11 ++++++++ parser/parser.go | 1 + 6 files changed, 101 insertions(+), 5 deletions(-) create mode 100644 parser/aside.go diff --git a/ast/node.go b/ast/node.go index 1725bf2..5dc9f63 100644 --- a/ast/node.go +++ b/ast/node.go @@ -129,6 +129,11 @@ type BlockQuote struct { Container } +// Aside represents an markdown aside node. +type Aside struct { + Container +} + // List represents markdown list node type List struct { Container diff --git a/html/renderer.go b/html/renderer.go index c35a845..0405775 100644 --- a/html/renderer.go +++ b/html/renderer.go @@ -536,7 +536,7 @@ func (r *Renderer) paragraphEnter(w io.Writer, para *ast.Paragraph) { prev := ast.GetPrevNode(para) if prev != nil { switch prev.(type) { - case *ast.HTMLBlock, *ast.List, *ast.Paragraph, *ast.Heading, *ast.CodeBlock, *ast.BlockQuote, *ast.HorizontalRule: + case *ast.HTMLBlock, *ast.List, *ast.Paragraph, *ast.Heading, *ast.CodeBlock, *ast.BlockQuote, *ast.Aside, *ast.HorizontalRule: r.cr(w) } } @@ -546,6 +546,10 @@ func (r *Renderer) paragraphEnter(w io.Writer, para *ast.Paragraph) { if isParentBlockQuote { r.cr(w) } + _, isParentAside := para.Parent.(*ast.Aside) + if isParentAside { + r.cr(w) + } } tag := tagWithAttributes("") + case *ast.Aside: + tag := tagWithAttributes("") case *ast.Link: r.link(w, node, entering) case *ast.Image: diff --git a/mmark_test.go b/mmark_test.go index 7fae258..5c119d6 100644 --- a/mmark_test.go +++ b/mmark_test.go @@ -21,14 +21,15 @@ func TestMmark(t *testing.T) { if len(testdata)%2 != 0 { t.Fatalf("odd test tuples: %d", len(testdata)) } + + ext := parser.CommonExtensions | parser.Attributes | parser.OrderedListStart | parser.MmarkSpecialHeading for i := 0; i < len(testdata); i += 2 { - ext := parser.CommonExtensions | parser.Attributes | parser.OrderedListStart | parser.MmarkSpecialHeading - parser := parser.NewWithExtensions(ext) + p := parser.NewWithExtensions(ext) input := testdata[i] want := testdata[i+1] - got := ToHTML([]byte(input), parser, nil) + got := ToHTML([]byte(input), p, nil) if bytes.Compare(got, want) != 0 { t.Errorf("want %s, got %s, for input %q", want, got, input) diff --git a/parser/aside.go b/parser/aside.go new file mode 100644 index 0000000..37375fe --- /dev/null +++ b/parser/aside.go @@ -0,0 +1,71 @@ +package parser + +import ( + "bytes" + + "github.com/gomarkdown/markdown/ast" +) + +// returns aisde prefix length +func (p *Parser) asidePrefix(data []byte) int { + i := 0 + n := len(data) + for i < 3 && i < n && data[i] == ' ' { + i++ + } + if i+1 < n && data[i] == 'A' && data[i+1] == '>' { + if i+2 < n && data[i+2] == ' ' { + return i + 3 + } + return i + 2 + } + return 0 +} + +// aside ends with at least one blank line +// followed by something without a aside prefix +func (p *Parser) terminateAside(data []byte, beg, end int) bool { + if p.isEmpty(data[beg:]) <= 0 { + return false + } + if end >= len(data) { + return true + } + return p.asidePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 +} + +// parse a blockquote fragment +func (p *Parser) aside(data []byte) int { + block := p.addBlock(&ast.Aside{}) + var raw bytes.Buffer + beg, end := 0, 0 + for beg < len(data) { + end = beg + // Step over whole lines, collecting them. While doing that, check for + // fenced code and if one's found, incorporate it altogether, + // irregardless of any contents inside it + for end < len(data) && data[end] != '\n' { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data[end:], false); i > 0 { + // -1 to compensate for the extra end++ after the loop: + end += i - 1 + break + } + } + end++ + } + end = skipCharN(data, end, '\n', 1) + if pre := p.asidePrefix(data[beg:]); pre > 0 { + // skip the prefix + beg += pre + } else if p.terminateAside(data, beg, end) { + break + } + // this line is part of the aside + raw.Write(data[beg:end]) + beg = end + } + p.block(raw.Bytes()) + p.finalize(block) + return end +} diff --git a/parser/block.go b/parser/block.go index 1b3b9a4..feb7243 100644 --- a/parser/block.go +++ b/parser/block.go @@ -228,6 +228,17 @@ func (p *Parser) block(data []byte) { continue } + // aside: + // + // A> The proof is too large to fit + // A> in the margin. + if p.extensions|MmarkAsides != 0 { + if p.asidePrefix(data) > 0 { + data = data[p.aside(data):] + continue + } + } + // table: // // Name | Age | Phone diff --git a/parser/parser.go b/parser/parser.go index 5f335f4..a99bf55 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -39,6 +39,7 @@ const ( OrderedListStart // Keep track of the first number used when starting an ordered list. Attributes // Block Attributes MmarkSpecialHeading // Allow Mmark special headings to be parsed. See mmark.nl/syntax. + MmarkAsides // Mmark asides paragraphs. See https://mmark.nl/syntax. CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | Autolink | Strikethrough | SpaceHeadings | HeadingIDs |