| // Copyright 2024 The BoringSSL Authors | 
 | // | 
 | // Permission to use, copy, modify, and/or distribute this software for any | 
 | // purpose with or without fee is hereby granted, provided that the above | 
 | // copyright notice and this permission notice appear in all copies. | 
 | // | 
 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY | 
 | // SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION | 
 | // OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN | 
 | // CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
 |  | 
 | package main | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"net/http" | 
 | 	"strings" | 
 | ) | 
 |  | 
 | func step(desc string, f func(*stepPrinter) error) error { | 
 | 	fmt.Printf("%s...", desc) | 
 | 	if *pipe { | 
 | 		fmt.Printf("\n") | 
 | 	} else { | 
 | 		fmt.Printf(" ") | 
 | 	} | 
 | 	s := stepPrinter{lastPercent: -1} | 
 | 	err := f(&s) | 
 | 	s.erasePercent() | 
 | 	if err != nil { | 
 | 		fmt.Printf("ERROR\n") | 
 | 	} else { | 
 | 		fmt.Printf("OK\n") | 
 | 	} | 
 | 	return err | 
 | } | 
 |  | 
 | type stepPrinter struct { | 
 | 	lastPercent     int | 
 | 	percentLen      int | 
 | 	progress, total int | 
 | } | 
 |  | 
 | func (s *stepPrinter) erasePercent() { | 
 | 	if !*pipe && s.percentLen > 0 { | 
 | 		var erase strings.Builder | 
 | 		for i := 0; i < s.percentLen; i++ { | 
 | 			erase.WriteString("\b \b") | 
 | 		} | 
 | 		fmt.Printf("%s", erase.String()) | 
 | 		s.percentLen = 0 | 
 | 	} | 
 | } | 
 |  | 
 | func (s *stepPrinter) setTotal(total int) { | 
 | 	s.progress = 0 | 
 | 	s.total = total | 
 | 	s.printPercent() | 
 | } | 
 |  | 
 | func (s *stepPrinter) addProgress(delta int) { | 
 | 	s.progress += delta | 
 | 	s.printPercent() | 
 | } | 
 |  | 
 | func (s *stepPrinter) printPercent() { | 
 | 	if s.total <= 0 { | 
 | 		return | 
 | 	} | 
 |  | 
 | 	percent := 100 | 
 | 	if s.progress < s.total { | 
 | 		percent = 100 * s.progress / s.total | 
 | 	} | 
 | 	if *pipe { | 
 | 		percent -= percent % 10 | 
 | 	} | 
 | 	if percent == s.lastPercent { | 
 | 		return | 
 | 	} | 
 |  | 
 | 	s.erasePercent() | 
 |  | 
 | 	s.lastPercent = percent | 
 | 	str := fmt.Sprintf("%d%%", percent) | 
 | 	s.percentLen = len(str) | 
 | 	fmt.Printf("%s", str) | 
 | 	if *pipe { | 
 | 		fmt.Printf("\n") | 
 | 	} | 
 | } | 
 |  | 
 | func (s *stepPrinter) progressWriter(total int) io.Writer { | 
 | 	s.setTotal(total) | 
 | 	return &progressWriter{step: s} | 
 | } | 
 |  | 
 | func (s *stepPrinter) httpBodyWithProgress(r *http.Response) io.Reader { | 
 | 	// This does not always give any progress. It seems GitHub will sometimes | 
 | 	// provide a Content-Length header and sometimes not, for the same URL. | 
 | 	return io.TeeReader(r.Body, s.progressWriter(int(r.ContentLength))) | 
 | } | 
 |  | 
 | type progressWriter struct { | 
 | 	step *stepPrinter | 
 | } | 
 |  | 
 | func (p *progressWriter) Write(b []byte) (int, error) { | 
 | 	p.step.addProgress(len(b)) | 
 | 	return len(b), nil | 
 | } |