|  | // Copyright 2024 The BoringSSL Authors | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //     https://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | 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 | 
|  | } |