github.com/rumpl/bof@v23.0.0-rc.2+incompatible/integration/internal/termtest/stripansi.go (about) 1 package termtest // import "github.com/docker/docker/integration/internal/termtest" 2 3 import ( 4 "errors" 5 "regexp" 6 7 "github.com/Azure/go-ansiterm" 8 ) 9 10 var stripOSC = regexp.MustCompile(`\x1b\][^\x1b\a]*(\x1b\\|\a)`) 11 12 // StripANSICommands attempts to strip ANSI console escape and control sequences 13 // from s, returning a string containing only the final printed characters which 14 // would be visible onscreen if the string was to be processed by a terminal 15 // emulator. Basic cursor positioning and screen erase control sequences are 16 // parsed and processed such that the output of simple CLI commands passed 17 // through a Windows Pseudoterminal and then this function yields the same 18 // string as if the output of those commands was redirected to a file. 19 // 20 // The only correct way to represent the result of processing ANSI console 21 // output would be a two-dimensional array of an emulated terminal's display 22 // buffer. That would be awkward to test against, so this function instead 23 // attempts to render to a one-dimensional string without extra padding. This is 24 // an inherently lossy process, and any attempts to render a string containing 25 // many cursor positioning commands are unlikely to yield satisfactory results. 26 // Handlers for several ANSI control sequences are also unimplemented; attempts 27 // to parse a string containing one will panic. 28 func StripANSICommands(s string, opts ...ansiterm.Option) (string, error) { 29 // Work around https://github.com/Azure/go-ansiterm/issues/34 30 s = stripOSC.ReplaceAllLiteralString(s, "") 31 32 var h stringHandler 33 p := ansiterm.CreateParser("Ground", &h, opts...) 34 _, err := p.Parse([]byte(s)) 35 return h.String(), err 36 } 37 38 type stringHandler struct { 39 ansiterm.AnsiEventHandler 40 cursor int 41 b []byte 42 } 43 44 func (h *stringHandler) Print(b byte) error { 45 if h.cursor == len(h.b) { 46 h.b = append(h.b, b) 47 } else { 48 h.b[h.cursor] = b 49 } 50 h.cursor++ 51 return nil 52 } 53 54 func (h *stringHandler) Execute(b byte) error { 55 switch b { 56 case '\b': 57 if h.cursor > 0 { 58 if h.cursor == len(h.b) && h.b[h.cursor-1] == ' ' { 59 h.b = h.b[:len(h.b)-1] 60 } 61 h.cursor-- 62 } 63 case '\r', '\n': 64 h.Print(b) 65 } 66 return nil 67 } 68 69 // Erase Display 70 func (h *stringHandler) ED(v int) error { 71 switch v { 72 case 1: // Erase from start to cursor. 73 for i := 0; i < h.cursor; i++ { 74 h.b[i] = ' ' 75 } 76 case 2, 3: // Erase whole display. 77 h.b = make([]byte, h.cursor) 78 for i := range h.b { 79 h.b[i] = ' ' 80 } 81 default: // Erase from cursor to end of display. 82 h.b = h.b[:h.cursor+1] 83 } 84 return nil 85 } 86 87 // CUrsor Position 88 func (h *stringHandler) CUP(x, y int) error { 89 if x > 1 { 90 return errors.New("termtest: cursor position not supported for X > 1") 91 } 92 if y > len(h.b) { 93 for n := len(h.b) - y; n > 0; n-- { 94 h.b = append(h.b, ' ') 95 } 96 } 97 h.cursor = y - 1 98 return nil 99 } 100 101 func (h stringHandler) DECTCEM(bool) error { return nil } // Text Cursor Enable 102 func (h stringHandler) SGR(v []int) error { return nil } // Set Graphics Rendition 103 func (h stringHandler) DA(attrs []string) error { return nil } 104 func (h stringHandler) Flush() error { return nil } 105 106 func (h *stringHandler) String() string { 107 return string(h.b) 108 }