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
|
|
|
|
|
|
|
|
|
|
import "io"
|
|
|
|
|
|
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-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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
r := e.releaseState(s)
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 04:17:58 +01:00
|
|
|
// Func can be used to define an Editor providing only the edit and releaseState functions.
|
2025-11-01 03:49:02 +01:00
|
|
|
func Func[S any](edit func(r rune, state S) ([]rune, S), releaseState func(S) []rune) Editor {
|
|
|
|
|
if edit == nil {
|
|
|
|
|
edit = func(r rune, s S) ([]rune, S) { return []rune{r}, s }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if releaseState == nil {
|
|
|
|
|
releaseState = func(S) []rune { return nil }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return editorFunc[S]{edit: edit, releaseState: releaseState}
|
|
|
|
|
}
|
|
|
|
|
|
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-01 04:17:58 +01:00
|
|
|
// Escape can be used for basic escaping, where the found characters from the chars argument are replaced with
|
|
|
|
|
// the same character prefixed by the escape char in the esc argument. The escape char will be escaped
|
|
|
|
|
// automatically.
|
2025-11-01 03:49:02 +01:00
|
|
|
func Escape(esc rune, chars ...rune) Editor {
|
|
|
|
|
return escape(esc, chars...)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-01 04:17:58 +01:00
|
|
|
// Indent applies indentation to multi-line text. Duplicate, leading, trailing and non-space whitespaces, other
|
|
|
|
|
// than the newline character, are collapsed into a single space.
|
2025-11-01 03:49:02 +01:00
|
|
|
func Indent(first, rest string) Editor {
|
|
|
|
|
return wrapIndent([]rune(first), []rune(rest), 0, 0)
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
return sequence(
|
|
|
|
|
replace("\n", " "),
|
|
|
|
|
wrapIndent(nil, nil, 0, 0),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
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 {
|
|
|
|
|
return &Writer{
|
|
|
|
|
out: out,
|
|
|
|
|
editor: sequence(e...),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *Writer) write(r []rune) error {
|
|
|
|
|
if w.err != nil {
|
|
|
|
|
return w.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ri := range r {
|
|
|
|
|
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
|
|
|
|
|
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
|
|
|
|
|
// the underlying io.Writer, but first passes it to the subsequent editors for editing. When 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.
|
2025-11-01 03:49:02 +01:00
|
|
|
func (w *Writer) Flush() error {
|
|
|
|
|
return w.flush()
|
|
|
|
|
}
|