stripansi.go 3.1 KB

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