aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorJP Appel <jeanpierre.appel01@gmail.com>2024-10-08 11:08:00 -0400
committerJP Appel <jeanpierre.appel01@gmail.com>2024-10-08 11:14:58 -0400
commit663baf92df7babdf3d1a587594d6301cc5c139ad (patch)
treec57ea39fc950571d88314fcb9ef7b318bcd5a224
parentb0c69c203ee697b7adf962a54e27203a13cf4ceb (diff)
Add middleware to strip ansi escape sequences
-rw-r--r--api/docker.go40
-rw-r--r--api/docker_test.go50
2 files changed, 88 insertions, 2 deletions
diff --git a/api/docker.go b/api/docker.go
index 248880e..0968a11 100644
--- a/api/docker.go
+++ b/api/docker.go
@@ -1,10 +1,12 @@
package api
import (
+ "bufio"
"context"
"fmt"
"io"
"log/slog"
+ "regexp"
"time"
"github.com/docker/docker/api/types/container"
@@ -13,6 +15,41 @@ import (
const vttContainerId string = "foundry-foundry-1"
+// Strip ansi control characters from source
+//
+// The source reader is automatically closed when stripped reader is
+func StripAnsi(source io.ReadCloser) io.ReadCloser {
+
+ regex := regexp.MustCompile(`\x1b[[0-9;]*m`)
+ reader, writer := io.Pipe()
+
+ go func() {
+ bufReader := bufio.NewReader(source)
+ defer source.Close()
+ defer writer.Close()
+
+ cleanSource:
+ for {
+ line, err := bufReader.ReadBytes('\n')
+ switch {
+ case err == io.EOF:
+ if len(line) > 0 {
+ cleaned := regex.ReplaceAll(line, []byte(""))
+ writer.Write(cleaned)
+ }
+ break cleanSource
+ case err != nil:
+ panic(err)
+ default:
+ cleaned := regex.ReplaceAll(line, []byte(""))
+ writer.Write(cleaned)
+ }
+ }
+ }()
+
+ return reader
+}
+
func stopVtt(ctx context.Context) error {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil {
@@ -42,7 +79,6 @@ func vttLogs(ctx context.Context, lines uint) (io.ReadCloser, error) {
opts := container.LogsOptions{
ShowStdout: true,
ShowStderr: true,
- Timestamps: true,
}
if lines != 0 {
opts.Tail = fmt.Sprint(lines)
@@ -55,7 +91,7 @@ func vttLogs(ctx context.Context, lines uint) (io.ReadCloser, error) {
return nil, err
}
- return r, nil
+ return StripAnsi(r), nil
}
func vttStatus(ctx context.Context) ServerStatus {
diff --git a/api/docker_test.go b/api/docker_test.go
new file mode 100644
index 0000000..acd92bc
--- /dev/null
+++ b/api/docker_test.go
@@ -0,0 +1,50 @@
+package api_test
+
+import (
+ "io"
+ "nonsense-time/api"
+ "strings"
+ "testing"
+)
+
+type stringReadCloser struct {
+ *strings.Reader
+}
+
+func newStringReadCloser(s string) stringReadCloser {
+ rc := stringReadCloser{}
+ rc.Reader = strings.NewReader(s)
+
+ return rc
+}
+
+func (stringReadCloser) Close() error {
+ return nil
+}
+
+func testAnsiStripper(t *testing.T, input string, expected string) {
+ reader := newStringReadCloser(input)
+ cleanReader := api.StripAnsi(reader)
+ defer cleanReader.Close()
+
+ buf := new(strings.Builder)
+ n, err := io.Copy(buf, cleanReader)
+ if err != nil {
+ t.Fatal("Error while copying cleaned text to output", err)
+ }
+
+ result := buf.String()
+
+ if n != int64(len(expected)) {
+ t.Errorf("Expected to write %d characters but wrote %d\n", 3, n)
+ }
+
+ if result != expected {
+ t.Errorf("Expected string `%s` but wrote `%s`", "abc", result)
+ }
+}
+
+func TestStripAnsiColors(t *testing.T) {
+ testAnsiStripper(t, "a\x1b[31mbc", "abc")
+ testAnsiStripper(t, "[\x1b[32minfo\x1b[39m]", "[info]")
+}