aboutsummaryrefslogtreecommitdiff
path: root/mardown
diff options
context:
space:
mode:
authorAnhgelus Morhtuuzh <william@herges.fr>2025-10-02 15:06:42 +0200
committerAnhgelus Morhtuuzh <william@herges.fr>2025-10-02 15:06:42 +0200
commit21dd71949b05d84ef6ee2f05610ec7254f1086f0 (patch)
tree72f94b52067774eed8264c5468b3b8a32b8553ef /mardown
parentbe4cea404c15979112653cd4d75784aab527f22c (diff)
feat(markdown): custom error display
Diffstat (limited to 'mardown')
-rw-r--r--mardown/ast.go17
-rw-r--r--mardown/ast_code.go10
-rw-r--r--mardown/ast_external.go12
-rw-r--r--mardown/ast_header.go10
-rw-r--r--mardown/ast_list.go4
-rw-r--r--mardown/ast_modifier.go5
-rw-r--r--mardown/ast_paragraph.go9
-rw-r--r--mardown/ast_quote.go4
-rw-r--r--mardown/error.go38
-rw-r--r--mardown/error_test.go26
-rw-r--r--mardown/eval.go16
11 files changed, 116 insertions, 35 deletions
diff --git a/mardown/ast.go b/mardown/ast.go
index ed4d1f8..ceeec6f 100644
--- a/mardown/ast.go
+++ b/mardown/ast.go
@@ -11,14 +11,14 @@ import (
var ErrUnkownLexType = errors.New("unkown lex type")
type block interface {
- Eval() (template.HTML, error)
+ Eval() (template.HTML, *ParseError)
}
type tree struct {
blocks []block
}
-func (t *tree) Eval() (template.HTML, error) {
+func (t *tree) Eval() (template.HTML, *ParseError) {
var content template.HTML
for _, c := range t.blocks {
ct, err := c.Eval()
@@ -35,7 +35,7 @@ func (t *tree) String() string {
return string(b)
}
-func ast(lxs *lexers) (*tree, error) {
+func ast(lxs *lexers) (*tree, *ParseError) {
tr := new(tree)
newLine := true
for lxs.Next() {
@@ -53,9 +53,9 @@ func ast(lxs *lexers) (*tree, error) {
return tr, nil
}
-func getBlock(lxs *lexers, newLine bool) (block, error) {
+func getBlock(lxs *lexers, newLine bool) (block, *ParseError) {
var b block
- var err error
+ var err *ParseError
switch lxs.Current().Type {
case lexerHeader:
if !newLine {
@@ -83,7 +83,7 @@ func getBlock(lxs *lexers, newLine bool) (block, error) {
}
case lexerCode:
if !newLine && len(lxs.Current().Value) == 3 {
- return nil, ErrInvalidCodeBlockPosition
+ return nil, &ParseError{lxs: *lxs, internal: ErrInvalidCodeBlockPosition}
}
if len(lxs.Current().Value) == 1 {
b, err = paragraph(lxs, false)
@@ -94,7 +94,10 @@ func getBlock(lxs *lexers, newLine bool) (block, error) {
b, err = paragraph(lxs, false)
case lexerBreak: // do nothing
default:
- err = errors.Join(ErrUnkownLexType, fmt.Errorf("type received: %s", lxs.Current().Type))
+ err = &ParseError{
+ lxs: *lxs,
+ internal: errors.Join(ErrUnkownLexType, fmt.Errorf("type received: %s", lxs.Current().Type)),
+ }
}
return b, err
}
diff --git a/mardown/ast_code.go b/mardown/ast_code.go
index 7edd6ba..f029df2 100644
--- a/mardown/ast_code.go
+++ b/mardown/ast_code.go
@@ -25,18 +25,18 @@ type astCode struct {
codeType codeType
}
-func (a *astCode) Eval() (template.HTML, error) {
+func (a *astCode) Eval() (template.HTML, *ParseError) {
switch a.codeType {
case codeOneLine:
return template.HTML(fmt.Sprintf("<code>%s</code>", template.HTMLEscapeString(a.content))), nil
case codeMultiLine:
return template.HTML(fmt.Sprintf("<pre><code>%s</code></pre>", template.HTMLEscapeString(a.content))), nil
default:
- return "", ErrUnknownCodeType
+ return "", &ParseError{lxs: lexers{}, internal: ErrUnknownCodeType}
}
}
-func code(lxs *lexers) (*astCode, error) {
+func code(lxs *lexers) (*astCode, *ParseError) {
tree := new(astCode)
current := lxs.Current().Value
if len(current) == 3 {
@@ -44,13 +44,13 @@ func code(lxs *lexers) (*astCode, error) {
} else if len(current) == 1 {
tree.codeType = codeOneLine
} else {
- return nil, ErrInvalidCodeFormat
+ return nil, &ParseError{lxs: *lxs, internal: ErrInvalidCodeFormat}
}
started := false
for lxs.Next() && lxs.Current().Value != current {
if lxs.Current().Type == lexerBreak {
if tree.codeType == codeOneLine {
- return nil, ErrInvalidCodeFormat
+ return nil, &ParseError{lxs: *lxs, internal: ErrInvalidCodeFormat}
}
if !started {
started = true
diff --git a/mardown/ast_external.go b/mardown/ast_external.go
index 78f12c8..98a090e 100644
--- a/mardown/ast_external.go
+++ b/mardown/ast_external.go
@@ -10,7 +10,7 @@ type astLink struct {
href block
}
-func (a *astLink) Eval() (template.HTML, error) {
+func (a *astLink) Eval() (template.HTML, *ParseError) {
content, err := a.content.Eval()
if err != nil {
return "", err
@@ -28,7 +28,7 @@ type astImage struct {
source []*astParagraph
}
-func (a *astImage) Eval() (template.HTML, error) {
+func (a *astImage) Eval() (template.HTML, *ParseError) {
alt, err := a.alt.Eval()
if err != nil {
return "", err
@@ -52,14 +52,14 @@ func (a *astImage) Eval() (template.HTML, error) {
return template.HTML(fmt.Sprintf(`<figure><img alt="%s" src="%s"><figcaption>%s</figcaption></figure>`, alt, src, s)), nil
}
-func external(lxs *lexers) (block, error) {
+func external(lxs *lexers) (block, *ParseError) {
tp := lxs.Current().Value
if !lxs.Next() {
return astLiteral(tp), nil
}
lxs.Before() // because we call Next
var b block
- var err error
+ var err *ParseError
switch tp {
case "![":
b, err = image(lxs)
@@ -71,7 +71,7 @@ func external(lxs *lexers) (block, error) {
return b, err
}
-func link(lxs *lexers) (block, error) {
+func link(lxs *lexers) (block, *ParseError) {
lk := new(astLink)
start := lxs.current
content, href, _, ok := parseExternal(lxs, false)
@@ -83,7 +83,7 @@ func link(lxs *lexers) (block, error) {
return lk, nil
}
-func image(lxs *lexers) (block, error) {
+func image(lxs *lexers) (block, *ParseError) {
img := new(astImage)
start := lxs.current
alt, src, source, ok := parseExternal(lxs, true)
diff --git a/mardown/ast_header.go b/mardown/ast_header.go
index 5b13411..0ce8a22 100644
--- a/mardown/ast_header.go
+++ b/mardown/ast_header.go
@@ -13,9 +13,9 @@ type astHeader struct {
content *astParagraph
}
-func (a *astHeader) Eval() (template.HTML, error) {
+func (a *astHeader) Eval() (template.HTML, *ParseError) {
if a.level > 6 {
- return "", ErrInvalidHeader
+ return "", &ParseError{lxs: lexers{}, internal: ErrInvalidCodeFormat}
}
var content template.HTML
content, err := a.content.Eval()
@@ -25,12 +25,12 @@ func (a *astHeader) Eval() (template.HTML, error) {
return template.HTML(fmt.Sprintf("<h%d>%s</h%d>", a.level, trimSpace(content), a.level)), nil
}
-func header(lxs *lexers) (*astHeader, error) {
+func header(lxs *lexers) (*astHeader, *ParseError) {
b := &astHeader{level: uint(len(lxs.Current().Value))}
if !lxs.Next() {
- return nil, ErrInvalidHeader
+ return nil, &ParseError{lxs: *lxs, internal: ErrInvalidHeader}
}
- var err error
+ var err *ParseError
b.content, err = paragraph(lxs, true)
if err != nil {
return nil, err
diff --git a/mardown/ast_list.go b/mardown/ast_list.go
index f95324b..39f0178 100644
--- a/mardown/ast_list.go
+++ b/mardown/ast_list.go
@@ -20,7 +20,7 @@ type astList struct {
content []*astParagraph
}
-func (a *astList) Eval() (template.HTML, error) {
+func (a *astList) Eval() (template.HTML, *ParseError) {
var content template.HTML
for _, c := range a.content {
ct, err := c.Eval()
@@ -32,7 +32,7 @@ func (a *astList) Eval() (template.HTML, error) {
return template.HTML(fmt.Sprintf("<%s>%s</%s>", a.tag, content, a.tag)), nil
}
-func list(lxs *lexers) (block, error) {
+func list(lxs *lexers) (block, *ParseError) {
tree := new(astList)
tree.tag = detectListType(lxs.Current().Value)
if len(tree.tag) == 0 {
diff --git a/mardown/ast_modifier.go b/mardown/ast_modifier.go
index bd04b28..93a5af3 100644
--- a/mardown/ast_modifier.go
+++ b/mardown/ast_modifier.go
@@ -8,7 +8,6 @@ import (
)
var (
- ErrInternalError = errors.New("internal error")
ErrInvalidModifier = errors.Join(ErrInvalidParagraph, errors.New("invalid modifier organization"))
ErrInvalidTypeInModifier = errors.Join(ErrInvalidParagraph, errors.New("invalid type in modifier"))
)
@@ -27,12 +26,12 @@ type astModifier struct {
super bool
}
-func (a *astModifier) Eval() (template.HTML, error) {
+func (a *astModifier) Eval() (template.HTML, *ParseError) {
var content template.HTML
for _, c := range a.content {
ct, err := c.Eval()
if err != nil {
- return "", err
+ return "", &ParseError{lxs: lexers{}, internal: err}
}
content += ct
}
diff --git a/mardown/ast_paragraph.go b/mardown/ast_paragraph.go
index 23d526e..15d9bb8 100644
--- a/mardown/ast_paragraph.go
+++ b/mardown/ast_paragraph.go
@@ -15,7 +15,7 @@ type astParagraph struct {
oneLine bool
}
-func (a *astParagraph) Eval() (template.HTML, error) {
+func (a *astParagraph) Eval() (template.HTML, *ParseError) {
var content template.HTML
for _, c := range a.content {
ct, err := c.Eval()
@@ -30,7 +30,7 @@ func (a *astParagraph) Eval() (template.HTML, error) {
return template.HTML(fmt.Sprintf("<p>%s</p>", trimSpace(content))), nil
}
-func paragraph(lxs *lexers, oneLine bool) (*astParagraph, error) {
+func paragraph(lxs *lexers, oneLine bool) (*astParagraph, *ParseError) {
tree := new(astParagraph)
tree.oneLine = oneLine
maxBreak := 2
@@ -40,7 +40,6 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, error) {
n := 0
lxs.current-- // because we do not use it before the next
for lxs.Next() && n < maxBreak {
- //println("p", strings.ReplaceAll(lxs.Current().Value, "\n", "/n"))
switch lxs.Current().Type {
case lexerBreak:
n += len(lxs.Current().Value)
@@ -62,7 +61,7 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, error) {
n = 0
mod, err := modifier(lxs)
if err != nil {
- return nil, err
+ return nil, &ParseError{lxs: *lxs, internal: err}
}
tree.content = append(tree.content, mod)
case lexerExternal:
@@ -94,6 +93,6 @@ func paragraph(lxs *lexers, oneLine bool) (*astParagraph, error) {
type astLiteral string
-func (a astLiteral) Eval() (template.HTML, error) {
+func (a astLiteral) Eval() (template.HTML, *ParseError) {
return template.HTML(template.HTMLEscapeString(string(a))), nil
}
diff --git a/mardown/ast_quote.go b/mardown/ast_quote.go
index f046445..ffc8e83 100644
--- a/mardown/ast_quote.go
+++ b/mardown/ast_quote.go
@@ -11,7 +11,7 @@ type astQuote struct {
source []*astParagraph
}
-func (a *astQuote) Eval() (template.HTML, error) {
+func (a *astQuote) Eval() (template.HTML, *ParseError) {
var quote template.HTML
for _, c := range a.quote {
ct, err := c.Eval()
@@ -36,7 +36,7 @@ func (a *astQuote) Eval() (template.HTML, error) {
return template.HTML(fmt.Sprintf(`<div class="quote">%s</div>`, quote)), nil
}
-func quote(lxs *lexers) (*astQuote, error) {
+func quote(lxs *lexers) (*astQuote, *ParseError) {
tree := new(astQuote)
n := 0
quoteContinue := true
diff --git a/mardown/error.go b/mardown/error.go
new file mode 100644
index 0000000..f85dfbb
--- /dev/null
+++ b/mardown/error.go
@@ -0,0 +1,38 @@
+package mardown
+
+import "fmt"
+
+type ParseError struct {
+ internal error
+ lxs lexers
+}
+
+func (e *ParseError) Error() string {
+ return e.internal.Error()
+}
+
+func (e *ParseError) Pretty() string {
+ lxs := e.lxs
+ if lxs.lexers == nil {
+ return e.internal.Error()
+ }
+ current := lxs.current - 1
+ for lxs.Before() && lxs.Current().Type != lexerBreak {
+ }
+ current -= lxs.current
+ contxt := ""
+ ind := ""
+ for lxs.Next() && lxs.Current().Type != lexerBreak {
+ contxt += lxs.Current().Value
+ if lxs.current < current {
+ if lxs.current == current-2 {
+ ind += "^"
+ } else {
+ for range len(lxs.Current().Value) {
+ ind += "~"
+ }
+ }
+ }
+ }
+ return fmt.Sprintf("%v\n\n%s\n%s", e, contxt, ind)
+}
diff --git a/mardown/error_test.go b/mardown/error_test.go
new file mode 100644
index 0000000..bcf6143
--- /dev/null
+++ b/mardown/error_test.go
@@ -0,0 +1,26 @@
+package mardown
+
+import "testing"
+
+func TestError(t *testing.T) {
+ v, err := Parse("**bonsoir")
+ if err == nil {
+ t.Errorf("expected error, got %s", v)
+ } else {
+ t.Log(err.Pretty())
+ }
+
+ v, err = Parse("bo*nso**ir")
+ if err == nil {
+ t.Errorf("expected error, got %s", v)
+ } else {
+ t.Log(err.Pretty())
+ }
+
+ v, err = Parse("test ``` hehe")
+ if err == nil {
+ t.Errorf("expected error, got %s", v)
+ } else {
+ t.Log(err.Pretty())
+ }
+}
diff --git a/mardown/eval.go b/mardown/eval.go
new file mode 100644
index 0000000..c085767
--- /dev/null
+++ b/mardown/eval.go
@@ -0,0 +1,16 @@
+package mardown
+
+import "html/template"
+
+func Parse(s string) (template.HTML, *ParseError) {
+ lxs := lex(s)
+ tree, err := ast(lxs)
+ if err != nil {
+ return "", err
+ }
+ return tree.Eval()
+}
+
+func ParseBytes(b []byte) (template.HTML, *ParseError) {
+ return Parse(string(b))
+}