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  }