formatting.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. // Copyright (C) 2018 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "bytes"
  9. "fmt"
  10. "strings"
  11. )
  12. type NumberType int
  13. const (
  14. NumberMetric NumberType = iota
  15. NumberBinary
  16. NumberDuration
  17. )
  18. func number(ntype NumberType, v float64) string {
  19. if ntype == NumberDuration {
  20. return duration(v)
  21. } else if ntype == NumberBinary {
  22. return binary(v)
  23. } else {
  24. return metric(v)
  25. }
  26. }
  27. type suffix struct {
  28. Suffix string
  29. Multiplier float64
  30. }
  31. var metricSuffixes = []suffix{
  32. {"G", 1e9},
  33. {"M", 1e6},
  34. {"k", 1e3},
  35. }
  36. var binarySuffixes = []suffix{
  37. {"Gi", 1 << 30},
  38. {"Mi", 1 << 20},
  39. {"Ki", 1 << 10},
  40. }
  41. var durationSuffix = []suffix{
  42. {"year", 365 * 24 * 60 * 60},
  43. {"month", 30 * 24 * 60 * 60},
  44. {"day", 24 * 60 * 60},
  45. {"hour", 60 * 60},
  46. {"minute", 60},
  47. {"second", 1},
  48. }
  49. func metric(v float64) string {
  50. return withSuffix(v, metricSuffixes, false)
  51. }
  52. func binary(v float64) string {
  53. return withSuffix(v, binarySuffixes, false)
  54. }
  55. func duration(v float64) string {
  56. return withSuffix(v, durationSuffix, true)
  57. }
  58. func withSuffix(v float64, ps []suffix, pluralize bool) string {
  59. for _, p := range ps {
  60. if v >= p.Multiplier {
  61. suffix := p.Suffix
  62. if pluralize && v/p.Multiplier != 1.0 {
  63. suffix += "s"
  64. }
  65. // If the number only has decimal zeroes, strip em off.
  66. num := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v/p.Multiplier), "0"), ".")
  67. return fmt.Sprintf("%s %s", num, suffix)
  68. }
  69. }
  70. return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
  71. }
  72. // commatize returns a number with sep as thousands separators. Handles
  73. // integers and plain floats.
  74. func commatize(sep, s string) string {
  75. // If no dot, don't do anything.
  76. if !strings.ContainsRune(s, '.') {
  77. return s
  78. }
  79. var b bytes.Buffer
  80. fs := strings.SplitN(s, ".", 2)
  81. l := len(fs[0])
  82. for i := range fs[0] {
  83. b.Write([]byte{s[i]})
  84. if i < l-1 && (l-i)%3 == 1 {
  85. b.WriteString(sep)
  86. }
  87. }
  88. if len(fs) > 1 && len(fs[1]) > 0 {
  89. b.WriteString(".")
  90. b.WriteString(fs[1])
  91. }
  92. return b.String()
  93. }
  94. func proportion(m map[string]int, count int) float64 {
  95. total := 0
  96. isMax := true
  97. for _, n := range m {
  98. total += n
  99. if n > count {
  100. isMax = false
  101. }
  102. }
  103. pct := (100 * float64(count)) / float64(total)
  104. // To avoid rounding errors in the template, surpassing 100% and breaking
  105. // the progress bars.
  106. if isMax && len(m) > 1 && count != total {
  107. pct -= 0.01
  108. }
  109. return pct
  110. }