| // 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 | |
| } |