formatting.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  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 serve
  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. switch ntype {
  20. case NumberMetric:
  21. return metric(v)
  22. case NumberDuration:
  23. return duration(v)
  24. case NumberBinary:
  25. return binary(v)
  26. default:
  27. return metric(v)
  28. }
  29. }
  30. type suffix struct {
  31. Suffix string
  32. Multiplier float64
  33. }
  34. var metricSuffixes = []suffix{
  35. {"G", 1e9},
  36. {"M", 1e6},
  37. {"k", 1e3},
  38. }
  39. var binarySuffixes = []suffix{
  40. {"Gi", 1 << 30},
  41. {"Mi", 1 << 20},
  42. {"Ki", 1 << 10},
  43. }
  44. var durationSuffix = []suffix{
  45. {"year", 365 * 24 * 60 * 60},
  46. {"month", 30 * 24 * 60 * 60},
  47. {"day", 24 * 60 * 60},
  48. {"hour", 60 * 60},
  49. {"minute", 60},
  50. {"second", 1},
  51. }
  52. func metric(v float64) string {
  53. return withSuffix(v, metricSuffixes, false)
  54. }
  55. func binary(v float64) string {
  56. return withSuffix(v, binarySuffixes, false)
  57. }
  58. func duration(v float64) string {
  59. return withSuffix(v, durationSuffix, true)
  60. }
  61. func withSuffix(v float64, ps []suffix, pluralize bool) string {
  62. for _, p := range ps {
  63. if v >= p.Multiplier {
  64. suffix := p.Suffix
  65. if pluralize && v/p.Multiplier != 1.0 {
  66. suffix += "s"
  67. }
  68. // If the number only has decimal zeroes, strip em off.
  69. num := strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v/p.Multiplier), "0"), ".")
  70. return fmt.Sprintf("%s %s", num, suffix)
  71. }
  72. }
  73. return strings.TrimRight(strings.TrimRight(fmt.Sprintf("%.1f", v), "0"), ".")
  74. }
  75. // commatize returns a number with sep as thousands separators. Handles
  76. // integers and plain floats.
  77. func commatize(sep, s string) string {
  78. // If no dot, don't do anything.
  79. if !strings.ContainsRune(s, '.') {
  80. return s
  81. }
  82. var b bytes.Buffer
  83. fs := strings.SplitN(s, ".", 2)
  84. l := len(fs[0])
  85. for i := range fs[0] {
  86. b.Write([]byte{s[i]})
  87. if i < l-1 && (l-i)%3 == 1 {
  88. b.WriteString(sep)
  89. }
  90. }
  91. if len(fs) > 1 && len(fs[1]) > 0 {
  92. b.WriteString(".")
  93. b.WriteString(fs[1])
  94. }
  95. return b.String()
  96. }
  97. func proportion(m map[string]int, count int) float64 {
  98. total := 0
  99. isMax := true
  100. for _, n := range m {
  101. total += n
  102. if n > count {
  103. isMax = false
  104. }
  105. }
  106. pct := (100 * float64(count)) / float64(total)
  107. // To avoid rounding errors in the template, surpassing 100% and breaking
  108. // the progress bars.
  109. if isMax && len(m) > 1 && count != total {
  110. pct -= 0.01
  111. }
  112. return pct
  113. }