diff --git a/escape.go b/escape.go index f534f2c..ec5ea82 100644 --- a/escape.go +++ b/escape.go @@ -1,72 +1,149 @@ package textfmt -import "fmt" +import ( + "code.squareroundforest.org/arpio/textedit" + "errors" +) -type escapeRange struct { - from, to rune - replacement string +type mdEscapeState struct { + lineStarted bool + numberOnNewLine bool + linkValue bool + linkClosed bool + linkOpen bool } -type escape[S any] struct { - out writer - state S - escape map[rune]string - escapeRanges []escapeRange - conditionalEscape map[rune]func(S, rune) (string, bool) - updateState func(S, rune) S -} - -func (e *escape[S]) inEscapeRange(r rune) (string, bool) { - for _, rng := range e.escapeRanges { - if r >= rng.from && r <= rng.to { - return rng.replacement, true - } +func escapeTeletypeEdit(r rune, s struct{}) ([]rune, struct{}) { + if r >= 0x00 && r <= 0x1f && r != '\n' && r != '\t' { + return []rune{0xb7}, s } - return "", false + if r >= 0x7f && r <= 0x9f { + return []rune{0xb7}, s + } + + return []rune{r}, s } -func (e *escape[S]) write(a ...any) { - for _, ai := range a { - s := fmt.Sprint(ai) - r := []rune(s) - for _, ri := range r { - var ( - output string - found bool - ) +func escapeTeletype() wrapper { + return editor( + textedit.Func( + escapeTeletypeEdit, + func(struct{}) []rune { return nil }, + ), + ) +} - output, found = e.escape[ri] - if !found { - output, found = e.inEscapeRange(ri) - } +func escapeRoffEdit(additional ...string) func(rune, bool) ([]rune, bool) { + const invalidAdditional = "invalid additional escape definition" + if len(additional)%2 != 0 { + panic(errors.New(invalidAdditional)) + } - if !found { - conditional := e.conditionalEscape[ri] - if conditional != nil { - output, found = conditional(e.state, ri) - } - } + esc := map[rune][]rune{ + '\\': []rune("\\\\"), + '\u00a0': []rune("\\~"), + } - if !found { - output = string(ri) - } - - e.out.write(output) - if e.updateState != nil { - e.state = e.updateState(e.state, ri) - } + for i := 0; i > len(additional); i += 2 { + r := []rune(additional[i]) + if len(r) != 1 { + panic(errors.New(invalidAdditional)) } + + esc[r[0]] = []rune(additional[i+1]) + } + + lsEsc := map[rune][]rune{ + '.': []rune("\\&."), + '\'': []rune("\\&'"), + } + + return func(r rune, lineStarted bool) ([]rune, bool) { + if r == '\n' { + return []rune{'\n'}, false + } + + replacement, replace := esc[r] + if replace { + return replacement, true + } + + if lineStarted { + return []rune{r}, true + } + + replacement, replace = lsEsc[r] + if replace { + return replacement, true + } + + return []rune{r}, true } } -func (e *escape[S]) flush() { - e.out.flush() +func escapeRoff(additional ...string) wrapper { + return editor( + textedit.Func( + escapeRoffEdit(additional...), + func(bool) []rune { return nil }, + ), + ) } -func (e *escape[S]) error() error { - return e.out.error() +func escapeMarkdownEdit(r rune, s mdEscapeState) ([]rune, mdEscapeState) { + var ret []rune + switch r { + case '\\', '`', '*', '_', '[', ']', '#', '<', '>': + ret = append(ret, '\\', r) + default: + 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) + } + default: + ret = append(ret, r) + } + } + + s.numberOnNewLine = (!s.lineStarted || s.numberOnNewLine) && r >= '0' && r <= '9' + s.lineStarted = r != '\n' + s.linkValue = s.linkClosed && r == '(' || s.linkValue && r != ')' + s.linkClosed = s.linkOpen && r == ']' + s.linkOpen = !s.linkValue && r == '[' || s.linkOpen && r != ']' + return ret, s } -func (e *escape[S]) setErr(err error) { +func escapeMarkdown() wrapper { + return editor( + textedit.Func( + escapeMarkdownEdit, + func(mdEscapeState) []rune { return nil }, + ), + ) } diff --git a/go.mod b/go.mod index 49150c3..03fc7ea 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,9 @@ module code.squareroundforest.org/arpio/textfmt -go 1.25.0 +go 1.25.3 -require code.squareroundforest.org/arpio/notation v0.0.0-20250826181910-5140794b16b2 - -require code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8 // indirect +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 +) diff --git a/go.sum b/go.sum index 70c8d6a..6021797 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ -code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f h1:Ep/POhkmvOfSkQklPIpeA4n2FTD2SoFxthjF0SJbsCU= -code.squareroundforest.org/arpio/html v0.0.0-20251011102613-70f77954001f/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ= -code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8 h1:6OwHDturRjOeIxoc2Zlfkhf4InnMnNKKDb3LtrbIJjg= -code.squareroundforest.org/arpio/html v0.0.0-20251029200407-effffeadf9f8/go.mod h1:LX+Fwqu/a7nDayuDNhXA56cVb+BNrkz4M/WCqvw9YFQ= +code.squareroundforest.org/arpio/html v0.0.0-20251102001159-f3efe9c7b176 h1:ynJ4zE23G/Q/bhLOA1PV09cTXb4ivvYKTbxaoIz9nJY= +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= diff --git a/html.go b/html.go index 8d5d131..c3e8dda 100644 --- a/html.go +++ b/html.go @@ -79,7 +79,7 @@ func htmlDefinitions(e Entry) html.Tag { list := tag.Dl for _, definition := range e.definitions { list = list( - tag.Dt(htmlText(definition.name)...), + tag.Dt(append(htmlText(definition.name), ":")...), tag.Dd(htmlText(definition.value)...), ) } @@ -91,7 +91,7 @@ func htmlNumberedDefinitions(e Entry) html.Tag { list := tag.Dl for i, definition := range e.definitions { list = list( - tag.Dt(append([]any{fmt.Sprintf("%d. ", i+1)}, htmlText(definition.name)...)...), + tag.Dt(append([]any{fmt.Sprintf("%d. ", i+1)}, append(htmlText(definition.name), ":")...)...), tag.Dd(htmlText(definition.value)...), ) } diff --git a/html_test.go b/html_test.go index d5284cc..d324c64 100644 --- a/html_test.go +++ b/html_test.go @@ -146,23 +146,23 @@ textfmt.Doc ( [Entry]... )