Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

humanize.gno

3.45 Kb · 145 lines
  1// Package humanize formats numbers for people rather than machines, as a pure,
  2// reusable package: byte sizes, thousands separators, ordinals, pluralisation
  3// and block-height "durations".
  4//
  5// Everything is integer-only. There are no floats here on purpose: gno has no
  6// float determinism guarantees worth relying on for consensus output, and a
  7// rendered value that differs between nodes would be a consensus bug. One
  8// decimal place is produced by scaling by 10 and taking a remainder.
  9//
 10// Durations are expressed in BLOCKS, not seconds. There is no wall clock on
 11// chain, so "about 2 hours" is a lie dressed as precision; this package says
 12// "~1200 blocks" and lets the caller decide what that means on their chain.
 13//
 14// A live demo of this package is at
 15// [r/moul/x/daily/humanizedemo](/r/nym-moultest123/x/daily/humanizedemo/v0).
 16package humanize
 17
 18import (
 19	"strconv"
 20	"strings"
 21)
 22
 23// Bytes renders n bytes with SI-ish binary units and one decimal place.
 24// Negative input is rendered with a leading minus rather than rejected.
 25func Bytes(n int64) string {
 26	neg := n < 0
 27	if neg {
 28		n = -n
 29	}
 30	const unit = 1024
 31	if n < unit {
 32		s := strconv.FormatInt(n, 10) + " B"
 33		if neg {
 34			return "-" + s
 35		}
 36		return s
 37	}
 38	units := []string{"KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
 39	div := int64(unit)
 40	i := 0
 41	for n/div >= unit && i < len(units)-1 {
 42		div *= unit
 43		i++
 44	}
 45	// one decimal place without floats: scale by 10, then split
 46	scaled := n * 10 / div
 47	whole, freq := scaled/10, scaled%10
 48	s := strconv.FormatInt(whole, 10)
 49	if freq != 0 {
 50		s += "." + strconv.FormatInt(freq, 10)
 51	}
 52	s += " " + units[i]
 53	if neg {
 54		return "-" + s
 55	}
 56	return s
 57}
 58
 59// Comma inserts thousands separators: 1234567 -> "1,234,567".
 60func Comma(n int64) string {
 61	neg := n < 0
 62	if neg {
 63		n = -n
 64	}
 65	s := strconv.FormatInt(n, 10)
 66	var b strings.Builder
 67	for i, c := range []byte(s) {
 68		if i > 0 && (len(s)-i)%3 == 0 {
 69			b.WriteByte(',')
 70		}
 71		b.WriteByte(c)
 72	}
 73	if neg {
 74		return "-" + b.String()
 75	}
 76	return b.String()
 77}
 78
 79// Ordinal renders 1 -> "1st", 2 -> "2nd", 11 -> "11th".
 80//
 81// The teens are the trap: 11/12/13 take "th" despite ending in 1/2/3, so the
 82// 11–13 case must be checked before the last digit.
 83func Ordinal(n int64) string {
 84	s := strconv.FormatInt(n, 10)
 85	a := n
 86	if a < 0 {
 87		a = -a
 88	}
 89	if a%100 >= 11 && a%100 <= 13 {
 90		return s + "th"
 91	}
 92	switch a % 10 {
 93	case 1:
 94		return s + "st"
 95	case 2:
 96		return s + "nd"
 97	case 3:
 98		return s + "rd"
 99	}
100	return s + "th"
101}
102
103// Plural returns "1 block" / "2 blocks", using plural when given, else word+"s".
104func Plural(n int64, word, plural string) string {
105	if n == 1 || n == -1 {
106		return strconv.FormatInt(n, 10) + " " + word
107	}
108	if plural == "" {
109		plural = word + "s"
110	}
111	return strconv.FormatInt(n, 10) + " " + plural
112}
113
114// Blocks renders a block count at a coarse magnitude — deliberately vague,
115// because a block count is not a wall-clock duration.
116func Blocks(n int64) string {
117	switch {
118	case n < 0:
119		return "in the past"
120	case n == 0:
121		return "now"
122	case n < 10:
123		return Plural(n, "block", "")
124	case n < 1000:
125		return "~" + Comma(n/10*10) + " blocks"
126	default:
127		return "~" + Comma(n/100*100) + " blocks"
128	}
129}
130
131// Truncate shortens s to at most max runes, appending "…" when it cut.
132// Counts RUNES, not bytes, so a multi-byte string is not sliced mid-character.
133func Truncate(s string, max int) string {
134	if max <= 0 {
135		return ""
136	}
137	r := []rune(s)
138	if len(r) <= max {
139		return s
140	}
141	if max == 1 {
142		return "…"
143	}
144	return string(r[:max-1]) + "…"
145}