1
0
textedit/lib.go

174 lines
5.4 KiB
Go
Raw Permalink Normal View History

2025-11-01 04:17:58 +01:00
// Package textedit provides a non-regexp, streaming editor to apply basic text manipulation.
2025-11-01 03:49:02 +01:00
package textedit
2025-11-01 05:06:41 +01:00
import (
"io"
"unicode"
)
2025-11-01 03:49:02 +01:00
2025-11-01 04:17:58 +01:00
// Editor instances can be used to edit a text stream. It is expected from the implementations to be reusable
// with fresh state after the Flush was called on the enclosing writer.
2025-11-01 03:49:02 +01:00
type Editor interface {
2025-11-01 04:17:58 +01:00
// Edit takes an input rune and the current state of the editor, and returns zero or more resulting runes
// and the updated state.
2025-11-01 03:49:02 +01:00
Edit(r rune, state any) ([]rune, any)
2025-11-01 04:17:58 +01:00
// On flushing the editing writer, the writer will call ReleaseState on each provided editor in the
// configured sequence with their latest associated state, and if there is any pending text to be written
// out, the editors should return it at that point.
2025-11-01 03:49:02 +01:00
ReleaseState(state any) []rune
}
type editorFunc[S any] struct {
edit func(rune, S) ([]rune, S)
releaseState func(S) []rune
2025-12-06 00:57:43 +01:00
init S
2025-11-01 03:49:02 +01:00
}
2025-11-01 04:17:58 +01:00
// Writer implements the io.Writer interface, passing the input to every editor in the configured sequence, and
// writing out the edited text to the underlying io.Writer.
2025-11-01 03:49:02 +01:00
type Writer struct {
out io.Writer
editor Editor
state any
err error
}
2025-12-06 00:57:43 +01:00
func (e editorFunc[S]) initialize() any {
return e.init
}
2025-11-01 03:49:02 +01:00
func (e editorFunc[S]) Edit(r rune, state any) ([]rune, any) {
s, _ := state.(S)
return e.edit(r, s)
}
func (e editorFunc[S]) ReleaseState(state any) []rune {
s, _ := state.(S)
2025-11-01 04:29:11 +01:00
return e.releaseState(s)
2025-11-01 03:49:02 +01:00
}
2025-12-06 00:57:43 +01:00
// FuncInit can be used to define an Editor providing only the edit and releaseState functions, and the initial
// state.
func FuncInit[S any](edit func(r rune, state S) ([]rune, S), releaseState func(S) []rune, init S) Editor {
2025-11-01 03:49:02 +01:00
if edit == nil {
edit = func(r rune, s S) ([]rune, S) { return []rune{r}, s }
}
if releaseState == nil {
releaseState = func(S) []rune { return nil }
}
2025-12-06 00:57:43 +01:00
return editorFunc[S]{edit: edit, releaseState: releaseState, init: init}
}
// Func can be used to define an Editor providing only the edit and releaseState functions.
func Func[S any](edit func(r rune, state S) ([]rune, S), releaseState func(S) []rune) Editor {
var s S
return FuncInit(edit, releaseState, s)
2025-11-01 03:49:02 +01:00
}
2025-11-01 04:17:58 +01:00
// Replace is a built-in editor for simple replace operations. The arguments are mapped such that the ones at
// odd positions will be the sequences to match in the input, and the immediately following ones will be the
// sequences replacing the matched sequences. Empty replacement sequences can be used to deleted the matched
// ones. In case of odd number of arguments, an empty arg is automatically appended.
2025-11-01 03:49:02 +01:00
func Replace(a ...string) Editor {
return replace(a...)
}
2025-11-02 01:04:54 +01:00
// Indent applies indentation to multi-line text. Whitespaces are preserved.
2025-11-01 03:49:02 +01:00
func Indent(first, rest string) Editor {
2025-11-02 01:04:54 +01:00
return indent([]rune(first), []rune(rest))
2025-11-01 03:49:02 +01:00
}
2025-11-01 04:17:58 +01:00
// Wrap wrap wraps multiline text to define maximual text width. Duplicate, leading, trailing and non-space
// whitespaces are collapsed into a single space.
2025-11-01 03:49:02 +01:00
func Wrap(firstWidth, restWidth int) Editor {
return wrapIndent(nil, nil, firstWidth, restWidth)
}
2025-11-01 04:17:58 +01:00
// WrapIndent is like Wrap, but applies indentation.
2025-11-01 03:49:02 +01:00
func WrapIndent(firstIndent, restIndent string, firstWidth, restWidth int) Editor {
return wrapIndent([]rune(firstIndent), []rune(restIndent), firstWidth, restWidth)
}
2025-11-01 04:17:58 +01:00
// SingleLine is like Wrap, but with infinite text width.
2025-11-01 03:49:02 +01:00
func SingleLine() Editor {
2025-11-02 01:04:54 +01:00
return singleLine()
2025-11-01 03:49:02 +01:00
}
2025-11-01 04:17:58 +01:00
// New initializes an editing writer. The editor instances will be called in the order they are passed in to
// New.
2025-11-01 03:49:02 +01:00
func New(out io.Writer, e ...Editor) *Writer {
2025-12-06 00:57:43 +01:00
seq := sequence(e...)
2025-11-01 03:49:02 +01:00
return &Writer{
out: out,
2025-12-06 00:57:43 +01:00
editor: seq,
state: seq.(interface{ initialize() any }).initialize(),
2025-11-01 03:49:02 +01:00
}
}
func (w *Writer) write(r []rune) error {
if w.err != nil {
return w.err
}
for _, ri := range r {
2025-11-01 05:06:41 +01:00
if ri == unicode.ReplacementChar {
continue
}
2025-11-01 03:49:02 +01:00
rr, s := w.editor.Edit(ri, w.state)
if _, err := w.out.Write([]byte(string(rr))); err != nil {
w.err = err
return w.err
}
w.state = s
}
return nil
}
func (w *Writer) flush() error {
if w.err != nil {
return w.err
}
r := w.editor.ReleaseState(w.state)
w.state = nil
2025-12-09 23:22:54 +01:00
if initializer, ok := w.editor.(interface{ initialize() any }); ok {
2025-12-07 23:48:21 +01:00
w.state = initializer.initialize()
}
2025-11-01 03:49:02 +01:00
if _, err := w.out.Write([]byte(string(r))); err != nil {
w.err = err
return w.err
}
return nil
}
2025-11-01 04:17:58 +01:00
// Write calls the configured editors with the input and forwards their output to the underlying io.Writer. It
// always returns len(p) as the number bytes written. It only returns an error when the underlying writer
// returns and error.
2025-11-01 03:49:02 +01:00
func (w *Writer) Write(p []byte) (int, error) {
return len(p), w.write([]rune(string(p)))
}
2025-11-01 04:17:58 +01:00
// WriteRune calls the configured editors with the input and forwards their output to the underlying io.Writer.
// It always returns byte length of the input rune as the number bytes written. It only returns an error when
// the underlying writer returns and error.
2025-11-01 03:49:02 +01:00
func (w *Writer) WriteRune(r rune) (int, error) {
return len([]byte(string(r))), w.write([]rune{r})
}
2025-11-01 04:17:58 +01:00
// Flush makes the underlying editors to release their associated state, and writes out the resulting text to
2025-11-03 02:28:40 +01:00
// the underlying io.Writer, but first passes it to the subsequent editors for editing. Is there were no errors,
// and the used editor instances comply with the expectations of the Editor interface, the writer will have a
// fresh state and can be reused for further editing. The Flush method of underlying writers is not called.
2025-11-01 03:49:02 +01:00
func (w *Writer) Flush() error {
return w.flush()
}