Skip to content

Commit

Permalink
align units of processed data to total size
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah committed Oct 8, 2023
1 parent aa5b847 commit f49bdcb
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 29 deletions.
51 changes: 51 additions & 0 deletions cmd/oras/internal/display/progress/humanize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
Copyright The ORAS 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
http://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 progress

import (
"math"
)

var (
units = []string{"B", "kB", "MB", "GB", "TB"}
base = 1000.0
)

type bytes struct {
size float64
unit string
}

// ToBytes converts size in bytes to human readable format.
func ToBytes(sizeInBytes int64) bytes {
f := float64(sizeInBytes)
if f < base {
return bytes{f, "B"}
}
e := math.Floor(math.Log(f) / math.Log(base))
p := f / math.Pow(base, e)
return bytes{RoundTo(p), units[int(e)]}
}

// RoundTo makes length of the size string to less than or equal to 4.
func RoundTo(size float64) float64 {
if size < 10 {
return math.Round(size*100) / 100
} else if size < 100 {
return math.Round(size*10) / 10
}
return math.Round(size)
}
41 changes: 41 additions & 0 deletions cmd/oras/internal/display/progress/humanize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
Copyright The ORAS 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
http://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 progress

import "testing"

func TestRoundTo(t *testing.T) {
type args struct {
quantity float64
}
tests := []struct {
name string
args args
want float64
}{
{"round to 2 digit", args{1.223}, 1.22},
{"round to 1 digit", args{12.23}, 12.2},
{"round to no digit", args{122.6}, 123},
{"round to no digit", args{1223.123}, 1223},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := RoundTo(tt.args.quantity); got != tt.want {
t.Errorf("RoundTo() = %v, want %v", got, tt.want)
}
})
}
}
29 changes: 20 additions & 9 deletions cmd/oras/internal/display/progress/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"time"
"unicode/utf8"

"github.com/dustin/go-humanize"
"github.com/morikuni/aec"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
Expand All @@ -40,10 +39,12 @@ type status struct {
prompt string
descriptor ocispec.Descriptor
offset int64
startTime time.Time
endTime time.Time
mark spinner
lock sync.RWMutex
total bytes

startTime time.Time
endTime time.Time
mark spinner
lock sync.RWMutex
}

// newStatus generates a base empty status.
Expand All @@ -58,7 +59,7 @@ func NewStatus(prompt string, descriptor ocispec.Descriptor, offset int64) *stat
return &status{
prompt: prompt,
descriptor: descriptor,
offset: int64(offset),
offset: offset,
}
}

Expand Down Expand Up @@ -102,8 +103,8 @@ func (s *status) String(width int) (string, string) {
name = s.descriptor.MediaType
}

// format: [left--------------------------------][margin][right-------------------------------]
// mark(1) bar(42) action(<10) name(<126) size_per_size(19) percent(8) time(>=6)
// format: [left--------------------------------][margin][right---------------------------------]
// mark(1) bar(42) action(<10) name(<126) size_per_size(<=11) percent(8) time(>=6)
// └─ digest(72)
var left string
lenLeft := 0
Expand All @@ -120,7 +121,14 @@ func (s *status) String(width int) (string, string) {
// mark(1) + space(1) + prompt + space(1) + name = len(prompt) + len(name) + 3
lenLeft += utf8.RuneCountInString(s.prompt) + utf8.RuneCountInString(name) + 3

right := fmt.Sprintf(" %s/%s %6.2f%% %6s", humanize.Bytes(uint64(s.offset)), humanize.Bytes(total), percent*100, s.durationString())
var offset string
switch percent {
case 1: // 100%, show exact size
offset = fmt.Sprint(s.total.size)
default: // 0% ~ 99%, show 2-digit precision
offset = fmt.Sprintf("%.2f", RoundTo(s.total.size*percent))
}
right := fmt.Sprintf(" %s/%v %s %6.2f%% %6s", offset, s.total.size, s.total.unit, percent*100, s.durationString())
lenRight := utf8.RuneCountInString(right)
lenMargin := width - lenLeft - lenRight
if lenMargin < 0 {
Expand Down Expand Up @@ -162,6 +170,9 @@ func (s *status) Update(n *status) {

if n.offset >= 0 {
s.offset = n.offset
if n.descriptor.Size != s.descriptor.Size {
s.total = ToBytes(n.descriptor.Size)
}
s.descriptor = n.descriptor
}
if n.prompt != "" {
Expand Down
39 changes: 22 additions & 17 deletions cmd/oras/internal/display/progress/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,36 @@ func Test_status_String(t *testing.T) {
}

// not done
s.startTime = time.Now().Add(-time.Minute)
s.prompt = "test"
s.descriptor = ocispec.Descriptor{
MediaType: "application/vnd.oci.empty.oras.test.v1+json",
Size: 2,
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
}
s.offset = 0
s.Update(&status{
prompt: "test",
descriptor: ocispec.Descriptor{
MediaType: "application/vnd.oci.empty.oras.test.v1+json",
Size: 2,
Digest: "sha256:44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
},
startTime: time.Now().Add(-time.Minute),
offset: 0,
total: ToBytes(2),
})
// full name
status, digest := s.String(120)
if err := testutils.OrderedMatch(status+digest, " [\x1b[7m\x1b[0m........................................]", s.prompt, s.descriptor.MediaType, "0 B/2 B", "0.00%", s.descriptor.Digest.String()); err != nil {
statusStr, digestStr := s.String(120)
if err := testutils.OrderedMatch(statusStr+digestStr, " [\x1b[7m\x1b[0m........................................]", s.prompt, s.descriptor.MediaType, "0.00/2 B", "0.00%", s.descriptor.Digest.String()); err != nil {
t.Error(err)
}
// partial name
status, digest = s.String(console.MinWidth)
if err := testutils.OrderedMatch(status+digest, " [\x1b[7m\x1b[0m........................................]", s.prompt, "applic.", "0 B/2 B", "0.00%", s.descriptor.Digest.String()); err != nil {
statusStr, digestStr = s.String(console.MinWidth)
if err := testutils.OrderedMatch(statusStr+digestStr, " [\x1b[7m\x1b[0m........................................]", s.prompt, "appli.", "0.00/2 B", "0.00%", s.descriptor.Digest.String()); err != nil {
t.Error(err)
}

// done
s.done = true
s.endTime = s.startTime.Add(time.Minute)
s.offset = s.descriptor.Size
status, digest = s.String(120)
if err := testutils.OrderedMatch(status+digest, "√", s.prompt, s.descriptor.MediaType, "2 B/2 B", "100.00%", s.descriptor.Digest.String()); err != nil {
s.Update(&status{
endTime: time.Now(),
offset: s.descriptor.Size,
descriptor: s.descriptor,
})
statusStr, digestStr = s.String(120)
if err := testutils.OrderedMatch(statusStr+digestStr, "√", s.prompt, s.descriptor.MediaType, "2/2 B", "100.00%", s.descriptor.Digest.String()); err != nil {
t.Error(err)
}
}
Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ go 1.21

require (
github.com/containerd/console v1.0.3
github.com/dustin/go-humanize v1.0.1
github.com/morikuni/aec v1.0.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc4
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
Expand Down

0 comments on commit f49bdcb

Please sign in to comment.