aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/util
diff options
context:
space:
mode:
authorJP Appel <jeanpierre.appel01@gmail.com>2024-10-08 15:13:39 -0400
committerJP Appel <jeanpierre.appel01@gmail.com>2024-10-08 15:13:39 -0400
commit68ed836d9b347a9ceac3e415027183bae042be5d (patch)
tree797e3d8572ca8f082911c03172719e5239259c80 /util
parent13c771c84e6b86cebe6df14d0e92001f940581ba (diff)
Add ansi filter writer
Diffstat (limited to 'util')
-rw-r--r--util/util.go57
-rw-r--r--util/util_test.go50
2 files changed, 85 insertions, 22 deletions
diff --git a/util/util.go b/util/util.go
index deb528b..dc73941 100644
--- a/util/util.go
+++ b/util/util.go
@@ -5,34 +5,63 @@ import (
"regexp"
)
+const ANSI_REGEX string = "\x1b[[0-9;]*m"
+
// Strip ansi control characters from source
//
// The source reader is automatically closed when stripper reader is
-type AnsiStripper struct {
- regex regexp.Regexp
- source io.ReadCloser
+type AnsiFilterReadCloser struct {
+ regex regexp.Regexp
+ readCloser io.ReadCloser
}
-func NewAnsiStripper(source io.ReadCloser) *AnsiStripper {
- striper := new(AnsiStripper)
- striper.regex = *regexp.MustCompile(`\x1b[[0-9;]*m`)
- striper.source = source
+func NewAnsiFilterReadCloser(readCloser io.ReadCloser) *AnsiFilterReadCloser {
+ r := new(AnsiFilterReadCloser)
+ r.regex = *regexp.MustCompile(ANSI_REGEX)
+ r.readCloser = readCloser
- return striper
+ return r
}
-func (rc AnsiStripper) Read(p []byte) (int, error) {
- n, err := rc.source.Read(p)
+func (rc AnsiFilterReadCloser) Read(p []byte) (int, error) {
+ n, err := rc.readCloser.Read(p)
if err != io.EOF && err != nil {
return n, err
}
- stripped := rc.regex.ReplaceAll(p[:n], []byte(""))
- n = copy(p, stripped)
+ filtered := rc.regex.ReplaceAll(p[:n], []byte(""))
+ n = copy(p, filtered)
return n, err
}
-func (rc AnsiStripper) Close() error {
- return rc.source.Close()
+func (rc AnsiFilterReadCloser) Close() error {
+ return rc.readCloser.Close()
+}
+
+type AnsiFilterWriter struct {
+ regex regexp.Regexp
+ writer io.Writer
+}
+
+func NewAnsiFilterWriter(writer io.Writer) *AnsiFilterWriter {
+ w := new(AnsiFilterWriter)
+ w.regex = *regexp.MustCompile(ANSI_REGEX)
+ w.writer = writer
+
+ return w
+}
+
+func (w AnsiFilterWriter) Write(p []byte) (int, error) {
+ filtered := w.regex.ReplaceAll(p, []byte(""))
+
+ n, err := w.writer.Write(filtered)
+
+ if n < len(filtered) {
+ return n, io.ErrShortWrite
+ }
+
+ // NOTE: report the original size not the filtered size to indicate to caller
+ // that input has been consumed
+ return len(p), err
}
diff --git a/util/util_test.go b/util/util_test.go
index 1c4ae39..fd9b7b8 100644
--- a/util/util_test.go
+++ b/util/util_test.go
@@ -1,4 +1,4 @@
-package util_test;
+package util_test
import (
"io"
@@ -22,9 +22,9 @@ func (stringReadCloser) Close() error {
return nil
}
-func testAnsiStripper(t *testing.T, input string, expected string) {
+func testAnsiFilterReadCloser(t *testing.T, input string, expected string) {
reader := newStringReadCloser(input)
- cleanReader := util.NewAnsiStripper(reader)
+ cleanReader := util.NewAnsiFilterReadCloser(reader)
defer cleanReader.Close()
buf := new(strings.Builder)
@@ -36,15 +36,49 @@ func testAnsiStripper(t *testing.T, input string, expected string) {
result := buf.String()
if n != int64(len(expected)) {
- t.Errorf("Expected to write %d characters but wrote %d\n", 3, n)
+ t.Errorf("Expected to read %d bytes but read %d\n", len(expected), n)
}
if result != expected {
- t.Errorf("Expected string `%s` but wrote `%s`", "abc", result)
+ t.Errorf("Expected string `%s` but read `%s`\n", "abc", result)
}
}
-func TestStripAnsiColors(t *testing.T) {
- testAnsiStripper(t, "a\x1b[31mbc", "abc")
- testAnsiStripper(t, "[\x1b[32minfo\x1b[39m]", "[info]")
+func testAnsiFilterWriter(t *testing.T, input string, expected string) {
+ writer := new(strings.Builder)
+ w := util.NewAnsiFilterWriter(writer)
+
+ n, err := w.Write([]byte(input))
+ if err != nil {
+ t.Fatal("Error while writting input", err)
+ }
+
+ if n != len(expected) {
+ t.Errorf("Expected to write %d bytes but read %d\n", len(expected), n)
+ }
+
+ result := writer.String()
+ if result != expected {
+ t.Errorf("Expected to write `%s` but wrote `%s`\n", expected, result)
+ }
+}
+
+func TestAnsiFilterColorsReadCloser(t *testing.T) {
+ testAnsiFilterReadCloser(t, "a\x1b[31mbc", "abc")
+ testAnsiFilterReadCloser(t, "[\x1b[32minfo\x1b[39m]", "[info]")
+ testAnsiFilterReadCloser(t,
+ "FoundryVTT | 2024-10-06 20:11:14 | [\x1b[32minfo\x1b[39m] Running on Node.js - Version 20.17.0\n"+
+ "FoundryVTT | 2024-10-06 20:11:14 | [\x1b[32minfo\x1b[39m] Foundry Virtual Tabletop - Version 12 Build 331\n",
+ "FoundryVTT | 2024-10-06 20:11:14 | [info] Running on Node.js - Version 20.17.0\n"+
+ "FoundryVTT | 2024-10-06 20:11:14 | [info] Foundry Virtual Tabletop - Version 12 Build 331\n")
+}
+
+func TestAnsiFilterColorsWriter(t *testing.T) {
+ testAnsiFilterWriter(t, "a\x1b[31mbc", "abc")
+ testAnsiFilterWriter(t, "[\x1b[32minfo\x1b[39m]", "[info]")
+ testAnsiFilterWriter(t,
+ "FoundryVTT | 2024-10-06 20:11:14 | [\x1b[32minfo\x1b[39m] Running on Node.js - Version 20.17.0\n"+
+ "FoundryVTT | 2024-10-06 20:11:14 | [\x1b[32minfo\x1b[39m] Foundry Virtual Tabletop - Version 12 Build 331\n",
+ "FoundryVTT | 2024-10-06 20:11:14 | [info] Running on Node.js - Version 20.17.0\n"+
+ "FoundryVTT | 2024-10-06 20:11:14 | [info] Foundry Virtual Tabletop - Version 12 Build 331\n")
}