main
Raw Download raw file
  1package compressor
  2
  3import (
  4	"bytes"
  5	"io"
  6	"unicode/utf8"
  7
  8	"github.com/muesli/ansi"
  9)
 10
 11type Writer struct {
 12	Forward io.Writer
 13
 14	ansi         bool
 15	ansiseq      bytes.Buffer
 16	lastseq      bytes.Buffer
 17	prevlastseq  bytes.Buffer
 18	resetreq     bool
 19	runeBuf      []byte
 20	compressed   int
 21	uncompressed int
 22}
 23
 24// Bytes is shorthand for declaring a new default compressor instance,
 25// used to immediately compress a byte slice.
 26func Bytes(b []byte) []byte {
 27	var buf bytes.Buffer
 28	f := Writer{
 29		Forward: &buf,
 30	}
 31	_, _ = f.Write(b)
 32	_ = f.Close()
 33
 34	return buf.Bytes()
 35}
 36
 37// String is shorthand for declaring a new default compressor instance,
 38// used to immediately compress a string.
 39func String(s string) string {
 40	return string(Bytes([]byte(s)))
 41}
 42
 43// Write is used to write content to the ANSI buffer.
 44func (w *Writer) Write(b []byte) (int, error) {
 45	w.uncompressed += len(b)
 46
 47	for _, c := range string(b) {
 48		if c == ansi.Marker {
 49			// ANSI escape sequence
 50			w.ansi = true
 51			_, _ = w.ansiseq.WriteRune(c)
 52		} else if w.ansi {
 53			_, _ = w.ansiseq.WriteRune(c)
 54			if ansi.IsTerminator(c) {
 55				// ANSI sequence terminated
 56				w.ansi = false
 57
 58				terminated := false
 59				if bytes.HasSuffix(w.ansiseq.Bytes(), []byte("[0m")) {
 60					// reset sequence
 61					w.prevlastseq.Reset()
 62					w.prevlastseq.Write(w.lastseq.Bytes())
 63
 64					w.lastseq.Reset()
 65					terminated = true
 66					w.resetreq = true
 67				} else if c == 'm' {
 68					// color code
 69					_, _ = w.lastseq.Write(w.ansiseq.Bytes())
 70				}
 71
 72				if !terminated {
 73					// did we reset the sequence just to restore it again?
 74					if bytes.Equal(w.ansiseq.Bytes(), w.prevlastseq.Bytes()) {
 75						w.resetreq = false
 76						w.ansiseq.Reset()
 77					}
 78
 79					w.prevlastseq.Reset()
 80
 81					if w.resetreq {
 82						w.ResetAnsi()
 83					}
 84
 85					_, _ = w.Forward.Write(w.ansiseq.Bytes())
 86					w.compressed += w.ansiseq.Len()
 87				}
 88
 89				w.ansiseq.Reset()
 90			}
 91		} else {
 92			if w.resetreq {
 93				w.ResetAnsi()
 94			}
 95
 96			_, err := w.writeRune(c)
 97			if err != nil {
 98				return 0, err
 99			}
100		}
101	}
102
103	return len(b), nil
104}
105
106func (w *Writer) writeRune(r rune) (int, error) {
107	if w.runeBuf == nil {
108		w.runeBuf = make([]byte, utf8.UTFMax)
109	}
110	n := utf8.EncodeRune(w.runeBuf, r)
111	w.compressed += n
112	return w.Forward.Write(w.runeBuf[:n])
113}
114
115// Close finishes the compression operation. Always call it before trying to
116// retrieve the final result.
117func (w *Writer) Close() error {
118	if w.resetreq {
119		w.ResetAnsi()
120	}
121
122	// log.Println("Written uncompressed: ", w.uncompressed)
123	// log.Println("Written compressed: ", w.compressed)
124
125	return nil
126}
127
128func (w *Writer) ResetAnsi() {
129	w.prevlastseq.Reset()
130	_, _ = w.Forward.Write([]byte("\x1b[0m"))
131	w.resetreq = false
132}