github.com/divyam234/rclone@v1.64.1/fs/sizesuffix.go (about)

     1  package fs
     2  
     3  // SizeSuffix is parsed by flag with K/M/G binary suffixes
     4  import (
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"math"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  )
    13  
    14  // SizeSuffix is an int64 with a friendly way of printing setting
    15  type SizeSuffix int64
    16  
    17  // Common multipliers for SizeSuffix
    18  const (
    19  	SizeSuffixBase SizeSuffix = 1 << (iota * 10)
    20  	Kibi
    21  	Mebi
    22  	Gibi
    23  	Tebi
    24  	Pebi
    25  	Exbi
    26  )
    27  const (
    28  	// SizeSuffixMax is the largest SizeSuffix multiplier
    29  	SizeSuffixMax = Exbi
    30  	// SizeSuffixMaxValue is the largest value that can be used to create SizeSuffix
    31  	SizeSuffixMaxValue = math.MaxInt64
    32  	// SizeSuffixMinValue is the smallest value that can be used to create SizeSuffix
    33  	SizeSuffixMinValue = math.MinInt64
    34  )
    35  
    36  // Turn SizeSuffix into a string and a suffix
    37  func (x SizeSuffix) string() (string, string) {
    38  	scaled := float64(0)
    39  	suffix := ""
    40  	switch {
    41  	case x < 0:
    42  		return "off", ""
    43  	case x == 0:
    44  		return "0", ""
    45  	case x < Kibi:
    46  		scaled = float64(x)
    47  		suffix = ""
    48  	case x < Mebi:
    49  		scaled = float64(x) / float64(Kibi)
    50  		suffix = "Ki"
    51  	case x < Gibi:
    52  		scaled = float64(x) / float64(Mebi)
    53  		suffix = "Mi"
    54  	case x < Tebi:
    55  		scaled = float64(x) / float64(Gibi)
    56  		suffix = "Gi"
    57  	case x < Pebi:
    58  		scaled = float64(x) / float64(Tebi)
    59  		suffix = "Ti"
    60  	case x < Exbi:
    61  		scaled = float64(x) / float64(Pebi)
    62  		suffix = "Pi"
    63  	default:
    64  		scaled = float64(x) / float64(Exbi)
    65  		suffix = "Ei"
    66  	}
    67  	if math.Floor(scaled) == scaled {
    68  		return fmt.Sprintf("%.0f", scaled), suffix
    69  	}
    70  	return fmt.Sprintf("%.3f", scaled), suffix
    71  }
    72  
    73  // String turns SizeSuffix into a string
    74  func (x SizeSuffix) String() string {
    75  	val, suffix := x.string()
    76  	return val + suffix
    77  }
    78  
    79  // Unit turns SizeSuffix into a string with a unit
    80  func (x SizeSuffix) unit(unit string) string {
    81  	val, suffix := x.string()
    82  	if val == "off" {
    83  		return val
    84  	}
    85  	var suffixUnit string
    86  	if suffix != "" && unit != "" {
    87  		suffixUnit = suffix + unit
    88  	} else {
    89  		suffixUnit = suffix + unit
    90  	}
    91  	return val + " " + suffixUnit
    92  }
    93  
    94  // BitUnit turns SizeSuffix into a string with bit unit
    95  func (x SizeSuffix) BitUnit() string {
    96  	return x.unit("bit")
    97  }
    98  
    99  // BitRateUnit turns SizeSuffix into a string with bit rate unit
   100  func (x SizeSuffix) BitRateUnit() string {
   101  	return x.unit("bit/s")
   102  }
   103  
   104  // ByteUnit turns SizeSuffix into a string with byte unit
   105  func (x SizeSuffix) ByteUnit() string {
   106  	return x.unit("B")
   107  }
   108  
   109  // ByteRateUnit turns SizeSuffix into a string with byte rate unit
   110  func (x SizeSuffix) ByteRateUnit() string {
   111  	return x.unit("B/s")
   112  }
   113  
   114  func (x *SizeSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
   115  	switch s {
   116  	case 'k', 'K':
   117  		return true, float64(Kibi)
   118  	case 'm', 'M':
   119  		return true, float64(Mebi)
   120  	case 'g', 'G':
   121  		return true, float64(Gibi)
   122  	case 't', 'T':
   123  		return true, float64(Tebi)
   124  	case 'p', 'P':
   125  		return true, float64(Pebi)
   126  	case 'e', 'E':
   127  		return true, float64(Exbi)
   128  	default:
   129  		return false, float64(SizeSuffixBase)
   130  	}
   131  }
   132  
   133  // Set a SizeSuffix
   134  func (x *SizeSuffix) Set(s string) error {
   135  	if len(s) == 0 {
   136  		return errors.New("empty string")
   137  	}
   138  	if strings.ToLower(s) == "off" {
   139  		*x = -1
   140  		return nil
   141  	}
   142  	suffix := s[len(s)-1]
   143  	suffixLen := 1
   144  	multiplierFound := false
   145  	var multiplier float64
   146  	switch suffix {
   147  	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
   148  		suffixLen = 0
   149  		multiplier = float64(Kibi)
   150  	case 'b', 'B':
   151  		if len(s) > 2 && s[len(s)-2] == 'i' {
   152  			suffix = s[len(s)-3]
   153  			suffixLen = 3
   154  			if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
   155  				return fmt.Errorf("bad suffix %q", suffix)
   156  			}
   157  			// Could also support SI form MB, and treat it equivalent to MiB, but perhaps better to reserve it for CountSuffix?
   158  			//} else if len(s) > 1 {
   159  			//	suffix = s[len(s)-2]
   160  			//	if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
   161  			//		suffixLen = 2
   162  			//	}
   163  			//}
   164  		} else {
   165  			multiplier = float64(SizeSuffixBase)
   166  		}
   167  	case 'i', 'I':
   168  		if len(s) > 1 {
   169  			suffix = s[len(s)-2]
   170  			suffixLen = 2
   171  			multiplierFound, multiplier = x.multiplierFromSymbol(suffix)
   172  		}
   173  		if !multiplierFound {
   174  			return fmt.Errorf("bad suffix %q", suffix)
   175  		}
   176  	default:
   177  		if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
   178  			return fmt.Errorf("bad suffix %q", suffix)
   179  		}
   180  	}
   181  	s = s[:len(s)-suffixLen]
   182  	value, err := strconv.ParseFloat(s, 64)
   183  	if err != nil {
   184  		return err
   185  	}
   186  	if value < 0 {
   187  		return fmt.Errorf("size can't be negative %q", s)
   188  	}
   189  	value *= multiplier
   190  	*x = SizeSuffix(value)
   191  	return nil
   192  }
   193  
   194  // Type of the value
   195  func (x *SizeSuffix) Type() string {
   196  	return "SizeSuffix"
   197  }
   198  
   199  // Scan implements the fmt.Scanner interface
   200  func (x *SizeSuffix) Scan(s fmt.ScanState, ch rune) error {
   201  	token, err := s.Token(true, nil)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	return x.Set(string(token))
   206  }
   207  
   208  // SizeSuffixList is a slice SizeSuffix values
   209  type SizeSuffixList []SizeSuffix
   210  
   211  func (l SizeSuffixList) Len() int           { return len(l) }
   212  func (l SizeSuffixList) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
   213  func (l SizeSuffixList) Less(i, j int) bool { return l[i] < l[j] }
   214  
   215  // Sort sorts the list
   216  func (l SizeSuffixList) Sort() {
   217  	sort.Sort(l)
   218  }
   219  
   220  // UnmarshalJSONFlag unmarshals a JSON input for a flag. If the input
   221  // is a string then it calls the Set method on the flag otherwise it
   222  // calls the setInt function with a parsed int64.
   223  func UnmarshalJSONFlag(in []byte, x interface{ Set(string) error }, setInt func(int64) error) error {
   224  	// Try to parse as string first
   225  	var s string
   226  	err := json.Unmarshal(in, &s)
   227  	if err == nil {
   228  		return x.Set(s)
   229  	}
   230  	// If that fails parse as integer
   231  	var i int64
   232  	err = json.Unmarshal(in, &i)
   233  	if err != nil {
   234  		return err
   235  	}
   236  	return setInt(i)
   237  }
   238  
   239  // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
   240  func (x *SizeSuffix) UnmarshalJSON(in []byte) error {
   241  	return UnmarshalJSONFlag(in, x, func(i int64) error {
   242  		*x = SizeSuffix(i)
   243  		return nil
   244  	})
   245  }