diff --git a/escape.go b/escape.go index 6a96947..c174c72 100644 --- a/escape.go +++ b/escape.go @@ -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, ), ) } diff --git a/go.mod b/go.mod index 03fc7ea..44dadb5 100644 --- a/go.mod +++ b/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 ) diff --git a/go.sum b/go.sum index 6021797..c34be5a 100644 --- a/go.sum +++ b/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= diff --git a/markdown.go b/markdown.go index 6087a73..8c202c2 100644 --- a/markdown.go +++ b/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) { diff --git a/markdown_test.go b/markdown_test.go index fd3d305..db6452d 100644 --- a/markdown_test.go +++ b/markdown_test.go @@ -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]... [--] [string|number]...\n```\n" { + t.Fatal(b.String()) + } + }) }) } diff --git a/write.go b/write.go index f86e0ea..c3fef00 100644 --- a/write.go +++ b/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) {