term_writer.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package term
  14. import (
  15. "io"
  16. "os"
  17. "github.com/docker/docker/pkg/term"
  18. wordwrap "github.com/mitchellh/go-wordwrap"
  19. )
  20. type wordWrapWriter struct {
  21. limit uint
  22. writer io.Writer
  23. }
  24. // NewResponsiveWriter creates a Writer that detects the column width of the
  25. // terminal we are in, and adjusts every line width to fit and use recommended
  26. // terminal sizes for better readability. Does proper word wrapping automatically.
  27. // if terminal width >= 120 columns use 120 columns
  28. // if terminal width >= 100 columns use 100 columns
  29. // if terminal width >= 80 columns use 80 columns
  30. // In case we're not in a terminal or if it's smaller than 80 columns width,
  31. // doesn't do any wrapping.
  32. func NewResponsiveWriter(w io.Writer) io.Writer {
  33. file, ok := w.(*os.File)
  34. if !ok {
  35. return w
  36. }
  37. fd := file.Fd()
  38. if !term.IsTerminal(fd) {
  39. return w
  40. }
  41. terminalSize := GetSize(fd)
  42. if terminalSize == nil {
  43. return w
  44. }
  45. var limit uint
  46. switch {
  47. case terminalSize.Width >= 120:
  48. limit = 120
  49. case terminalSize.Width >= 100:
  50. limit = 100
  51. case terminalSize.Width >= 80:
  52. limit = 80
  53. }
  54. return NewWordWrapWriter(w, limit)
  55. }
  56. // NewWordWrapWriter is a Writer that supports a limit of characters on every line
  57. // and does auto word wrapping that respects that limit.
  58. func NewWordWrapWriter(w io.Writer, limit uint) io.Writer {
  59. return &wordWrapWriter{
  60. limit: limit,
  61. writer: w,
  62. }
  63. }
  64. func (w wordWrapWriter) Write(p []byte) (nn int, err error) {
  65. if w.limit == 0 {
  66. return w.writer.Write(p)
  67. }
  68. original := string(p)
  69. wrapped := wordwrap.WrapString(original, w.limit)
  70. return w.writer.Write([]byte(wrapped))
  71. }
  72. // NewPunchCardWriter is a NewWordWrapWriter that limits the line width to 80 columns.
  73. func NewPunchCardWriter(w io.Writer) io.Writer {
  74. return NewWordWrapWriter(w, 80)
  75. }
  76. type maxWidthWriter struct {
  77. maxWidth uint
  78. currentWidth uint
  79. written uint
  80. writer io.Writer
  81. }
  82. // NewMaxWidthWriter is a Writer that supports a limit of characters on every
  83. // line, but doesn't do any word wrapping automatically.
  84. func NewMaxWidthWriter(w io.Writer, maxWidth uint) io.Writer {
  85. return &maxWidthWriter{
  86. maxWidth: maxWidth,
  87. writer: w,
  88. }
  89. }
  90. func (m maxWidthWriter) Write(p []byte) (nn int, err error) {
  91. for _, b := range p {
  92. if m.currentWidth == m.maxWidth {
  93. m.writer.Write([]byte{'\n'})
  94. m.currentWidth = 0
  95. }
  96. if b == '\n' {
  97. m.currentWidth = 0
  98. }
  99. _, err := m.writer.Write([]byte{b})
  100. if err != nil {
  101. return int(m.written), err
  102. }
  103. m.written++
  104. m.currentWidth++
  105. }
  106. return len(p), nil
  107. }