1
0

fix markdown escaping

This commit is contained in:
Arpad Ryszka 2025-12-08 00:41:08 +01:00
parent cb1b9806c3
commit fed32c8bbe
6 changed files with 114 additions and 61 deletions

View File

@ -96,39 +96,36 @@ func escapeMarkdownEdit(r rune, s mdEscapeState) ([]rune, mdEscapeState) {
switch r {
case '\\', '`', '*', '_', '[', ']', '#', '<', '>':
ret = append(ret, '\\', r)
default:
case '+', '-':
switch {
case s.lineStarted:
ret = append(ret, r)
default:
ret = append(ret, '\\', r)
}
case '.':
switch {
case !s.lineStarted:
switch r {
case '+', '-':
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
case s.numberOnNewLine:
switch r {
case '.':
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
case s.linkClosed:
switch r {
case '(':
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
case s.linkValue:
switch r {
case ')':
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
case '(':
switch {
case s.linkClosed:
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
case ')':
switch {
case s.linkValue:
ret = append(ret, '\\', r)
default:
ret = append(ret, r)
}
default:
ret = append(ret, r)
}
s.numberOnNewLine = (!s.lineStarted || s.numberOnNewLine) && r >= '0' && r <= '9'
@ -139,11 +136,12 @@ func escapeMarkdownEdit(r rune, s mdEscapeState) ([]rune, mdEscapeState) {
return ret, s
}
func escapeMarkdown() wrapper {
func escapeMarkdown(s mdEscapeState) wrapper {
return editor(
textedit.Func(
textedit.FuncInit(
escapeMarkdownEdit,
func(mdEscapeState) []rune { return nil },
s,
),
)
}

2
go.mod
View File

@ -5,5 +5,5 @@ go 1.25.3
require (
code.squareroundforest.org/arpio/html v0.0.0-20251102001159-f3efe9c7b176
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2
code.squareroundforest.org/arpio/textedit v0.0.0-20251102002300-caf622f43f10
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f
)

4
go.sum
View File

@ -2,5 +2,5 @@ code.squareroundforest.org/arpio/html v0.0.0-20251102001159-f3efe9c7b176 h1:ynJ4
code.squareroundforest.org/arpio/html v0.0.0-20251102001159-f3efe9c7b176/go.mod h1:JKD2DXph0Zt975trJII7YbdhM2gL1YEHjsh5M1X63eA=
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 h1:S4mjQHL70CuzFg1AGkr0o0d+4M+ZWM0sbnlYq6f0b3I=
code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2/go.mod h1:ait4Fvg9o0+bq5hlxi9dAcPL5a+/sr33qsZPNpToMLY=
code.squareroundforest.org/arpio/textedit v0.0.0-20251102002300-caf622f43f10 h1:u3hMmSBJzrSnJ+C7krjHFkCEVG6Ms9W6vX6F+Mk/KnY=
code.squareroundforest.org/arpio/textedit v0.0.0-20251102002300-caf622f43f10/go.mod h1:nXdFdxdI69JrkIT97f+AEE4OgplmxbgNFZC5j7gsdqs=
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f h1:gomu8xTD953IkL3M528qVEuZ2z93C2I6Hr4vyIwE7kI=
code.squareroundforest.org/arpio/textedit v0.0.0-20251207224821-c75c3965789f/go.mod h1:nXdFdxdI69JrkIT97f+AEE4OgplmxbgNFZC5j7gsdqs=

View File

@ -8,9 +8,9 @@ import (
"strings"
)
func mdTextToString(text Txt) string {
func mdTextToString(text Txt, es mdEscapeState) string {
var b bytes.Buffer
renderMDText(&b, text)
renderMDText(&b, text, es)
return b.String()
}
@ -19,7 +19,7 @@ func mdCellTexts(rows []TableRow) [][]string {
for _, row := range rows {
var rowTexts []string
for _, cell := range row.cells {
rowTexts = append(rowTexts, mdTextToString(cell.text))
rowTexts = append(rowTexts, mdTextToString(cell.text, mdEscapeState{lineStarted: false}))
}
texts = append(texts, rowTexts)
@ -41,14 +41,16 @@ func mdEnsureHeaderTexts(h []string) []string {
return hh
}
func renderMDText(w io.Writer, text Txt) {
func renderMDText(w io.Writer, text Txt, es mdEscapeState, wr ...wrapper) {
if len(text.cat) > 0 {
for i, tc := range text.cat {
if i > 0 {
write(w, " ")
}
renderMDText(w, tc)
esi := es
esi.lineStarted = esi.lineStarted || i > 0
renderMDText(w, tc, esi, wr...)
}
return
@ -56,10 +58,12 @@ func renderMDText(w io.Writer, text Txt) {
if text.bold {
write(w, "**")
es.lineStarted = true
}
if text.italic {
write(w, "_")
es.lineStarted = true
}
defer func() {
@ -72,30 +76,31 @@ func renderMDText(w io.Writer, text Txt) {
}
}()
wr = append([]wrapper{singleLine()}, wr...)
if text.link == "" {
w, f := writeWith(w, singleLine(), escapeMarkdown())
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
write(w, text.text)
w, _ = f()
return
}
if text.text == "" {
w, f := writeWith(w, singleLine(), escapeMarkdown())
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
write(w, text.link)
w, _ = f()
return
}
write(w, "[")
w, f := writeWith(w, singleLine(), escapeMarkdown())
es.lineStarted = true
w, f := writeWith(w, append(wr, escapeMarkdown(es))...)
write(w, text.text)
w, _ = f()
write(w, "](")
w, f = writeWith(w, singleLine(), escapeMarkdown())
w, f = writeWith(w, append(wr, escapeMarkdown(es))...)
write(w, text.link)
w, _ = f()
write(w, ")")
w, _ = f()
}
func renderMDTitle(w io.Writer, e Entry) {
@ -105,26 +110,23 @@ func renderMDTitle(w io.Writer, e Entry) {
}
write(w, timesn("#", hashes), " ")
renderMDText(w, e.text)
renderMDText(w, e.text, mdEscapeState{lineStarted: true})
}
func renderMDParagraphIndent(w io.Writer, e Entry) {
var f func() (io.Writer, error)
func renderMDParagraphIndent(w io.Writer, e Entry, es mdEscapeState) {
var wr []wrapper
if e.wrapWidth > 0 {
indentFirst := e.indent + e.indentFirst
w, f = writeWith(w, wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth))
wr = []wrapper{wrapIndent(indentFirst, e.indent, e.wrapWidth, e.wrapWidth)}
}
renderMDText(w, e.text)
if f != nil {
f()
}
renderMDText(w, e.text, es, wr...)
}
func renderMDParagraph(w io.Writer, e Entry) {
e.indent = 0
e.indentFirst = 0
renderMDParagraphIndent(w, e)
renderMDParagraphIndent(w, e, mdEscapeState{})
}
func renderMDList(w io.Writer, e Entry) {
@ -141,7 +143,7 @@ func renderMDList(w io.Writer, e Entry) {
write(w, "- ")
p := itemToParagraph(e, item.text)
renderMDParagraphIndent(w, p)
renderMDParagraphIndent(w, p, mdEscapeState{lineStarted: true})
}
}
@ -160,7 +162,7 @@ func renderMDNumberedList(w io.Writer, e Entry) {
write(w, padRight(fmt.Sprintf("%d.", i+1), maxDigits+2))
p := itemToParagraph(e, item.text)
renderMDParagraphIndent(w, p)
renderMDParagraphIndent(w, p, mdEscapeState{lineStarted: true})
}
}
@ -168,7 +170,7 @@ func renderMDDefinitions(w io.Writer, e Entry) {
for _, d := range e.definitions {
e.items = append(
e.items,
Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)),
Item(Text(fmt.Sprintf("%s: %s", d.name.text, d.value.text))),
)
}
@ -179,7 +181,7 @@ func renderMDNumberedDefinitions(w io.Writer, e Entry) {
for _, d := range e.definitions {
e.items = append(
e.items,
Item(Cat(Text(fmt.Sprintf("%s:", d.name.text)), d.value)),
Item(Text(fmt.Sprintf("%s: %s", d.name.text, d.value.text))),
)
}
@ -303,9 +305,7 @@ func renderMDChoice(w io.Writer, s SyntaxItem) {
}
func renderMDSymbol(w io.Writer, s SyntaxItem) {
w, f := writeWith(w, escapeMarkdown())
write(w, s.symbol)
f()
}
func renderMDSyntaxItem(w io.Writer, s SyntaxItem) {

View File

@ -550,6 +550,31 @@ textfmt supports the following entries:
}
})
t.Run("non line start dash", func(t *testing.T) {
doc := textfmt.Doc(
textfmt.List(
textfmt.Item(textfmt.Text("--foo one")),
textfmt.Item(textfmt.Text("--bar two")),
textfmt.Item(textfmt.Text("--baz three")),
),
)
var b bytes.Buffer
if err := textfmt.Markdown(&b, doc); err != nil {
t.Fatal(err)
}
const expect = `
- --foo one
- --bar two
- --baz three
`
if "\n"+b.String() != expect {
t.Fatal("\n" + b.String())
}
})
t.Run("indent ignored", func(t *testing.T) {
doc := textfmt.Doc(
textfmt.Indent(
@ -1509,7 +1534,11 @@ textfmt supports the following entries:
return textfmt.Document(
textfmt.Paragraph(textfmt.Text("Hello, world!")),
)
}`
}
/*
- no escape: -` + "`" + `[*]` + "`" + `
*/`
var b bytes.Buffer
if err := textfmt.Markdown(&b, textfmt.Doc(textfmt.CodeBlock(code))); err != nil {
@ -1773,5 +1802,31 @@ textfmt supports the following entries:
t.Fatal(b.String())
}
})
t.Run("no escape", func(t *testing.T) {
doc := textfmt.Doc(
textfmt.Syntax(
textfmt.Symbol("foo"),
textfmt.ZeroOrMore(textfmt.Symbol("options")),
textfmt.Optional(textfmt.Symbol("--")),
textfmt.Required(textfmt.Symbol("filename")),
textfmt.ZeroOrMore(
textfmt.Choice(
textfmt.Symbol("string"),
textfmt.Symbol("number"),
),
),
),
)
var b bytes.Buffer
if err := textfmt.Markdown(&b, doc); err != nil {
t.Fatal(err)
}
if b.String() != "```\nfoo [options]... [--] <filename> [string|number]...\n```\n" {
t.Fatal(b.String())
}
})
})
}

View File

@ -25,12 +25,12 @@ func (w *errorWriter) Write(p []byte) (int, error) {
}
func writeWith(out io.Writer, w ...wrapper) (io.Writer, func() (io.Writer, error)) {
var f []func() error
f := make([]func() error, len(w))
ww := out
for i := len(w) - 1; i >= 0; i-- {
var fi func() error
ww, fi = w[i](ww)
f = append(f, fi)
f[i] = fi
}
return ww, func() (io.Writer, error) {