mirror of
https://github.com/rclone/rclone.git
synced 2024-12-22 23:22:08 +01:00
parent
2925e1384c
commit
08a2df51be
@ -373,7 +373,7 @@ func (u *UI) Draw() error {
|
|||||||
extras := ""
|
extras := ""
|
||||||
if u.showCounts {
|
if u.showCounts {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
extras += fmt.Sprintf("%8v ", fs.SizeSuffix(count))
|
extras += fmt.Sprintf("%8v ", fs.CountSuffix(count))
|
||||||
} else {
|
} else {
|
||||||
extras += " "
|
extras += " "
|
||||||
}
|
}
|
||||||
|
189
fs/countsuffix.go
Normal file
189
fs/countsuffix.go
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
// CountSuffix is parsed by flag with k/M/G decimal suffixes
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CountSuffix is an int64 with a friendly way of printing setting
|
||||||
|
type CountSuffix int64
|
||||||
|
|
||||||
|
// Common multipliers for SizeSuffix
|
||||||
|
const (
|
||||||
|
CountSuffixBase CountSuffix = 1
|
||||||
|
Kilo = 1000 * CountSuffixBase
|
||||||
|
Mega = 1000 * Kilo
|
||||||
|
Giga = 1000 * Mega
|
||||||
|
Tera = 1000 * Giga
|
||||||
|
Peta = 1000 * Tera
|
||||||
|
Exa = 1000 * Peta
|
||||||
|
)
|
||||||
|
const (
|
||||||
|
// CountSuffixMax is the largest CountSuffix multiplier
|
||||||
|
CountSuffixMax = Exa
|
||||||
|
// CountSuffixMaxValue is the largest value that can be used to create CountSuffix
|
||||||
|
CountSuffixMaxValue = math.MaxInt64
|
||||||
|
// CountSuffixMinValue is the smallest value that can be used to create CountSuffix
|
||||||
|
CountSuffixMinValue = math.MinInt64
|
||||||
|
)
|
||||||
|
|
||||||
|
// Turn CountSuffix into a string and a suffix
|
||||||
|
func (x CountSuffix) string() (string, string) {
|
||||||
|
scaled := float64(0)
|
||||||
|
suffix := ""
|
||||||
|
switch {
|
||||||
|
case x < 0:
|
||||||
|
return "off", ""
|
||||||
|
case x == 0:
|
||||||
|
return "0", ""
|
||||||
|
case x < Kilo:
|
||||||
|
scaled = float64(x)
|
||||||
|
suffix = ""
|
||||||
|
case x < Mega:
|
||||||
|
scaled = float64(x) / float64(Kilo)
|
||||||
|
suffix = "k"
|
||||||
|
case x < Giga:
|
||||||
|
scaled = float64(x) / float64(Mega)
|
||||||
|
suffix = "M"
|
||||||
|
case x < Tera:
|
||||||
|
scaled = float64(x) / float64(Giga)
|
||||||
|
suffix = "G"
|
||||||
|
case x < Peta:
|
||||||
|
scaled = float64(x) / float64(Tera)
|
||||||
|
suffix = "T"
|
||||||
|
case x < Exa:
|
||||||
|
scaled = float64(x) / float64(Peta)
|
||||||
|
suffix = "P"
|
||||||
|
default:
|
||||||
|
scaled = float64(x) / float64(Exa)
|
||||||
|
suffix = "E"
|
||||||
|
}
|
||||||
|
if math.Floor(scaled) == scaled {
|
||||||
|
return fmt.Sprintf("%.0f", scaled), suffix
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.3f", scaled), suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// String turns CountSuffix into a string
|
||||||
|
func (x CountSuffix) String() string {
|
||||||
|
val, suffix := x.string()
|
||||||
|
return val + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit turns CountSuffix into a string with a unit
|
||||||
|
func (x CountSuffix) Unit(unit string) string {
|
||||||
|
val, suffix := x.string()
|
||||||
|
if val == "off" {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
var suffixUnit string
|
||||||
|
if suffix != "" && unit != "" {
|
||||||
|
suffixUnit = suffix + unit
|
||||||
|
} else {
|
||||||
|
suffixUnit = suffix + unit
|
||||||
|
}
|
||||||
|
return val + " " + suffixUnit
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *CountSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
|
||||||
|
switch s {
|
||||||
|
case 'k', 'K':
|
||||||
|
return true, float64(Kilo)
|
||||||
|
case 'm', 'M':
|
||||||
|
return true, float64(Mega)
|
||||||
|
case 'g', 'G':
|
||||||
|
return true, float64(Giga)
|
||||||
|
case 't', 'T':
|
||||||
|
return true, float64(Tera)
|
||||||
|
case 'p', 'P':
|
||||||
|
return true, float64(Peta)
|
||||||
|
case 'e', 'E':
|
||||||
|
return true, float64(Exa)
|
||||||
|
default:
|
||||||
|
return false, float64(CountSuffixBase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a CountSuffix
|
||||||
|
func (x *CountSuffix) Set(s string) error {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return errors.New("empty string")
|
||||||
|
}
|
||||||
|
if strings.ToLower(s) == "off" {
|
||||||
|
*x = -1
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
suffix := s[len(s)-1]
|
||||||
|
suffixLen := 1
|
||||||
|
multiplierFound := false
|
||||||
|
var multiplier float64
|
||||||
|
switch suffix {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
|
||||||
|
suffixLen = 0
|
||||||
|
multiplier = float64(Kilo)
|
||||||
|
case 'b', 'B':
|
||||||
|
if len(s) > 1 {
|
||||||
|
suffix = s[len(s)-2]
|
||||||
|
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
|
||||||
|
suffixLen = 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
multiplier = float64(CountSuffixBase)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
|
||||||
|
return errors.Errorf("bad suffix %q", suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s = s[:len(s)-suffixLen]
|
||||||
|
value, err := strconv.ParseFloat(s, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if value < 0 {
|
||||||
|
return errors.Errorf("size can't be negative %q", s)
|
||||||
|
}
|
||||||
|
value *= multiplier
|
||||||
|
*x = CountSuffix(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type of the value
|
||||||
|
func (x *CountSuffix) Type() string {
|
||||||
|
return "CountSuffix"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan implements the fmt.Scanner interface
|
||||||
|
func (x *CountSuffix) Scan(s fmt.ScanState, ch rune) error {
|
||||||
|
token, err := s.Token(true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return x.Set(string(token))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CountSuffixList is a slice CountSuffix values
|
||||||
|
type CountSuffixList []CountSuffix
|
||||||
|
|
||||||
|
func (l CountSuffixList) Len() int { return len(l) }
|
||||||
|
func (l CountSuffixList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||||
|
func (l CountSuffixList) Less(i, j int) bool { return l[i] < l[j] }
|
||||||
|
|
||||||
|
// Sort sorts the list
|
||||||
|
func (l CountSuffixList) Sort() {
|
||||||
|
sort.Sort(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
|
||||||
|
func (x *CountSuffix) UnmarshalJSON(in []byte) error {
|
||||||
|
return UnmarshalJSONFlag(in, x, func(i int64) error {
|
||||||
|
*x = CountSuffix(i)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
147
fs/countsuffix_test.go
Normal file
147
fs/countsuffix_test.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check it satisfies the interface
|
||||||
|
var _ flagger = (*CountSuffix)(nil)
|
||||||
|
|
||||||
|
func TestCountSuffixString(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in float64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0"},
|
||||||
|
{102, "102"},
|
||||||
|
{1000, "1k"},
|
||||||
|
{1000 * 1000, "1M"},
|
||||||
|
{1000 * 1000 * 1000, "1G"},
|
||||||
|
{10 * 1000 * 1000 * 1000, "10G"},
|
||||||
|
{10.1 * 1000 * 1000 * 1000, "10.100G"},
|
||||||
|
{-1, "off"},
|
||||||
|
{-100, "off"},
|
||||||
|
} {
|
||||||
|
ss := CountSuffix(test.in)
|
||||||
|
got := ss.String()
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountSuffixUnit(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in float64
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{0, "0 Byte"},
|
||||||
|
{102, "102 Byte"},
|
||||||
|
{1000, "1 kByte"},
|
||||||
|
{1000 * 1000, "1 MByte"},
|
||||||
|
{1000 * 1000 * 1000, "1 GByte"},
|
||||||
|
{10 * 1000 * 1000 * 1000, "10 GByte"},
|
||||||
|
{10.1 * 1000 * 1000 * 1000, "10.100 GByte"},
|
||||||
|
{10 * 1000 * 1000 * 1000 * 1000, "10 TByte"},
|
||||||
|
{10 * 1000 * 1000 * 1000 * 1000 * 1000, "10 PByte"},
|
||||||
|
{1 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, "1 EByte"},
|
||||||
|
{-1, "off"},
|
||||||
|
{-100, "off"},
|
||||||
|
} {
|
||||||
|
ss := CountSuffix(test.in)
|
||||||
|
got := ss.Unit("Byte")
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountSuffixSet(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want int64
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"0", 0, false},
|
||||||
|
{"1b", 1, false},
|
||||||
|
{"100B", 100, false},
|
||||||
|
{"0.1k", 100, false},
|
||||||
|
{"0.1", 100, false},
|
||||||
|
{"1K", 1000, false},
|
||||||
|
{"1k", 1000, false},
|
||||||
|
{"1KB", 1000, false},
|
||||||
|
{"1kB", 1000, false},
|
||||||
|
{"1kb", 1000, false},
|
||||||
|
{"1", 1000, false},
|
||||||
|
{"2.5", 1000 * 2.5, false},
|
||||||
|
{"1M", 1000 * 1000, false},
|
||||||
|
{"1MB", 1000 * 1000, false},
|
||||||
|
{"1.g", 1000 * 1000 * 1000, false},
|
||||||
|
{"10G", 10 * 1000 * 1000 * 1000, false},
|
||||||
|
{"10T", 10 * 1000 * 1000 * 1000 * 1000, false},
|
||||||
|
{"10P", 10 * 1000 * 1000 * 1000 * 1000 * 1000, false},
|
||||||
|
{"off", -1, false},
|
||||||
|
{"OFF", -1, false},
|
||||||
|
{"", 0, true},
|
||||||
|
{"1q", 0, true},
|
||||||
|
{"1.q", 0, true},
|
||||||
|
{"1q", 0, true},
|
||||||
|
{"-1K", 0, true},
|
||||||
|
{"1i", 0, true},
|
||||||
|
{"1iB", 0, true},
|
||||||
|
{"1Ki", 0, true},
|
||||||
|
{"1KiB", 0, true},
|
||||||
|
} {
|
||||||
|
ss := CountSuffix(0)
|
||||||
|
err := ss.Set(test.in)
|
||||||
|
if test.err {
|
||||||
|
require.Error(t, err, test.in)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, test.in)
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.want, int64(ss))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountSuffixScan(t *testing.T) {
|
||||||
|
var v CountSuffix
|
||||||
|
n, err := fmt.Sscan(" 17M ", &v)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, 1, n)
|
||||||
|
assert.Equal(t, CountSuffix(17000000), v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCountSuffixUnmarshalJSON(t *testing.T) {
|
||||||
|
for _, test := range []struct {
|
||||||
|
in string
|
||||||
|
want int64
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{`"0"`, 0, false},
|
||||||
|
{`"102B"`, 102, false},
|
||||||
|
{`"1K"`, 1000, false},
|
||||||
|
{`"2.5"`, 1000 * 2.5, false},
|
||||||
|
{`"1M"`, 1000 * 1000, false},
|
||||||
|
{`"1.g"`, 1000 * 1000 * 1000, false},
|
||||||
|
{`"10G"`, 10 * 1000 * 1000 * 1000, false},
|
||||||
|
{`"off"`, -1, false},
|
||||||
|
{`""`, 0, true},
|
||||||
|
{`"1q"`, 0, true},
|
||||||
|
{`"-1K"`, 0, true},
|
||||||
|
{`0`, 0, false},
|
||||||
|
{`102`, 102, false},
|
||||||
|
{`1000`, 1000, false},
|
||||||
|
{`1000000000`, 1000000000, false},
|
||||||
|
{`1.1.1`, 0, true},
|
||||||
|
} {
|
||||||
|
var ss CountSuffix
|
||||||
|
err := json.Unmarshal([]byte(test.in), &ss)
|
||||||
|
if test.err {
|
||||||
|
require.Error(t, err, test.in)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, test.in)
|
||||||
|
}
|
||||||
|
assert.Equal(t, test.want, int64(ss))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user