progress.go 1.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. package progress
  2. import (
  3. "fmt"
  4. "io"
  5. "os"
  6. "strings"
  7. "sync"
  8. "time"
  9. "golang.org/x/term"
  10. )
  11. type State interface {
  12. String() string
  13. }
  14. type Progress struct {
  15. mu sync.Mutex
  16. pos int
  17. w io.Writer
  18. ticker *time.Ticker
  19. states []State
  20. }
  21. func NewProgress(w io.Writer) *Progress {
  22. p := &Progress{w: w}
  23. go p.start()
  24. return p
  25. }
  26. func (p *Progress) Stop() bool {
  27. for _, state := range p.states {
  28. if spinner, ok := state.(*Spinner); ok {
  29. spinner.Stop()
  30. }
  31. }
  32. if p.ticker != nil {
  33. p.ticker.Stop()
  34. p.ticker = nil
  35. p.render()
  36. return true
  37. }
  38. return false
  39. }
  40. func (p *Progress) StopAndClear() bool {
  41. stopped := p.Stop()
  42. if stopped {
  43. termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
  44. if err != nil {
  45. panic(err)
  46. }
  47. // clear the progress bar by:
  48. // 1. reset to beginning of line
  49. // 2. move up to the first line of the progress bar
  50. // 3. fill the terminal width with spaces
  51. // 4. reset to beginning of line
  52. fmt.Fprintf(p.w, "\r\033[%dA%s\r", p.pos, strings.Repeat(" ", termWidth))
  53. }
  54. return stopped
  55. }
  56. func (p *Progress) Add(key string, state State) {
  57. p.mu.Lock()
  58. defer p.mu.Unlock()
  59. p.states = append(p.states, state)
  60. }
  61. func (p *Progress) render() error {
  62. p.mu.Lock()
  63. defer p.mu.Unlock()
  64. if p.pos > 0 {
  65. fmt.Fprintf(p.w, "\033[%dA", p.pos)
  66. }
  67. for _, state := range p.states {
  68. fmt.Fprintln(p.w, state.String())
  69. }
  70. if len(p.states) > 0 {
  71. p.pos = len(p.states)
  72. }
  73. return nil
  74. }
  75. func (p *Progress) start() {
  76. p.ticker = time.NewTicker(100 * time.Millisecond)
  77. for range p.ticker.C {
  78. p.render()
  79. }
  80. }