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  }