aboutsummaryrefslogtreecommitdiff
path: root/markdown
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2025-12-14 18:05:35 +0100
committerAnhgelus Morhtuuzh <william@herges.fr>2025-12-14 18:05:35 +0100
commita65b9cb6b2c7dc7b48b8076d5e463776f1de0cf1 (patch)
tree021a5d38fe1a6c9bd876114fd3b3f4a296737fbd /markdown
parent8edad5f9aee4625384485ad07180da751858839c (diff)
feat(markdown): custom replacer
Diffstat (limited to 'markdown')
-rw-r--r--markdown/ast.go2
-rw-r--r--markdown/ast_list_test.go2
-rw-r--r--markdown/ast_modifier_test.go2
-rw-r--r--markdown/ast_paragraph.go47
-rw-r--r--markdown/ast_paragraph_test.go20
-rw-r--r--markdown/ast_quote_test.go2
-rw-r--r--markdown/eval.go18
-rw-r--r--markdown/lexer.go9
-rw-r--r--markdown/lexer_test.go27
9 files changed, 94 insertions, 35 deletions
diff --git a/markdown/ast.go b/markdown/ast.go
index f409ccb..b78104c 100644
--- a/markdown/ast.go
+++ b/markdown/ast.go
@@ -95,7 +95,7 @@ func getBlock(lxs *lexers, newLine bool) (block, *ParseError) {
} else {
b, err = code(lxs)
}
- case lexerLiteral, lexerModifier:
+ case lexerLiteral, lexerModifier, lexerReplace:
b, err = paragraph(lxs, false)
case lexerBreak: // do nothing
default:
diff --git a/markdown/ast_list_test.go b/markdown/ast_list_test.go
index f33a229..210a83a 100644
--- a/markdown/ast_list_test.go
+++ b/markdown/ast_list_test.go
@@ -27,7 +27,7 @@ var expected = `
`
func TestList(t *testing.T) {
- lxs := lex(rw)
+ lxs := lex(rw, new(Option))
tree, err := ast(lxs)
if err != nil {
t.Fatal(err)
diff --git a/markdown/ast_modifier_test.go b/markdown/ast_modifier_test.go
index 8ccc860..3689657 100644
--- a/markdown/ast_modifier_test.go
+++ b/markdown/ast_modifier_test.go
@@ -6,7 +6,7 @@ func TestModifier(t *testing.T) {
content := `
**bo*n*soir**, ça ***va* bien** ?
`
- lxs := lex(content)
+ lxs := lex(content, new(Option))
tree, err := ast(lxs)
if err != nil {
t.Fatal(err)
diff --git a/markdown/ast_paragraph.go b/markdown/ast_paragraph.go
index eddba0a..70d3414 100644
--- a/markdown/ast_paragraph.go
+++ b/markdown/ast_paragraph.go
@@ -42,6 +42,14 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, *ParseError) {
maxBreak = 1
}
n := 0
+ asLiteral := func(conv func(s string) block) {
+ s := lxs.Current().Value
+ // replace line break by space
+ if n > 0 && len(tree.content) != 0 {
+ s = " " + s
+ }
+ tree.content = append(tree.content, conv(s))
+ }
lxs.current-- // because we do not use it before the next
for lxs.Next() && n < maxBreak {
switch lxs.Current().Type {
@@ -52,21 +60,16 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, *ParseError) {
lxs.Before() // because we did not use it
return tree, nil
}
- tree.content = append(tree.content, astLiteral(lxs.Current().Value))
+ asLiteral(toAstLiteral)
case lexerLiteral, lexerHeading:
- s := lxs.Current().Value
- // replace line break by space
- if n > 0 && len(tree.content) != 0 {
- s = " " + s
- }
- n = 0
- tree.content = append(tree.content, astLiteral(s))
+ asLiteral(toAstLiteral)
+ case lexerReplace:
+ asLiteral(toAstReplacer)
case lexerModifier:
// replace line break by space
if n > 0 {
tree.content = append(tree.content, astLiteral(" "))
}
- n = 0
mod, err := modifier(lxs)
if err != nil {
return nil, &ParseError{lxs: *lxs, internal: err}
@@ -78,12 +81,7 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, *ParseError) {
return tree, nil
}
if lxs.Current().Value != "[" {
- //if lxs.Current().Value == "!" {
- s := lxs.Current().Value
- if n > 0 {
- s = " " + s
- }
- tree.content = append(tree.content, astLiteral(s))
+ asLiteral(toAstLiteral)
} else {
ext, err := external(lxs)
if err != nil {
@@ -91,18 +89,19 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, *ParseError) {
}
tree.content = append(tree.content, ext)
}
- n = 0
case lexerCode:
if len(lxs.Current().Value) > 1 {
return nil, &ParseError{lxs: *lxs, internal: ErrInvalidCodeBlockPosition}
}
- n = 0
b, err := code(lxs)
if err != nil {
return nil, err
}
tree.content = append(tree.content, b)
}
+ if lxs.Current().Type != lexerBreak {
+ n = 0
+ }
}
lxs.Before() // because we never handle the last item
return tree, nil
@@ -113,3 +112,17 @@ type astLiteral string
func (a astLiteral) Eval(_ *Option) (template.HTML, *ParseError) {
return template.HTML(template.HTMLEscapeString(string(a))), nil
}
+
+func toAstLiteral(s string) block {
+ return astLiteral(s)
+}
+
+type astReplacer string
+
+func (a astReplacer) Eval(opt *Option) (template.HTML, *ParseError) {
+ return template.HTML(opt.Replaces[[]rune(a)[0]]), nil
+}
+
+func toAstReplacer(s string) block {
+ return astReplacer(s)
+}
diff --git a/markdown/ast_paragraph_test.go b/markdown/ast_paragraph_test.go
index 46dd714..2014ab9 100644
--- a/markdown/ast_paragraph_test.go
+++ b/markdown/ast_paragraph_test.go
@@ -11,3 +11,23 @@ func TestParagraph(t *testing.T) {
t.Errorf("failed, got %s", c)
}
}
+
+func TestParagraph_Replacer(t *testing.T) {
+ opt := &Option{
+ Replaces: map[rune]string{'~': "&thinsp;"},
+ }
+ c, err := Parse("bonsoir", opt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if c != "<p>bonsoir</p>" {
+ t.Errorf("failed, got %s", c)
+ }
+ c, err = Parse("bonsoir~!", opt)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if c != "<p>bonsoir&thinsp;!</p>" {
+ t.Errorf("failed, got %s", c)
+ }
+}
diff --git a/markdown/ast_quote_test.go b/markdown/ast_quote_test.go
index 3346174..50ff23a 100644
--- a/markdown/ast_quote_test.go
+++ b/markdown/ast_quote_test.go
@@ -7,7 +7,7 @@ func TestQuote(t *testing.T) {
> Bonsoir, je suis un **code**
avec une source
`
- lxs := lex(content)
+ lxs := lex(content, new(Option))
tree, err := ast(lxs)
if err != nil {
t.Fatal(err)
diff --git a/markdown/eval.go b/markdown/eval.go
index 376e577..ae4bafc 100644
--- a/markdown/eval.go
+++ b/markdown/eval.go
@@ -1,18 +1,16 @@
package markdown
-import "html/template"
+import (
+ "html/template"
+)
type Option struct {
ImageSource func(source string) string
RenderLink func(content, href string) template.HTML
+ Replaces map[rune]string
}
func Parse(s string, opt *Option) (template.HTML, *ParseError) {
- lxs := lex(s)
- tree, err := ast(lxs)
- if err != nil {
- return "", err
- }
if opt == nil {
opt = new(Option)
}
@@ -22,6 +20,14 @@ func Parse(s string, opt *Option) (template.HTML, *ParseError) {
if opt.RenderLink == nil {
opt.RenderLink = RenderLink
}
+ if opt.Replaces == nil {
+ opt.Replaces = make(map[rune]string, 0)
+ }
+ lxs := lex(s, opt)
+ tree, err := ast(lxs)
+ if err != nil {
+ return "", err
+ }
return tree.Eval(opt)
}
diff --git a/markdown/lexer.go b/markdown/lexer.go
index b68bbf9..375ef7f 100644
--- a/markdown/lexer.go
+++ b/markdown/lexer.go
@@ -21,6 +21,7 @@ const (
lexerExternal lexerType = "external"
lexerLiteral lexerType = "literal"
+ lexerReplace lexerType = "replace"
)
type lexer struct {
@@ -63,7 +64,7 @@ func (l *lexers) String() string {
return s + "]"
}
-func lex(s string) *lexers {
+func lex(s string, opt *Option) *lexers {
lxs := &lexers{current: -1}
var lexs []lexer
var currentType lexerType
@@ -127,7 +128,11 @@ func lex(s string) *lexers {
fn(c, lexerList, nil)
default:
newLine = false
- fn(c, lexerLiteral, nil)
+ if _, ok := opt.Replaces[c]; ok {
+ fn(c, lexerReplace, func(c rune) bool { return false })
+ } else {
+ fn(c, lexerLiteral, nil)
+ }
}
}
if len(previous) > 0 {
diff --git a/markdown/lexer_test.go b/markdown/lexer_test.go
index e994142..d8ec3ba 100644
--- a/markdown/lexer_test.go
+++ b/markdown/lexer_test.go
@@ -3,28 +3,43 @@ package markdown
import "testing"
func TestLex(t *testing.T) {
- lxs := lex("bonjour les gens")
+ opt := new(Option)
+ lxs := lex("bonjour les gens", opt)
if lxs.String() != "Lexers[literal(bonjour les gens) ]" {
t.Errorf("invalid lex, got %s", lxs)
}
- lxs = lex("# bonjour les gens")
+ lxs = lex("# bonjour les gens", opt)
if lxs.String() != "Lexers[header(#) literal( bonjour les gens) ]" {
t.Errorf("invalid lex, got %s", lxs)
}
- lxs = lex("# bonjour les gens\nComment ça va ?")
+ lxs = lex("# bonjour les gens\nComment ça va ?", opt)
if lxs.String() != `Lexers[header(#) literal( bonjour les gens) break({\n}) literal(Comment ça va ?) ]` {
t.Errorf("invalid lex, got %s", lxs)
}
- lxs = lex("***hey***, what's up?")
+ lxs = lex("***hey***, what's up?", opt)
if lxs.String() != "Lexers[modifier(***) literal(hey) modifier(***) literal(, what's up?) ]" {
t.Errorf("invalid lex, got %s", lxs)
}
- lxs = lex(`Xxx\_DarkEmperor\_xxX`)
+ lxs = lex(`Xxx\_DarkEmperor\_xxX`, opt)
if lxs.String() != `Lexers[literal(Xxx_DarkEmperor_xxX) ]` {
t.Errorf("invalid lex, got %s", lxs)
}
- lxs = lex(`* list`)
+ lxs = lex(`* list`, opt)
if lxs.String() != `Lexers[list(*) literal( list) ]` {
t.Errorf("invalid lex, got %s", lxs)
}
}
+
+func TestLex_Replacer(t *testing.T) {
+ opt := &Option{
+ Replaces: map[rune]string{'~': "&thinsp;"},
+ }
+ lxs := lex("bonjour les gens", opt)
+ if lxs.String() != "Lexers[literal(bonjour les gens) ]" {
+ t.Errorf("invalid lex, got %s", lxs)
+ }
+ lxs = lex("bonjour les gens~!", opt)
+ if lxs.String() != "Lexers[literal(bonjour les gens) replace(~) external(!) ]" {
+ t.Errorf("invalid lex, got %s", lxs)
+ }
+}