progress.go 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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{pos: -1, w: w}
  23. go p.start()
  24. return p
  25. }
  26. func (p *Progress) Stop() bool {
  27. if p.ticker != nil {
  28. p.ticker.Stop()
  29. p.ticker = nil
  30. p.render()
  31. return true
  32. }
  33. return false
  34. }
  35. func (p *Progress) StopAndClear() bool {
  36. stopped := p.Stop()
  37. if stopped {
  38. termWidth, _, err := term.GetSize(int(os.Stderr.Fd()))
  39. if err != nil {
  40. panic(err)
  41. }
  42. // clear the progress bar by:
  43. // 1. reset to beginning of line
  44. // 2. move up to the first line of the progress bar
  45. // 3. fill the terminal width with spaces
  46. // 4. reset to beginning of line
  47. fmt.Fprintf(p.w, "\r\033[%dA%s\r", p.pos, strings.Repeat(" ", termWidth))
  48. }
  49. return stopped
  50. }
  51. func (p *Progress) Add(key string, state State) {
  52. p.mu.Lock()
  53. defer p.mu.Unlock()
  54. p.states = append(p.states, state)
  55. }
  56. func (p *Progress) render() error {
  57. p.mu.Lock()
  58. defer p.mu.Unlock()
  59. fmt.Fprintf(p.w, "\033[%dA", p.pos)
  60. for _, state := range p.states {
  61. fmt.Fprintln(p.w, state.String())
  62. }
  63. if len(p.states) > 0 {
  64. p.pos = len(p.states)
  65. }
  66. return nil
  67. }
  68. func (p *Progress) start() {
  69. p.ticker = time.NewTicker(100 * time.Millisecond)
  70. for range p.ticker.C {
  71. p.render()
  72. }
  73. }