| package termtest // import "github.com/docker/docker/integration/internal/termtest" |
| |
| import ( |
| "errors" |
| "regexp" |
| |
| "github.com/Azure/go-ansiterm" |
| ) |
| |
| var stripOSC = regexp.MustCompile(`\x1b\][^\x1b\a]*(\x1b\\|\a)`) |
| |
| // StripANSICommands attempts to strip ANSI console escape and control sequences |
| // from s, returning a string containing only the final printed characters which |
| // would be visible onscreen if the string was to be processed by a terminal |
| // emulator. Basic cursor positioning and screen erase control sequences are |
| // parsed and processed such that the output of simple CLI commands passed |
| // through a Windows Pseudoterminal and then this function yields the same |
| // string as if the output of those commands was redirected to a file. |
| // |
| // The only correct way to represent the result of processing ANSI console |
| // output would be a two-dimensional array of an emulated terminal's display |
| // buffer. That would be awkward to test against, so this function instead |
| // attempts to render to a one-dimensional string without extra padding. This is |
| // an inherently lossy process, and any attempts to render a string containing |
| // many cursor positioning commands are unlikely to yield satisfactory results. |
| // Handlers for several ANSI control sequences are also unimplemented; attempts |
| // to parse a string containing one will panic. |
| func StripANSICommands(s string, opts ...ansiterm.Option) (string, error) { |
| // Work around https://github.com/Azure/go-ansiterm/issues/34 |
| s = stripOSC.ReplaceAllLiteralString(s, "") |
| |
| var h stringHandler |
| p := ansiterm.CreateParser("Ground", &h, opts...) |
| _, err := p.Parse([]byte(s)) |
| return h.String(), err |
| } |
| |
| type stringHandler struct { |
| ansiterm.AnsiEventHandler |
| cursor int |
| b []byte |
| } |
| |
| func (h *stringHandler) Print(b byte) error { |
| if h.cursor == len(h.b) { |
| h.b = append(h.b, b) |
| } else { |
| h.b[h.cursor] = b |
| } |
| h.cursor++ |
| return nil |
| } |
| |
| func (h *stringHandler) Execute(b byte) error { |
| switch b { |
| case '\b': |
| if h.cursor > 0 { |
| if h.cursor == len(h.b) && h.b[h.cursor-1] == ' ' { |
| h.b = h.b[:len(h.b)-1] |
| } |
| h.cursor-- |
| } |
| case '\r', '\n': |
| h.Print(b) |
| } |
| return nil |
| } |
| |
| // Erase Display |
| func (h *stringHandler) ED(v int) error { |
| switch v { |
| case 1: // Erase from start to cursor. |
| for i := 0; i < h.cursor; i++ { |
| h.b[i] = ' ' |
| } |
| case 2, 3: // Erase whole display. |
| h.b = make([]byte, h.cursor) |
| for i := range h.b { |
| h.b[i] = ' ' |
| } |
| default: // Erase from cursor to end of display. |
| h.b = h.b[:h.cursor+1] |
| } |
| return nil |
| } |
| |
| // CUrsor Position |
| func (h *stringHandler) CUP(x, y int) error { |
| if x > 1 { |
| return errors.New("termtest: cursor position not supported for X > 1") |
| } |
| if y > len(h.b) { |
| for n := len(h.b) - y; n > 0; n-- { |
| h.b = append(h.b, ' ') |
| } |
| } |
| h.cursor = y - 1 |
| return nil |
| } |
| |
| func (h stringHandler) DECTCEM(bool) error { return nil } // Text Cursor Enable |
| func (h stringHandler) SGR(v []int) error { return nil } // Set Graphics Rendition |
| func (h stringHandler) DA(attrs []string) error { return nil } |
| func (h stringHandler) Flush() error { return nil } |
| |
| func (h *stringHandler) String() string { |
| return string(h.b) |
| } |