diff options
| author | JP Appel <jeanpierre.appel01@gmail.com> | 2024-10-08 11:08:00 -0400 |
|---|---|---|
| committer | JP Appel <jeanpierre.appel01@gmail.com> | 2024-10-08 11:14:58 -0400 |
| commit | 663baf92df7babdf3d1a587594d6301cc5c139ad (patch) | |
| tree | c57ea39fc950571d88314fcb9ef7b318bcd5a224 /api | |
| parent | b0c69c203ee697b7adf962a54e27203a13cf4ceb (diff) | |
Add middleware to strip ansi escape sequences
Diffstat (limited to 'api')
| -rw-r--r-- | api/docker.go | 40 | ||||
| -rw-r--r-- | api/docker_test.go | 50 |
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]") +} |
