fix markdown escaping
This commit is contained in:
parent
cb1b9806c3
commit
fed32c8bbe
28
escape.go
28
escape.go
@ -96,32 +96,30 @@ func escapeMarkdownEdit(r rune, s mdEscapeState) ([]rune, mdEscapeState) {
|
||||
switch r {
|
||||
case '\\', '`', '*', '_', '[', ']', '#', '<', '>':
|
||||
ret = append(ret, '\\', r)
|
||||
default:
|
||||
switch {
|
||||
case !s.lineStarted:
|
||||
switch r {
|
||||
case '+', '-':
|
||||
ret = append(ret, '\\', r)
|
||||
default:
|
||||
switch {
|
||||
case s.lineStarted:
|
||||
ret = append(ret, r)
|
||||
default:
|
||||
ret = append(ret, '\\', r)
|
||||
}
|
||||
case s.numberOnNewLine:
|
||||
switch r {
|
||||
case '.':
|
||||
switch {
|
||||
case s.numberOnNewLine:
|
||||
ret = append(ret, '\\', r)
|
||||
default:
|
||||
ret = append(ret, r)
|
||||
}
|
||||
case s.linkClosed:
|
||||
switch r {
|
||||
case '(':
|
||||
switch {
|
||||
case s.linkClosed:
|
||||
ret = append(ret, '\\', r)
|
||||
default:
|
||||
ret = append(ret, r)
|
||||
}
|
||||
case s.linkValue:
|
||||
switch r {
|
||||
case ')':
|
||||
switch {
|
||||
case s.linkValue:
|
||||
ret = append(ret, '\\', r)
|
||||
default:
|
||||
ret = append(ret, r)
|
||||
@ -129,7 +127,6 @@ func escapeMarkdownEdit(r rune, s mdEscapeState) ([]rune, mdEscapeState) {
|
||||
default:
|
||||
ret = append(ret, r)
|
||||
}
|
||||
}
|
||||
|
||||
s.numberOnNewLine = (!s.lineStarted || s.numberOnNewLine) && r >= '0' && r <= '9'
|
||||
s.lineStarted = r != '\n'
|
||||
@ -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
2
go.mod
@ -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
4
go.sum
@ -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=
|
||||
|
||||
50
markdown.go
50
markdown.go
@ -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) {
|
||||
|
||||
@ -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())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
4
write.go
4
write.go
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user