github.com/tiagovtristao/plz@v13.4.0+incompatible/src/cli/progress.go (about) 1 package cli 2 3 import ( 4 "io" 5 "strconv" 6 "strings" 7 8 "github.com/dustin/go-humanize" 9 ) 10 11 // progressReader implements an io.ReadCloser that shows a progress bar in the terminal which 12 // updates as more is read. 13 // You should take care to close this in order to clean up the terminal afterwards. 14 // Note that it is fairly hardcoded to our use case right now (i.e. downloading Please tarballs), 15 // it probably doesn't generalise perfectly to things of arbitrary sizes. 16 type progressReader struct { 17 current, last, max, width int 18 reader io.ReadCloser 19 interactive bool 20 } 21 22 // NewProgressReader returns a new progress bar reader. 23 func NewProgressReader(reader io.ReadCloser, total string) io.ReadCloser { 24 i, _ := strconv.Atoi(total) 25 r := &progressReader{ 26 max: i, // If we failed above this is zero, that's handled later. 27 reader: reader, 28 width: 80, 29 interactive: StdErrIsATerminal, 30 } 31 if StdErrIsATerminal { 32 _, cols, err := WindowSize() 33 if err != nil { 34 log.Error("%s", err) 35 r.interactive = false 36 } 37 r.width = cols 38 } 39 return r 40 } 41 42 // Read implements the io.Reader interface 43 func (pr *progressReader) Read(b []byte) (int, error) { 44 n, err := pr.reader.Read(b) 45 pr.current = pr.current + n 46 pr.update() 47 pr.last = pr.current 48 return n, err 49 } 50 51 // Close implements the io.Closer interface 52 // It closes the internal reader as well as cleaning up itself. 53 func (pr *progressReader) Close() error { 54 if pr.interactive { 55 // Clear out the line. 56 Printf("${RESETLN}") 57 } else { 58 // Can't clear out the line, just move down to the next one. 59 Printf("\n") 60 } 61 return pr.reader.Close() 62 } 63 64 // update refreshes the display. 65 func (pr *progressReader) update() { 66 if !pr.interactive { 67 // Can't do interactive things, just print dots. 68 if pr.current > pr.last { 69 Printf(strings.Repeat(".", (pr.current-pr.last)/10000)) 70 } 71 return 72 } 73 currentBytes := humanize.Bytes(uint64(pr.current)) 74 if pr.max == 0 { 75 // we don't know how big the download is going to be, just show the downloaded size. 76 // this shouldn't happen normally, our server does return the content size. 77 Printf("${RESETLN}%s...", currentBytes) 78 return 79 } 80 maxBytes := humanize.Bytes(uint64(pr.max)) 81 proportion := float64(pr.current) / float64(pr.max) 82 percentage := 100.0 * proportion 83 totalCols := pr.width - 30 // Pretty arbitrary amount of overhead to make sure we have space. 84 currentPos := int(proportion * float64(totalCols)) 85 if currentPos > totalCols { 86 currentPos = totalCols 87 } 88 before := strings.Repeat("=", currentPos) 89 after := strings.Repeat(" ", totalCols-currentPos) 90 Printf("${RESETLN}${BOLD_WHITE}%s / %s ${GREY}[%s>%s] ${BOLD_WHITE}%0.1f%%${RESET}", currentBytes, maxBytes, before, after, percentage) 91 }