github.phpd.cn/thought-machine/please@v12.2.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 _, cols, err := WindowSize() 26 if err != nil { 27 log.Error("%s", err) 28 } 29 return &progressReader{ 30 max: i, // If we failed above this is zero, that's handled later. 31 reader: reader, 32 width: cols, 33 interactive: err == nil && StdErrIsATerminal, 34 } 35 } 36 37 // Read implements the io.Reader interface 38 func (pr *progressReader) Read(b []byte) (int, error) { 39 n, err := pr.reader.Read(b) 40 pr.current = pr.current + n 41 pr.update() 42 pr.last = pr.current 43 return n, err 44 } 45 46 // Close implements the io.Closer interface 47 // It closes the internal reader as well as cleaning up itself. 48 func (pr *progressReader) Close() error { 49 if pr.interactive { 50 // Clear out the line. 51 Printf("${RESETLN}") 52 } else { 53 // Can't clear out the line, just move down to the next one. 54 Printf("\n") 55 } 56 return pr.reader.Close() 57 } 58 59 // update refreshes the display. 60 func (pr *progressReader) update() { 61 if !pr.interactive { 62 // Can't do interactive things, just print dots. 63 if pr.current > pr.last { 64 Printf(strings.Repeat(".", (pr.current-pr.last)/10000)) 65 } 66 return 67 } 68 currentBytes := humanize.Bytes(uint64(pr.current)) 69 if pr.max == 0 { 70 // we don't know how big the download is going to be, just show the downloaded size. 71 // this shouldn't happen normally, our server does return the content size. 72 Printf("${RESETLN}%s...", currentBytes) 73 return 74 } 75 maxBytes := humanize.Bytes(uint64(pr.max)) 76 proportion := float64(pr.current) / float64(pr.max) 77 percentage := 100.0 * proportion 78 totalCols := pr.width - 30 // Pretty arbitrary amount of overhead to make sure we have space. 79 currentPos := int(proportion * float64(totalCols)) 80 if currentPos > totalCols { 81 currentPos = totalCols 82 } 83 before := strings.Repeat("=", currentPos) 84 after := strings.Repeat(" ", totalCols-currentPos) 85 Printf("${RESETLN}${BOLD_WHITE}%s / %s ${GREY}[%s>%s] ${BOLD_WHITE}%0.1f%%${RESET}", currentBytes, maxBytes, before, after, percentage) 86 }