mirror of
https://github.com/usememos/memos.git
synced 2025-04-13 00:52:07 +02:00
chore: implement gomark skeleton
This commit is contained in:
parent
7f1f6f77a0
commit
aa3632e2ac
@ -67,6 +67,8 @@ linters-settings:
|
|||||||
disabled: true
|
disabled: true
|
||||||
- name: early-return
|
- name: early-return
|
||||||
disabled: true
|
disabled: true
|
||||||
|
- name: use-any
|
||||||
|
disabled: true
|
||||||
gocritic:
|
gocritic:
|
||||||
disabled-checks:
|
disabled-checks:
|
||||||
- ifElseChain
|
- ifElseChain
|
||||||
|
@ -1,19 +1 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Type string
|
|
||||||
Text string
|
|
||||||
Children []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
type Document struct {
|
|
||||||
Nodes []*Node
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDocument() *Document {
|
|
||||||
return &Document{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Document) AddNode(node *Node) {
|
|
||||||
d.Nodes = append(d.Nodes, node)
|
|
||||||
}
|
|
||||||
|
42
plugin/gomark/ast/block.go
Normal file
42
plugin/gomark/ast/block.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
type BaseBlock struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Paragraph struct {
|
||||||
|
BaseBlock
|
||||||
|
|
||||||
|
Children []Node
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeTypeParagraph = NewNodeType("Paragraph")
|
||||||
|
|
||||||
|
func NewParagraph(children []Node) *Paragraph {
|
||||||
|
return &Paragraph{
|
||||||
|
Children: children,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Paragraph) Type() NodeType {
|
||||||
|
return NodeTypeParagraph
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeBlock struct {
|
||||||
|
BaseBlock
|
||||||
|
|
||||||
|
Language string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeTypeCodeBlock = NewNodeType("CodeBlock")
|
||||||
|
|
||||||
|
func NewCodeBlock(language, content string) *CodeBlock {
|
||||||
|
return &CodeBlock{
|
||||||
|
Language: language,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CodeBlock) Type() NodeType {
|
||||||
|
return NodeTypeCodeBlock
|
||||||
|
}
|
42
plugin/gomark/ast/inline.go
Normal file
42
plugin/gomark/ast/inline.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package ast
|
||||||
|
|
||||||
|
type BaseInline struct{}
|
||||||
|
|
||||||
|
type Text struct {
|
||||||
|
BaseInline
|
||||||
|
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeTypeText = NewNodeType("Text")
|
||||||
|
|
||||||
|
func NewText(content string) *Text {
|
||||||
|
return &Text{
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Text) Type() NodeType {
|
||||||
|
return NodeTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bold struct {
|
||||||
|
BaseInline
|
||||||
|
|
||||||
|
// Symbol is "*" or "_"
|
||||||
|
Symbol string
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
var NodeTypeBold = NewNodeType("Bold")
|
||||||
|
|
||||||
|
func NewBold(symbol, content string) *Bold {
|
||||||
|
return &Bold{
|
||||||
|
Symbol: symbol,
|
||||||
|
Content: content,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Bold) Type() NodeType {
|
||||||
|
return NodeTypeBold
|
||||||
|
}
|
@ -1,12 +1,20 @@
|
|||||||
package ast
|
package ast
|
||||||
|
|
||||||
func NewNode(tp, text string) *Node {
|
type Node interface {
|
||||||
return &Node{
|
Type() NodeType
|
||||||
Type: tp,
|
|
||||||
Text: text,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Node) AddChild(child *Node) {
|
type NodeType int
|
||||||
n.Children = append(n.Children, child)
|
|
||||||
|
func (t NodeType) String() string {
|
||||||
|
return nodeTypeNames[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
var nodeTypeIndex NodeType
|
||||||
|
var nodeTypeNames = []string{""}
|
||||||
|
|
||||||
|
func NewNodeType(name string) NodeType {
|
||||||
|
nodeTypeNames = append(nodeTypeNames, name)
|
||||||
|
nodeTypeIndex++
|
||||||
|
return nodeTypeIndex
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,60 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BoldParser struct {
|
type BoldParser struct{}
|
||||||
ContentTokens []*tokenizer.Token
|
|
||||||
|
var defaultBoldParser = &BoldParser{}
|
||||||
|
|
||||||
|
func NewBoldParser() InlineParser {
|
||||||
|
return defaultBoldParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoldParser() *BoldParser {
|
func (*BoldParser) Match(tokens []*tokenizer.Token) (int, bool) {
|
||||||
return &BoldParser{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (*BoldParser) Match(tokens []*tokenizer.Token) *BoldParser {
|
|
||||||
if len(tokens) < 5 {
|
if len(tokens) < 5 {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
prefixTokens := tokens[:2]
|
prefixTokens := tokens[:2]
|
||||||
if prefixTokens[0].Type != prefixTokens[1].Type {
|
if prefixTokens[0].Type != prefixTokens[1].Type {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
prefixTokenType := prefixTokens[0].Type
|
prefixTokenType := prefixTokens[0].Type
|
||||||
if prefixTokenType != tokenizer.Star && prefixTokenType != tokenizer.Underline {
|
if prefixTokenType != tokenizer.Star && prefixTokenType != tokenizer.Underline {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
contentTokens := []*tokenizer.Token{}
|
|
||||||
cursor, matched := 2, false
|
cursor, matched := 2, false
|
||||||
for ; cursor < len(tokens)-1; cursor++ {
|
for ; cursor < len(tokens)-1; cursor++ {
|
||||||
token, nextToken := tokens[cursor], tokens[cursor+1]
|
token, nextToken := tokens[cursor], tokens[cursor+1]
|
||||||
if token.Type == tokenizer.Newline || nextToken.Type == tokenizer.Newline {
|
if token.Type == tokenizer.Newline || nextToken.Type == tokenizer.Newline {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
if token.Type == prefixTokenType && nextToken.Type == prefixTokenType {
|
if token.Type == prefixTokenType && nextToken.Type == prefixTokenType {
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
contentTokens = append(contentTokens, token)
|
|
||||||
}
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor + 2, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *BoldParser) Parse(tokens []*tokenizer.Token) ast.Node {
|
||||||
|
size, ok := p.Match(tokens)
|
||||||
|
if size == 0 || !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &BoldParser{
|
prefixTokenType := tokens[0].Type
|
||||||
ContentTokens: contentTokens,
|
contentTokens := tokens[2 : size-2]
|
||||||
|
return &ast.Bold{
|
||||||
|
Symbol: prefixTokenType,
|
||||||
|
Content: tokenizer.Stringify(contentTokens),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBoldParser(t *testing.T) {
|
func TestBoldParser(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
text string
|
text string
|
||||||
bold *BoldParser
|
bold ast.Node
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
text: "*Hello world!",
|
text: "*Hello world!",
|
||||||
@ -19,32 +20,16 @@ func TestBoldParser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "**Hello**",
|
text: "**Hello**",
|
||||||
bold: &BoldParser{
|
bold: &ast.Bold{
|
||||||
ContentTokens: []*tokenizer.Token{
|
Symbol: "*",
|
||||||
{
|
Content: "Hello",
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "** Hello **",
|
text: "** Hello **",
|
||||||
bold: &BoldParser{
|
bold: &ast.Bold{
|
||||||
ContentTokens: []*tokenizer.Token{
|
Symbol: "*",
|
||||||
{
|
Content: " Hello ",
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -55,35 +40,11 @@ func TestBoldParser(t *testing.T) {
|
|||||||
text: "* * Hello **",
|
text: "* * Hello **",
|
||||||
bold: nil,
|
bold: nil,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
text: `** Hello
|
|
||||||
**`,
|
|
||||||
bold: nil,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `**Hello \n**`,
|
|
||||||
bold: &BoldParser{
|
|
||||||
ContentTokens: []*tokenizer.Token{
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: `\n`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
tokens := tokenizer.Tokenize(test.text)
|
tokens := tokenizer.Tokenize(test.text)
|
||||||
bold := NewBoldParser()
|
parser := NewBoldParser()
|
||||||
require.Equal(t, test.bold, bold.Match(tokens))
|
require.Equal(t, test.bold, parser.Parse(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,52 +1,79 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
import (
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
type CodeBlockParser struct {
|
type CodeBlockParser struct {
|
||||||
Language string
|
Language string
|
||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultCodeBlockParser = &CodeBlockParser{}
|
||||||
|
|
||||||
func NewCodeBlockParser() *CodeBlockParser {
|
func NewCodeBlockParser() *CodeBlockParser {
|
||||||
return &CodeBlockParser{}
|
return defaultCodeBlockParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*CodeBlockParser) Match(tokens []*tokenizer.Token) *CodeBlockParser {
|
func (*CodeBlockParser) Match(tokens []*tokenizer.Token) (int, bool) {
|
||||||
if len(tokens) < 9 {
|
if len(tokens) < 9 {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if tokens[0].Type != tokenizer.Backtick || tokens[1].Type != tokenizer.Backtick || tokens[2].Type != tokenizer.Backtick {
|
if tokens[0].Type != tokenizer.Backtick || tokens[1].Type != tokenizer.Backtick || tokens[2].Type != tokenizer.Backtick {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
if tokens[3].Type != tokenizer.Newline && tokens[4].Type != tokenizer.Newline {
|
if tokens[3].Type != tokenizer.Newline && tokens[4].Type != tokenizer.Newline {
|
||||||
return nil
|
return 0, false
|
||||||
}
|
}
|
||||||
cursor, language := 4, ""
|
cursor := 4
|
||||||
if tokens[3].Type != tokenizer.Newline {
|
if tokens[3].Type != tokenizer.Newline {
|
||||||
language = tokens[3].Value
|
|
||||||
cursor = 5
|
cursor = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
content, matched := "", false
|
matched := false
|
||||||
for ; cursor < len(tokens)-3; cursor++ {
|
for ; cursor < len(tokens)-3; cursor++ {
|
||||||
if tokens[cursor].Type == tokenizer.Newline && tokens[cursor+1].Type == tokenizer.Backtick && tokens[cursor+2].Type == tokenizer.Backtick && tokens[cursor+3].Type == tokenizer.Backtick {
|
if tokens[cursor].Type == tokenizer.Newline && tokens[cursor+1].Type == tokenizer.Backtick && tokens[cursor+2].Type == tokenizer.Backtick && tokens[cursor+3].Type == tokenizer.Backtick {
|
||||||
if cursor+3 == len(tokens)-1 {
|
if cursor+3 == len(tokens)-1 {
|
||||||
|
cursor += 4
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
} else if tokens[cursor+4].Type == tokenizer.Newline {
|
} else if tokens[cursor+4].Type == tokenizer.Newline {
|
||||||
|
cursor += 5
|
||||||
matched = true
|
matched = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content += tokens[cursor].Value
|
|
||||||
}
|
}
|
||||||
if !matched {
|
if !matched {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *CodeBlockParser) Parse(tokens []*tokenizer.Token) ast.Node {
|
||||||
|
size, ok := p.Match(tokens)
|
||||||
|
if size == 0 || !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &CodeBlockParser{
|
languageToken := tokens[3]
|
||||||
Language: language,
|
contentStart, contentEnd := 5, size-4
|
||||||
Content: content,
|
if languageToken.Type == tokenizer.Newline {
|
||||||
|
languageToken = nil
|
||||||
|
contentStart = 4
|
||||||
}
|
}
|
||||||
|
if tokens[size-1].Type == tokenizer.Newline {
|
||||||
|
contentEnd = size - 5
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBlock := &ast.CodeBlock{
|
||||||
|
Content: tokenizer.Stringify(tokens[contentStart:contentEnd]),
|
||||||
|
}
|
||||||
|
if languageToken != nil {
|
||||||
|
codeBlock.Language = languageToken.String()
|
||||||
|
}
|
||||||
|
return codeBlock
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,14 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCodeBlockParser(t *testing.T) {
|
func TestCodeBlockParser(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
text string
|
text string
|
||||||
codeBlock *CodeBlockParser
|
codeBlock ast.Node
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
text: "```Hello world!```",
|
text: "```Hello world!```",
|
||||||
@ -19,21 +20,21 @@ func TestCodeBlockParser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "```\nHello\n```",
|
text: "```\nHello\n```",
|
||||||
codeBlock: &CodeBlockParser{
|
codeBlock: &ast.CodeBlock{
|
||||||
Language: "",
|
Language: "",
|
||||||
Content: "Hello",
|
Content: "Hello",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "```\nHello world!\n```",
|
text: "```\nHello world!\n```",
|
||||||
codeBlock: &CodeBlockParser{
|
codeBlock: &ast.CodeBlock{
|
||||||
Language: "",
|
Language: "",
|
||||||
Content: "Hello world!",
|
Content: "Hello world!",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "```java\nHello \n world!\n```",
|
text: "```java\nHello \n world!\n```",
|
||||||
codeBlock: &CodeBlockParser{
|
codeBlock: &ast.CodeBlock{
|
||||||
Language: "java",
|
Language: "java",
|
||||||
Content: "Hello \n world!",
|
Content: "Hello \n world!",
|
||||||
},
|
},
|
||||||
@ -48,7 +49,7 @@ func TestCodeBlockParser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "```java\nHello \n world!\n```\n123123",
|
text: "```java\nHello \n world!\n```\n123123",
|
||||||
codeBlock: &CodeBlockParser{
|
codeBlock: &ast.CodeBlock{
|
||||||
Language: "java",
|
Language: "java",
|
||||||
Content: "Hello \n world!",
|
Content: "Hello \n world!",
|
||||||
},
|
},
|
||||||
@ -57,7 +58,7 @@ func TestCodeBlockParser(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
tokens := tokenizer.Tokenize(test.text)
|
tokens := tokenizer.Tokenize(test.text)
|
||||||
codeBlock := NewCodeBlockParser()
|
parser := NewCodeBlockParser()
|
||||||
require.Equal(t, test.codeBlock, codeBlock.Match(tokens))
|
require.Equal(t, test.codeBlock, parser.Parse(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,21 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
import "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
import (
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
type ParagraphParser struct {
|
type ParagraphParser struct {
|
||||||
ContentTokens []*tokenizer.Token
|
ContentTokens []*tokenizer.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultParagraphParser = &ParagraphParser{}
|
||||||
|
|
||||||
func NewParagraphParser() *ParagraphParser {
|
func NewParagraphParser() *ParagraphParser {
|
||||||
return &ParagraphParser{}
|
return defaultParagraphParser
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ParagraphParser) Match(tokens []*tokenizer.Token) *ParagraphParser {
|
func (*ParagraphParser) Match(tokens []*tokenizer.Token) (int, bool) {
|
||||||
contentTokens := []*tokenizer.Token{}
|
contentTokens := []*tokenizer.Token{}
|
||||||
cursor := 0
|
cursor := 0
|
||||||
for ; cursor < len(tokens); cursor++ {
|
for ; cursor < len(tokens); cursor++ {
|
||||||
@ -21,10 +26,21 @@ func (*ParagraphParser) Match(tokens []*tokenizer.Token) *ParagraphParser {
|
|||||||
contentTokens = append(contentTokens, token)
|
contentTokens = append(contentTokens, token)
|
||||||
}
|
}
|
||||||
if len(contentTokens) == 0 {
|
if len(contentTokens) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return len(contentTokens), true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParagraphParser) Parse(tokens []*tokenizer.Token) ast.Node {
|
||||||
|
size, ok := p.Match(tokens)
|
||||||
|
if size == 0 || !ok {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ParagraphParser{
|
contentTokens := tokens[:size]
|
||||||
ContentTokens: contentTokens,
|
children := ParseInline(contentTokens, []InlineParser{
|
||||||
}
|
NewBoldParser(),
|
||||||
|
NewTextParser(),
|
||||||
|
})
|
||||||
|
return ast.NewParagraph(children)
|
||||||
}
|
}
|
||||||
|
@ -5,73 +5,25 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParagraphParser(t *testing.T) {
|
func TestParagraphParser(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
text string
|
text string
|
||||||
paragraph *ParagraphParser
|
paragraph ast.Node
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
text: "",
|
text: "",
|
||||||
paragraph: nil,
|
paragraph: nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: "Hello world",
|
text: "Hello world!",
|
||||||
paragraph: &ParagraphParser{
|
paragraph: &ast.Paragraph{
|
||||||
ContentTokens: []*tokenizer.Token{
|
Children: []ast.Node{
|
||||||
{
|
&ast.Text{
|
||||||
Type: tokenizer.Text,
|
Content: "Hello world!",
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "world",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `Hello
|
|
||||||
world`,
|
|
||||||
paragraph: &ParagraphParser{
|
|
||||||
ContentTokens: []*tokenizer.Token{
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: `Hello \n
|
|
||||||
world`,
|
|
||||||
paragraph: &ParagraphParser{
|
|
||||||
ContentTokens: []*tokenizer.Token{
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: "Hello",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Text,
|
|
||||||
Value: `\n`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: tokenizer.Space,
|
|
||||||
Value: " ",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -80,7 +32,7 @@ world`,
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
tokens := tokenizer.Tokenize(test.text)
|
tokens := tokenizer.Tokenize(test.text)
|
||||||
paragraph := NewParagraphParser()
|
parser := NewParagraphParser()
|
||||||
require.Equal(t, test.paragraph, paragraph.Match(tokens))
|
require.Equal(t, test.paragraph, parser.Parse(tokens))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,65 @@
|
|||||||
package parser
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
BlockParsers []BlockParser
|
||||||
|
InlineParsers []InlineParser
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseParser interface {
|
||||||
|
Match(tokens []*tokenizer.Token) (int, bool)
|
||||||
|
Parse(tokens []*tokenizer.Token) ast.Node
|
||||||
|
}
|
||||||
|
|
||||||
|
type InlineParser interface {
|
||||||
|
BaseParser
|
||||||
|
}
|
||||||
|
|
||||||
|
type BlockParser interface {
|
||||||
|
BaseParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(tokens []*tokenizer.Token) []ast.Node {
|
||||||
|
nodes := []ast.Node{}
|
||||||
|
blockParsers := []BlockParser{
|
||||||
|
NewParagraphParser(),
|
||||||
|
}
|
||||||
|
for len(tokens) > 0 {
|
||||||
|
for _, blockParser := range blockParsers {
|
||||||
|
cursor, matched := blockParser.Match(tokens)
|
||||||
|
if matched {
|
||||||
|
node := blockParser.Parse(tokens)
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
tokens = tokens[cursor:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseInline(tokens []*tokenizer.Token, inlineParsers []InlineParser) []ast.Node {
|
||||||
|
nodes := []ast.Node{}
|
||||||
|
var lastNode ast.Node
|
||||||
|
for len(tokens) > 0 {
|
||||||
|
for _, inlineParser := range inlineParsers {
|
||||||
|
cursor, matched := inlineParser.Match(tokens)
|
||||||
|
if matched {
|
||||||
|
node := inlineParser.Parse(tokens)
|
||||||
|
if node.Type() == ast.NodeTypeText && lastNode != nil && lastNode.Type() == ast.NodeTypeText {
|
||||||
|
lastNode.(*ast.Text).Content += node.(*ast.Text).Content
|
||||||
|
} else {
|
||||||
|
nodes = append(nodes, node)
|
||||||
|
lastNode = node
|
||||||
|
}
|
||||||
|
tokens = tokens[cursor:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
71
plugin/gomark/parser/parser_test.go
Normal file
71
plugin/gomark/parser/parser_test.go
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
text string
|
||||||
|
nodes []ast.Node
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
text: "Hello world!",
|
||||||
|
nodes: []ast.Node{
|
||||||
|
&ast.Paragraph{
|
||||||
|
Children: []ast.Node{
|
||||||
|
&ast.Text{
|
||||||
|
Content: "Hello world!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "**Hello** world!",
|
||||||
|
nodes: []ast.Node{
|
||||||
|
&ast.Paragraph{
|
||||||
|
Children: []ast.Node{
|
||||||
|
&ast.Bold{
|
||||||
|
Symbol: "*",
|
||||||
|
Content: "Hello",
|
||||||
|
},
|
||||||
|
&ast.Text{
|
||||||
|
Content: " world!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Hello **world**!",
|
||||||
|
nodes: []ast.Node{
|
||||||
|
&ast.Paragraph{
|
||||||
|
Children: []ast.Node{
|
||||||
|
&ast.Text{
|
||||||
|
Content: "Hello ",
|
||||||
|
},
|
||||||
|
&ast.Bold{
|
||||||
|
Symbol: "*",
|
||||||
|
Content: "world",
|
||||||
|
},
|
||||||
|
&ast.Text{
|
||||||
|
Content: "!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
tokens := tokenizer.Tokenize(test.text)
|
||||||
|
nodes := Parse(tokens)
|
||||||
|
require.Equal(t, test.nodes, nodes)
|
||||||
|
}
|
||||||
|
}
|
30
plugin/gomark/parser/text.go
Normal file
30
plugin/gomark/parser/text.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/usememos/memos/plugin/gomark/ast"
|
||||||
|
"github.com/usememos/memos/plugin/gomark/parser/tokenizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TextParser struct {
|
||||||
|
Content string
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultTextParser = &TextParser{}
|
||||||
|
|
||||||
|
func NewTextParser() *TextParser {
|
||||||
|
return defaultTextParser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TextParser) Match(tokens []*tokenizer.Token) (int, bool) {
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
return 1, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TextParser) Parse(tokens []*tokenizer.Token) ast.Node {
|
||||||
|
if len(tokens) == 0 {
|
||||||
|
return ast.NewText("")
|
||||||
|
}
|
||||||
|
return ast.NewText(tokens[0].String())
|
||||||
|
}
|
@ -72,3 +72,15 @@ func Tokenize(text string) []*Token {
|
|||||||
}
|
}
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Token) String() string {
|
||||||
|
return t.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func Stringify(tokens []*Token) string {
|
||||||
|
text := ""
|
||||||
|
for _, token := range tokens {
|
||||||
|
text += token.String()
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user