mirror of
https://github.com/containers/skopeo.git
synced 2025-09-28 05:25:48 +00:00
192 lines
4.2 KiB
Go
192 lines
4.2 KiB
Go
package mpb
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"unicode/utf8"
|
|
|
|
"github.com/mattn/go-runewidth"
|
|
"github.com/rivo/uniseg"
|
|
"github.com/vbauerster/mpb/v6/decor"
|
|
"github.com/vbauerster/mpb/v6/internal"
|
|
)
|
|
|
|
const (
|
|
rLeft = iota
|
|
rFill
|
|
rTip
|
|
rSpace
|
|
rRight
|
|
rRevTip
|
|
rRefill
|
|
)
|
|
|
|
// BarDefaultStyle is a style for rendering a progress bar.
|
|
// It consist of 7 ordered runes:
|
|
//
|
|
// '1st rune' stands for left boundary rune
|
|
//
|
|
// '2nd rune' stands for fill rune
|
|
//
|
|
// '3rd rune' stands for tip rune
|
|
//
|
|
// '4th rune' stands for space rune
|
|
//
|
|
// '5th rune' stands for right boundary rune
|
|
//
|
|
// '6th rune' stands for reverse tip rune
|
|
//
|
|
// '7th rune' stands for refill rune
|
|
//
|
|
const BarDefaultStyle string = "[=>-]<+"
|
|
|
|
type barFiller struct {
|
|
format [][]byte
|
|
rwidth []int
|
|
tip []byte
|
|
refill int64
|
|
reverse bool
|
|
flush func(io.Writer, *space, [][]byte)
|
|
}
|
|
|
|
type space struct {
|
|
space []byte
|
|
rwidth int
|
|
count int
|
|
}
|
|
|
|
// NewBarFiller returns a BarFiller implementation which renders a
|
|
// progress bar in regular direction. If style is empty string,
|
|
// BarDefaultStyle is applied. To be used with `*Progress.Add(...)
|
|
// *Bar` method.
|
|
func NewBarFiller(style string) BarFiller {
|
|
return newBarFiller(style, false)
|
|
}
|
|
|
|
// NewBarFillerRev returns a BarFiller implementation which renders a
|
|
// progress bar in reverse direction. If style is empty string,
|
|
// BarDefaultStyle is applied. To be used with `*Progress.Add(...)
|
|
// *Bar` method.
|
|
func NewBarFillerRev(style string) BarFiller {
|
|
return newBarFiller(style, true)
|
|
}
|
|
|
|
// NewBarFillerPick pick between regular and reverse BarFiller implementation
|
|
// based on rev param. To be used with `*Progress.Add(...) *Bar` method.
|
|
func NewBarFillerPick(style string, rev bool) BarFiller {
|
|
return newBarFiller(style, rev)
|
|
}
|
|
|
|
func newBarFiller(style string, rev bool) BarFiller {
|
|
bf := &barFiller{
|
|
format: make([][]byte, len(BarDefaultStyle)),
|
|
rwidth: make([]int, len(BarDefaultStyle)),
|
|
reverse: rev,
|
|
}
|
|
bf.parse(BarDefaultStyle)
|
|
if style != "" && style != BarDefaultStyle {
|
|
bf.parse(style)
|
|
}
|
|
return bf
|
|
}
|
|
|
|
func (s *barFiller) parse(style string) {
|
|
if !utf8.ValidString(style) {
|
|
panic("invalid bar style")
|
|
}
|
|
srcFormat := make([][]byte, len(BarDefaultStyle))
|
|
srcRwidth := make([]int, len(BarDefaultStyle))
|
|
i := 0
|
|
for gr := uniseg.NewGraphemes(style); i < len(BarDefaultStyle) && gr.Next(); i++ {
|
|
srcFormat[i] = gr.Bytes()
|
|
srcRwidth[i] = runewidth.StringWidth(gr.Str())
|
|
}
|
|
copy(s.format, srcFormat[:i])
|
|
copy(s.rwidth, srcRwidth[:i])
|
|
if s.reverse {
|
|
s.tip = s.format[rRevTip]
|
|
s.flush = reverseFlush
|
|
} else {
|
|
s.tip = s.format[rTip]
|
|
s.flush = regularFlush
|
|
}
|
|
}
|
|
|
|
func (s *barFiller) Fill(w io.Writer, reqWidth int, stat decor.Statistics) {
|
|
width := internal.CheckRequestedWidth(reqWidth, stat.AvailableWidth)
|
|
brackets := s.rwidth[rLeft] + s.rwidth[rRight]
|
|
if width < brackets {
|
|
return
|
|
}
|
|
// don't count brackets as progress
|
|
width -= brackets
|
|
|
|
w.Write(s.format[rLeft])
|
|
defer w.Write(s.format[rRight])
|
|
|
|
cwidth := int(internal.PercentageRound(stat.Total, stat.Current, width))
|
|
space := &space{
|
|
space: s.format[rSpace],
|
|
rwidth: s.rwidth[rSpace],
|
|
count: width - cwidth,
|
|
}
|
|
|
|
index, refill := 0, 0
|
|
bb := make([][]byte, cwidth)
|
|
|
|
if cwidth > 0 && cwidth != width {
|
|
bb[index] = s.tip
|
|
cwidth -= s.rwidth[rTip]
|
|
index++
|
|
}
|
|
|
|
if stat.Refill > 0 {
|
|
refill = int(internal.PercentageRound(stat.Total, int64(stat.Refill), width))
|
|
if refill > cwidth {
|
|
refill = cwidth
|
|
}
|
|
cwidth -= refill
|
|
}
|
|
|
|
for cwidth > 0 {
|
|
bb[index] = s.format[rFill]
|
|
cwidth -= s.rwidth[rFill]
|
|
index++
|
|
}
|
|
|
|
for refill > 0 {
|
|
bb[index] = s.format[rRefill]
|
|
refill -= s.rwidth[rRefill]
|
|
index++
|
|
}
|
|
|
|
if cwidth+refill < 0 || space.rwidth > 1 {
|
|
buf := new(bytes.Buffer)
|
|
s.flush(buf, space, bb[:index])
|
|
io.WriteString(w, runewidth.Truncate(buf.String(), width, "…"))
|
|
return
|
|
}
|
|
|
|
s.flush(w, space, bb)
|
|
}
|
|
|
|
func regularFlush(w io.Writer, space *space, bb [][]byte) {
|
|
for i := len(bb) - 1; i >= 0; i-- {
|
|
w.Write(bb[i])
|
|
}
|
|
for space.count > 0 {
|
|
w.Write(space.space)
|
|
space.count -= space.rwidth
|
|
}
|
|
}
|
|
|
|
func reverseFlush(w io.Writer, space *space, bb [][]byte) {
|
|
for space.count > 0 {
|
|
w.Write(space.space)
|
|
space.count -= space.rwidth
|
|
}
|
|
for i := 0; i < len(bb); i++ {
|
|
w.Write(bb[i])
|
|
}
|
|
}
|