From ac8cb8d6c356f8a1787b9c9f138f7f2b9989fcf5 Mon Sep 17 00:00:00 2001 From: Anubhav Singh Date: Tue, 25 Mar 2025 19:55:57 +0000 Subject: [PATCH 1/3] feat: enhance bold and italic parsers to support underscore syntax --- parser/bold.go | 2 +- parser/italic.go | 2 +- parser/tests/bold_test.go | 30 ++++++++++++++++++++++++++++++ parser/tests/italic_test.go | 21 +++++++++++++++++++++ 4 files changed, 53 insertions(+), 2 deletions(-) diff --git a/parser/bold.go b/parser/bold.go index 9dbec90..4298108 100644 --- a/parser/bold.go +++ b/parser/bold.go @@ -22,7 +22,7 @@ func (*BoldParser) Match(tokens []*tokenizer.Token) (ast.Node, int) { return nil, 0 } prefixTokenType := prefixTokens[0].Type - if prefixTokenType != tokenizer.Asterisk { + if prefixTokenType != tokenizer.Asterisk && prefixTokenType != tokenizer.Underscore { return nil, 0 } diff --git a/parser/italic.go b/parser/italic.go index 52c85e1..71e0c60 100644 --- a/parser/italic.go +++ b/parser/italic.go @@ -20,7 +20,7 @@ func (*ItalicParser) Match(tokens []*tokenizer.Token) (ast.Node, int) { } prefixTokens := matchedTokens[:1] - if prefixTokens[0].Type != tokenizer.Asterisk { + if prefixTokens[0].Type != tokenizer.Asterisk && prefixTokens[0].Type != tokenizer.Underscore { return nil, 0 } prefixTokenType := prefixTokens[0].Type diff --git a/parser/tests/bold_test.go b/parser/tests/bold_test.go index a314304..a506c20 100644 --- a/parser/tests/bold_test.go +++ b/parser/tests/bold_test.go @@ -52,6 +52,36 @@ func TestBoldParser(t *testing.T) { text: "* * Hello **", node: nil, }, + { + text: "__Hello__", + node: &ast.Bold{ + Symbol: "_", + Children: []ast.Node{ + &ast.Text{ + Content: "Hello", + }, + }, + }, + }, + { + text: "__ Hello __", + node: &ast.Bold{ + Symbol: "_", + Children: []ast.Node{ + &ast.Text{ + Content: " Hello ", + }, + }, + }, + }, + { + text: "__ Hello _ _", + node: nil, + }, + { + text: "_ _ Hello __", + node: nil, + }, } for _, test := range tests { diff --git a/parser/tests/italic_test.go b/parser/tests/italic_test.go index 82ba56e..a954b6c 100644 --- a/parser/tests/italic_test.go +++ b/parser/tests/italic_test.go @@ -39,6 +39,27 @@ func TestItalicParser(t *testing.T) { Content: "1", }, }, + { + text: "_Hello_", + node: &ast.Italic{ + Symbol: "_", + Content: "Hello", + }, + }, + { + text: "_ Hello _", + node: &ast.Italic{ + Symbol: "_", + Content: " Hello ", + }, + }, + { + text: "_1_ Hello _ _", + node: &ast.Italic{ + Symbol: "_", + Content: "1", + }, + }, } for _, test := range tests { From 0dc5d37f4f838d3ec7e8a7799851935611ad4e63 Mon Sep 17 00:00:00 2001 From: Anubhav Singh Date: Thu, 27 Mar 2025 14:41:48 +0000 Subject: [PATCH 2/3] fix: adjust blockquote parser to handle empty content rows --- parser/blockquote.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/parser/blockquote.go b/parser/blockquote.go index 5474bbc..7e6a817 100644 --- a/parser/blockquote.go +++ b/parser/blockquote.go @@ -15,7 +15,7 @@ func (*BlockquoteParser) Match(tokens []*tokenizer.Token) (ast.Node, int) { rows := tokenizer.Split(tokens, tokenizer.NewLine) contentRows := [][]*tokenizer.Token{} for _, row := range rows { - if len(row) < 3 || row[0].Type != tokenizer.GreaterThan || row[1].Type != tokenizer.Space { + if len(row) < 2 || row[0].Type != tokenizer.GreaterThan || row[1].Type != tokenizer.Space { break } contentRows = append(contentRows, row) @@ -26,17 +26,25 @@ func (*BlockquoteParser) Match(tokens []*tokenizer.Token) (ast.Node, int) { children := []ast.Node{} size := 0 + for index, row := range contentRows { contentTokens := row[2:] - nodes, err := ParseBlockWithParsers(contentTokens, []BlockParser{NewBlockquoteParser(), NewParagraphParser()}) - if err != nil { - return nil, 0 - } - if len(nodes) != 1 { - return nil, 0 + var node ast.Node + if len(contentTokens) == 0 { + node = &ast.Paragraph{ + Children: []ast.Node{&ast.Text{Content: " "}}, + } + } else { + nodes, err := ParseBlockWithParsers(contentTokens, []BlockParser{NewBlockquoteParser(), NewParagraphParser()}) + if err != nil { + return nil, 0 + } + if len(nodes) != 1 { + return nil, 0 + } + node = nodes[0] } - - children = append(children, nodes[0]) + children = append(children, node) size += len(row) if index != len(contentRows)-1 { size++ // NewLine. From a6d71e795739a129a7a68fd03463dd8e437fdccb Mon Sep 17 00:00:00 2001 From: Anubhav Singh Date: Thu, 27 Mar 2025 15:14:06 +0000 Subject: [PATCH 3/3] feat: refactor italic node structure to use children for content representation --- ast/inline.go | 10 ++-- parser/italic.go | 9 +++- parser/tests/italic_test.go | 64 +++++++++++++++++++++----- parser/tests/ordered_list_item_test.go | 8 +++- renderer/html/html.go | 2 +- renderer/string/string.go | 2 +- 6 files changed, 74 insertions(+), 21 deletions(-) diff --git a/ast/inline.go b/ast/inline.go index 03afb1e..0feeed5 100644 --- a/ast/inline.go +++ b/ast/inline.go @@ -48,8 +48,8 @@ type Italic struct { BaseInline // Symbol is "*" or "_". - Symbol string - Content string + Symbol string + Children []Node } func (*Italic) Type() NodeType { @@ -57,7 +57,11 @@ func (*Italic) Type() NodeType { } func (n *Italic) Restore() string { - return fmt.Sprintf("%s%s%s", n.Symbol, n.Content, n.Symbol) + content := "" + for _, child := range n.Children { + content += child.Restore() + } + return fmt.Sprintf("%s%s%s", n.Symbol, content, n.Symbol) } type BoldItalic struct { diff --git a/parser/italic.go b/parser/italic.go index 71e0c60..e3915c2 100644 --- a/parser/italic.go +++ b/parser/italic.go @@ -37,8 +37,13 @@ func (*ItalicParser) Match(tokens []*tokenizer.Token) (ast.Node, int) { return nil, 0 } + children, err := ParseInlineWithParsers(contentTokens, []InlineParser{NewLinkParser(), NewTextParser()}) + if err != nil || len(children) == 0 { + return nil, 0 + } + return &ast.Italic{ - Symbol: prefixTokenType, - Content: tokenizer.Stringify(contentTokens), + Symbol: prefixTokenType, + Children: children, }, len(contentTokens) + 2 } diff --git a/parser/tests/italic_test.go b/parser/tests/italic_test.go index a954b6c..d560847 100644 --- a/parser/tests/italic_test.go +++ b/parser/tests/italic_test.go @@ -21,43 +21,83 @@ func TestItalicParser(t *testing.T) { { text: "*Hello*", node: &ast.Italic{ - Symbol: "*", - Content: "Hello", + Symbol: "*", + Children: []ast.Node{ + &ast.Text{ + Content: "Hello", + }, + }, }, }, { text: "* Hello *", node: &ast.Italic{ - Symbol: "*", - Content: " Hello ", + Symbol: "*", + Children: []ast.Node{ + &ast.Text{ + Content: " Hello ", + }, + }, }, }, { text: "*1* Hello * *", node: &ast.Italic{ - Symbol: "*", - Content: "1", + Symbol: "*", + Children: []ast.Node{ + &ast.Text{ + Content: "1", + }, + }, }, }, { text: "_Hello_", node: &ast.Italic{ - Symbol: "_", - Content: "Hello", + Symbol: "_", + Children: []ast.Node{ + &ast.Text{ + Content: "Hello", + }, + }, }, }, { text: "_ Hello _", node: &ast.Italic{ - Symbol: "_", - Content: " Hello ", + Symbol: "_", + Children: []ast.Node{ + &ast.Text{ + Content: " Hello ", + }, + }, }, }, { text: "_1_ Hello _ _", node: &ast.Italic{ - Symbol: "_", - Content: "1", + Symbol: "_", + Children: []ast.Node{ + &ast.Text{ + Content: "1", + }, + }, + }, + }, + { + text: "*[Hello](https://example.com)*", + node: &ast.Italic{ + Symbol: "*", + Children: []ast.Node{ + &ast.Link{ + Content: []ast.Node{ + &ast.Text{ + Content: "Hello", + }, + }, + URL: "https://example.com", + }, + }, }, }, } diff --git a/parser/tests/ordered_list_item_test.go b/parser/tests/ordered_list_item_test.go index 55516c7..40868cf 100644 --- a/parser/tests/ordered_list_item_test.go +++ b/parser/tests/ordered_list_item_test.go @@ -55,8 +55,12 @@ func TestOrderedListItemParser(t *testing.T) { Content: "Hello ", }, &ast.Italic{ - Symbol: "*", - Content: "World", + Symbol: "*", + Children: []ast.Node{ + &ast.Text{ + Content: "World", + }, + }, }, }, }, diff --git a/renderer/html/html.go b/renderer/html/html.go index 9ebead9..eb52e23 100644 --- a/renderer/html/html.go +++ b/renderer/html/html.go @@ -249,7 +249,7 @@ func (r *HTMLRenderer) renderBold(node *ast.Bold) { func (r *HTMLRenderer) renderItalic(node *ast.Italic) { r.output.WriteString("") - r.output.WriteString(node.Content) + r.RenderNodes(node.Children) r.output.WriteString("") } diff --git a/renderer/string/string.go b/renderer/string/string.go index 4cd95d9..b4b1c04 100644 --- a/renderer/string/string.go +++ b/renderer/string/string.go @@ -197,7 +197,7 @@ func (r *StringRenderer) renderBold(node *ast.Bold) { } func (r *StringRenderer) renderItalic(node *ast.Italic) { - r.output.WriteString(node.Content) + r.RenderNodes(node.Children) } func (r *StringRenderer) renderBoldItalic(node *ast.BoldItalic) {