github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/countsuffix.go (about)

     1  package fs
     2  
     3  // CountSuffix is parsed by flag with k/M/G decimal suffixes
     4  import (
     5  	"errors"
     6  	"fmt"
     7  	"math"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  )
    12  
    13  // CountSuffix is an int64 with a friendly way of printing setting
    14  type CountSuffix int64
    15  
    16  // Common multipliers for SizeSuffix
    17  const (
    18  	CountSuffixBase CountSuffix = 1
    19  	Kilo                        = 1000 * CountSuffixBase
    20  	Mega                        = 1000 * Kilo
    21  	Giga                        = 1000 * Mega
    22  	Tera                        = 1000 * Giga
    23  	Peta                        = 1000 * Tera
    24  	Exa                         = 1000 * Peta
    25  )
    26  const (
    27  	// CountSuffixMax is the largest CountSuffix multiplier
    28  	CountSuffixMax = Exa
    29  	// CountSuffixMaxValue is the largest value that can be used to create CountSuffix
    30  	CountSuffixMaxValue = math.MaxInt64
    31  	// CountSuffixMinValue is the smallest value that can be used to create CountSuffix
    32  	CountSuffixMinValue = math.MinInt64
    33  )
    34  
    35  // Turn CountSuffix into a string and a suffix
    36  func (x CountSuffix) string() (string, string) {
    37  	scaled := float64(0)
    38  	suffix := ""
    39  	switch {
    40  	case x < 0:
    41  		return "off", ""
    42  	case x == 0:
    43  		return "0", ""
    44  	case x < Kilo:
    45  		scaled = float64(x)
    46  		suffix = ""
    47  	case x < Mega:
    48  		scaled = float64(x) / float64(Kilo)
    49  		suffix = "k"
    50  	case x < Giga:
    51  		scaled = float64(x) / float64(Mega)
    52  		suffix = "M"
    53  	case x < Tera:
    54  		scaled = float64(x) / float64(Giga)
    55  		suffix = "G"
    56  	case x < Peta:
    57  		scaled = float64(x) / float64(Tera)
    58  		suffix = "T"
    59  	case x < Exa:
    60  		scaled = float64(x) / float64(Peta)
    61  		suffix = "P"
    62  	default:
    63  		scaled = float64(x) / float64(Exa)
    64  		suffix = "E"
    65  	}
    66  	if math.Floor(scaled) == scaled {
    67  		return fmt.Sprintf("%.0f", scaled), suffix
    68  	}
    69  	return fmt.Sprintf("%.3f", scaled), suffix
    70  }
    71  
    72  // String turns CountSuffix into a string
    73  func (x CountSuffix) String() string {
    74  	val, suffix := x.string()
    75  	return val + suffix
    76  }
    77  
    78  // Unit turns CountSuffix into a string with a unit
    79  func (x CountSuffix) Unit(unit string) string {
    80  	val, suffix := x.string()
    81  	if val == "off" {
    82  		return val
    83  	}
    84  	var suffixUnit string
    85  	if suffix != "" && unit != "" {
    86  		suffixUnit = suffix + unit
    87  	} else {
    88  		suffixUnit = suffix + unit
    89  	}
    90  	return val + " " + suffixUnit
    91  }
    92  
    93  func (x *CountSuffix) multiplierFromSymbol(s byte) (found bool, multiplier float64) {
    94  	switch s {
    95  	case 'k', 'K':
    96  		return true, float64(Kilo)
    97  	case 'm', 'M':
    98  		return true, float64(Mega)
    99  	case 'g', 'G':
   100  		return true, float64(Giga)
   101  	case 't', 'T':
   102  		return true, float64(Tera)
   103  	case 'p', 'P':
   104  		return true, float64(Peta)
   105  	case 'e', 'E':
   106  		return true, float64(Exa)
   107  	default:
   108  		return false, float64(CountSuffixBase)
   109  	}
   110  }
   111  
   112  // Set a CountSuffix
   113  func (x *CountSuffix) Set(s string) error {
   114  	if len(s) == 0 {
   115  		return errors.New("empty string")
   116  	}
   117  	if strings.ToLower(s) == "off" {
   118  		*x = -1
   119  		return nil
   120  	}
   121  	suffix := s[len(s)-1]
   122  	suffixLen := 1
   123  	multiplierFound := false
   124  	var multiplier float64
   125  	switch suffix {
   126  	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.':
   127  		suffixLen = 0
   128  		multiplier = float64(Kilo)
   129  	case 'b', 'B':
   130  		if len(s) > 1 {
   131  			suffix = s[len(s)-2]
   132  			if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); multiplierFound {
   133  				suffixLen = 2
   134  			}
   135  		} else {
   136  			multiplier = float64(CountSuffixBase)
   137  		}
   138  	default:
   139  		if multiplierFound, multiplier = x.multiplierFromSymbol(suffix); !multiplierFound {
   140  			return fmt.Errorf("bad suffix %q", suffix)
   141  		}
   142  	}
   143  	s = s[:len(s)-suffixLen]
   144  	value, err := strconv.ParseFloat(s, 64)
   145  	if err != nil {
   146  		return err
   147  	}
   148  	if value < 0 {
   149  		return fmt.Errorf("size can't be negative %q", s)
   150  	}
   151  	value *= multiplier
   152  	*x = CountSuffix(value)
   153  	return nil
   154  }
   155  
   156  // Type of the value
   157  func (x CountSuffix) Type() string {
   158  	return "CountSuffix"
   159  }
   160  
   161  // Scan implements the fmt.Scanner interface
   162  func (x *CountSuffix) Scan(s fmt.ScanState, ch rune) error {
   163  	token, err := s.Token(true, nil)
   164  	if err != nil {
   165  		return err
   166  	}
   167  	return x.Set(string(token))
   168  }
   169  
   170  // CountSuffixList is a slice CountSuffix values
   171  type CountSuffixList []CountSuffix
   172  
   173  func (l CountSuffixList) Len() int           { return len(l) }
   174  func (l CountSuffixList) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }
   175  func (l CountSuffixList) Less(i, j int) bool { return l[i] < l[j] }
   176  
   177  // Sort sorts the list
   178  func (l CountSuffixList) Sort() {
   179  	sort.Sort(l)
   180  }
   181  
   182  // UnmarshalJSON makes sure the value can be parsed as a string or integer in JSON
   183  func (x *CountSuffix) UnmarshalJSON(in []byte) error {
   184  	return UnmarshalJSONFlag(in, x, func(i int64) error {
   185  		*x = CountSuffix(i)
   186  		return nil
   187  	})
   188  }