progress.go 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package progress
  2. import (
  3. "fmt"
  4. "io"
  5. "sync"
  6. "time"
  7. )
  8. type State interface {
  9. String() string
  10. }
  11. type Progress struct {
  12. mu sync.Mutex
  13. w io.Writer
  14. pos int
  15. ticker *time.Ticker
  16. states []State
  17. }
  18. func NewProgress(w io.Writer) *Progress {
  19. p := &Progress{w: w}
  20. go p.start()
  21. return p
  22. }
  23. func (p *Progress) stop() bool {
  24. for _, state := range p.states {
  25. if spinner, ok := state.(*Spinner); ok {
  26. spinner.Stop()
  27. }
  28. }
  29. if p.ticker != nil {
  30. p.ticker.Stop()
  31. p.ticker = nil
  32. p.render()
  33. return true
  34. }
  35. return false
  36. }
  37. func (p *Progress) Stop() bool {
  38. stopped := p.stop()
  39. if stopped {
  40. fmt.Fprint(p.w, "\n")
  41. }
  42. return stopped
  43. }
  44. func (p *Progress) StopWithoutClear() bool {
  45. return p.stop()
  46. }
  47. func (p *Progress) StopAndClear() bool {
  48. fmt.Fprint(p.w, "\033[?25l")
  49. defer fmt.Fprint(p.w, "\033[?25h")
  50. stopped := p.stop()
  51. if stopped {
  52. // clear all progress lines
  53. for i := 0; i < p.pos; i++ {
  54. if i > 0 {
  55. fmt.Fprint(p.w, "\033[A")
  56. }
  57. fmt.Fprint(p.w, "\033[2K\033[1G")
  58. }
  59. }
  60. return stopped
  61. }
  62. func (p *Progress) Add(key string, state State) {
  63. p.mu.Lock()
  64. defer p.mu.Unlock()
  65. p.states = append(p.states, state)
  66. }
  67. func (p *Progress) render() {
  68. p.mu.Lock()
  69. defer p.mu.Unlock()
  70. fmt.Fprint(p.w, "\033[?25l")
  71. defer fmt.Fprint(p.w, "\033[?25h")
  72. // clear already rendered progress lines
  73. for i := 0; i < p.pos; i++ {
  74. if i > 0 {
  75. fmt.Fprint(p.w, "\033[A")
  76. }
  77. fmt.Fprint(p.w, "\033[2K\033[1G")
  78. }
  79. // render progress lines
  80. for i, state := range p.states {
  81. fmt.Fprint(p.w, state.String())
  82. if i < len(p.states)-1 {
  83. fmt.Fprint(p.w, "\n")
  84. }
  85. }
  86. p.pos = len(p.states)
  87. }
  88. func (p *Progress) start() {
  89. p.ticker = time.NewTicker(100 * time.Millisecond)
  90. for range p.ticker.C {
  91. p.render()
  92. }
  93. }