index.js 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. const Stream = require('stream')
  2. class MuteStream extends Stream {
  3. #isTTY = null
  4. constructor (opts = {}) {
  5. super(opts)
  6. this.writable = this.readable = true
  7. this.muted = false
  8. this.on('pipe', this._onpipe)
  9. this.replace = opts.replace
  10. // For readline-type situations
  11. // This much at the start of a line being redrawn after a ctrl char
  12. // is seen (such as backspace) won't be redrawn as the replacement
  13. this._prompt = opts.prompt || null
  14. this._hadControl = false
  15. }
  16. #destSrc (key, def) {
  17. if (this._dest) {
  18. return this._dest[key]
  19. }
  20. if (this._src) {
  21. return this._src[key]
  22. }
  23. return def
  24. }
  25. #proxy (method, ...args) {
  26. if (typeof this._dest?.[method] === 'function') {
  27. this._dest[method](...args)
  28. }
  29. if (typeof this._src?.[method] === 'function') {
  30. this._src[method](...args)
  31. }
  32. }
  33. get isTTY () {
  34. if (this.#isTTY !== null) {
  35. return this.#isTTY
  36. }
  37. return this.#destSrc('isTTY', false)
  38. }
  39. // basically just get replace the getter/setter with a regular value
  40. set isTTY (val) {
  41. this.#isTTY = val
  42. }
  43. get rows () {
  44. return this.#destSrc('rows')
  45. }
  46. get columns () {
  47. return this.#destSrc('columns')
  48. }
  49. mute () {
  50. this.muted = true
  51. }
  52. unmute () {
  53. this.muted = false
  54. }
  55. _onpipe (src) {
  56. this._src = src
  57. }
  58. pipe (dest, options) {
  59. this._dest = dest
  60. return super.pipe(dest, options)
  61. }
  62. pause () {
  63. if (this._src) {
  64. return this._src.pause()
  65. }
  66. }
  67. resume () {
  68. if (this._src) {
  69. return this._src.resume()
  70. }
  71. }
  72. write (c) {
  73. if (this.muted) {
  74. if (!this.replace) {
  75. return true
  76. }
  77. // eslint-disable-next-line no-control-regex
  78. if (c.match(/^\u001b/)) {
  79. if (c.indexOf(this._prompt) === 0) {
  80. c = c.slice(this._prompt.length)
  81. c = c.replace(/./g, this.replace)
  82. c = this._prompt + c
  83. }
  84. this._hadControl = true
  85. return this.emit('data', c)
  86. } else {
  87. if (this._prompt && this._hadControl &&
  88. c.indexOf(this._prompt) === 0) {
  89. this._hadControl = false
  90. this.emit('data', this._prompt)
  91. c = c.slice(this._prompt.length)
  92. }
  93. c = c.toString().replace(/./g, this.replace)
  94. }
  95. }
  96. this.emit('data', c)
  97. }
  98. end (c) {
  99. if (this.muted) {
  100. if (c && this.replace) {
  101. c = c.toString().replace(/./g, this.replace)
  102. } else {
  103. c = null
  104. }
  105. }
  106. if (c) {
  107. this.emit('data', c)
  108. }
  109. this.emit('end')
  110. }
  111. destroy (...args) {
  112. return this.#proxy('destroy', ...args)
  113. }
  114. destroySoon (...args) {
  115. return this.#proxy('destroySoon', ...args)
  116. }
  117. close (...args) {
  118. return this.#proxy('close', ...args)
  119. }
  120. }
  121. module.exports = MuteStream