mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Show download count info in release list (#10124)
* Show download count info in release list * Use go-humanize
This commit is contained in:
		
							
								
								
									
										1
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								go.mod
									
									
									
									
									
								
							| @@ -32,6 +32,7 @@ require ( | |||||||
| 	github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | 	github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect | ||||||
| 	github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 | 	github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
|  | 	github.com/dustin/go-humanize v1.0.0 | ||||||
| 	github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | 	github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | ||||||
| 	github.com/emirpasic/gods v1.12.0 | 	github.com/emirpasic/gods v1.12.0 | ||||||
| 	github.com/etcd-io/bbolt v1.3.3 // indirect | 	github.com/etcd-io/bbolt v1.3.3 // indirect | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.sum
									
									
									
									
									
								
							| @@ -133,6 +133,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm | |||||||
| github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= | ||||||
| github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||||
| github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= | ||||||
|  | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= | ||||||
|  | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= | ||||||
| github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= | ||||||
| github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= | ||||||
| github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ import ( | |||||||
| 	"encoding/hex" | 	"encoding/hex" | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"io" | 	"io" | ||||||
| 	"math" |  | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"net/url" | 	"net/url" | ||||||
| 	"os" | 	"os" | ||||||
| @@ -29,6 +28,7 @@ import ( | |||||||
| 	"code.gitea.io/gitea/modules/log" | 	"code.gitea.io/gitea/modules/log" | ||||||
| 	"code.gitea.io/gitea/modules/setting" | 	"code.gitea.io/gitea/modules/setting" | ||||||
|  |  | ||||||
|  | 	"github.com/dustin/go-humanize" | ||||||
| 	"github.com/unknwon/com" | 	"github.com/unknwon/com" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -214,40 +214,15 @@ func AvatarLink(email string) string { | |||||||
| 	return SizedAvatarLink(email, DefaultAvatarSize) | 	return SizedAvatarLink(email, DefaultAvatarSize) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Storage space size types |  | ||||||
| const ( |  | ||||||
| 	Byte  = 1 |  | ||||||
| 	KByte = Byte * 1024 |  | ||||||
| 	MByte = KByte * 1024 |  | ||||||
| 	GByte = MByte * 1024 |  | ||||||
| 	TByte = GByte * 1024 |  | ||||||
| 	PByte = TByte * 1024 |  | ||||||
| 	EByte = PByte * 1024 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func logn(n, b float64) float64 { |  | ||||||
| 	return math.Log(n) / math.Log(b) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func humanateBytes(s uint64, base float64, sizes []string) string { |  | ||||||
| 	if s < 10 { |  | ||||||
| 		return fmt.Sprintf("%dB", s) |  | ||||||
| 	} |  | ||||||
| 	e := math.Floor(logn(float64(s), base)) |  | ||||||
| 	suffix := sizes[int(e)] |  | ||||||
| 	val := float64(s) / math.Pow(base, math.Floor(e)) |  | ||||||
| 	f := "%.0f" |  | ||||||
| 	if val < 10 { |  | ||||||
| 		f = "%.1f" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return fmt.Sprintf(f+"%s", val, suffix) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // FileSize calculates the file size and generate user-friendly string. | // FileSize calculates the file size and generate user-friendly string. | ||||||
| func FileSize(s int64) string { | func FileSize(s int64) string { | ||||||
| 	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} | 	return humanize.IBytes(uint64(s)) | ||||||
| 	return humanateBytes(uint64(s), 1024, sizes) | } | ||||||
|  |  | ||||||
|  | // PrettyNumber produces a string form of the given number in base 10 with | ||||||
|  | // commas after every three orders of magnitud | ||||||
|  | func PrettyNumber(v int64) string { | ||||||
|  | 	return humanize.Comma(v) | ||||||
| } | } | ||||||
|  |  | ||||||
| // Subtract deals with subtraction of all types of number. | // Subtract deals with subtraction of all types of number. | ||||||
|   | |||||||
| @@ -103,19 +103,19 @@ func TestAvatarLink(t *testing.T) { | |||||||
|  |  | ||||||
| func TestFileSize(t *testing.T) { | func TestFileSize(t *testing.T) { | ||||||
| 	var size int64 = 512 | 	var size int64 = 512 | ||||||
| 	assert.Equal(t, "512B", FileSize(size)) | 	assert.Equal(t, "512 B", FileSize(size)) | ||||||
| 	size *= 1024 | 	size *= 1024 | ||||||
| 	assert.Equal(t, "512KB", FileSize(size)) | 	assert.Equal(t, "512 KiB", FileSize(size)) | ||||||
| 	size *= 1024 | 	size *= 1024 | ||||||
| 	assert.Equal(t, "512MB", FileSize(size)) | 	assert.Equal(t, "512 MiB", FileSize(size)) | ||||||
| 	size *= 1024 | 	size *= 1024 | ||||||
| 	assert.Equal(t, "512GB", FileSize(size)) | 	assert.Equal(t, "512 GiB", FileSize(size)) | ||||||
| 	size *= 1024 | 	size *= 1024 | ||||||
| 	assert.Equal(t, "512TB", FileSize(size)) | 	assert.Equal(t, "512 TiB", FileSize(size)) | ||||||
| 	size *= 1024 | 	size *= 1024 | ||||||
| 	assert.Equal(t, "512PB", FileSize(size)) | 	assert.Equal(t, "512 PiB", FileSize(size)) | ||||||
| 	size *= 4 | 	size *= 4 | ||||||
| 	assert.Equal(t, "2.0EB", FileSize(size)) | 	assert.Equal(t, "2.0 EiB", FileSize(size)) | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestSubtract(t *testing.T) { | func TestSubtract(t *testing.T) { | ||||||
|   | |||||||
| @@ -93,6 +93,7 @@ func NewFuncMap() []template.FuncMap { | |||||||
| 		"TimeSinceUnix": timeutil.TimeSinceUnix, | 		"TimeSinceUnix": timeutil.TimeSinceUnix, | ||||||
| 		"RawTimeSince":  timeutil.RawTimeSince, | 		"RawTimeSince":  timeutil.RawTimeSince, | ||||||
| 		"FileSize":      base.FileSize, | 		"FileSize":      base.FileSize, | ||||||
|  | 		"PrettyNumber":  base.PrettyNumber, | ||||||
| 		"Subtract":      base.Subtract, | 		"Subtract":      base.Subtract, | ||||||
| 		"EntryIcon":     base.EntryIcon, | 		"EntryIcon":     base.EntryIcon, | ||||||
| 		"MigrationIcon": MigrationIcon, | 		"MigrationIcon": MigrationIcon, | ||||||
|   | |||||||
| @@ -1565,6 +1565,7 @@ release.deletion_success = The release has been deleted. | |||||||
| release.tag_name_already_exist = A release with this tag name already exists. | release.tag_name_already_exist = A release with this tag name already exists. | ||||||
| release.tag_name_invalid = The tag name is not valid. | release.tag_name_invalid = The tag name is not valid. | ||||||
| release.downloads = Downloads | release.downloads = Downloads | ||||||
|  | release.download_count = Downloads: %s | ||||||
|  |  | ||||||
| branch.name = Branch Name | branch.name = Branch Name | ||||||
| branch.search = Search branches | branch.search = Search branches | ||||||
|   | |||||||
| @@ -84,6 +84,7 @@ | |||||||
| 									{{if .Attachments}} | 									{{if .Attachments}} | ||||||
| 										{{range .Attachments}} | 										{{range .Attachments}} | ||||||
| 										<li> | 										<li> | ||||||
|  | 											<span class="ui text right" data-tooltip="{{$.i18n.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}" data-position="bottom right"><i class="ui octicon octicon-info"></i></span> | ||||||
| 											<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}"> | 											<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}"> | ||||||
| 												<strong><span class="ui image octicon octicon-package" title='{{.Name}}'></span> {{.Name}}</strong> | 												<strong><span class="ui image octicon octicon-package" title='{{.Name}}'></span> {{.Name}}</strong> | ||||||
| 												<span class="ui text grey right">{{.Size | FileSize}}</span> | 												<span class="ui text grey right">{{.Size | FileSize}}</span> | ||||||
|   | |||||||
							
								
								
									
										21
									
								
								vendor/github.com/dustin/go-humanize/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/dustin/go-humanize/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | sudo: false | ||||||
|  | language: go | ||||||
|  | go: | ||||||
|  |   - 1.3.x | ||||||
|  |   - 1.5.x | ||||||
|  |   - 1.6.x | ||||||
|  |   - 1.7.x | ||||||
|  |   - 1.8.x | ||||||
|  |   - 1.9.x | ||||||
|  |   - master | ||||||
|  | matrix: | ||||||
|  |   allow_failures: | ||||||
|  |     - go: master | ||||||
|  |   fast_finish: true | ||||||
|  | install: | ||||||
|  |   - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). | ||||||
|  | script: | ||||||
|  |   - go get -t -v ./... | ||||||
|  |   - diff -u <(echo -n) <(gofmt -d -s .) | ||||||
|  |   - go tool vet . | ||||||
|  |   - go test -v -race ./... | ||||||
							
								
								
									
										21
									
								
								vendor/github.com/dustin/go-humanize/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/dustin/go-humanize/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | |||||||
|  | Copyright (c) 2005-2008  Dustin Sallings <dustin@spy.net> | ||||||
|  |  | ||||||
|  | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
|  | of this software and associated documentation files (the "Software"), to deal | ||||||
|  | in the Software without restriction, including without limitation the rights | ||||||
|  | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||||
|  | copies of the Software, and to permit persons to whom the Software is | ||||||
|  | furnished to do so, subject to the following conditions: | ||||||
|  |  | ||||||
|  | The above copyright notice and this permission notice shall be included in | ||||||
|  | all copies or substantial portions of the Software. | ||||||
|  |  | ||||||
|  | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||||
|  | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||||
|  | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||||
|  | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||||
|  | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||||
|  | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||||
|  | SOFTWARE. | ||||||
|  |  | ||||||
|  | <http://www.opensource.org/licenses/mit-license.php> | ||||||
							
								
								
									
										124
									
								
								vendor/github.com/dustin/go-humanize/README.markdown
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								vendor/github.com/dustin/go-humanize/README.markdown
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | |||||||
|  | # Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize) | ||||||
|  |  | ||||||
|  | Just a few functions for helping humanize times and sizes. | ||||||
|  |  | ||||||
|  | `go get` it as `github.com/dustin/go-humanize`, import it as | ||||||
|  | `"github.com/dustin/go-humanize"`, use it as `humanize`. | ||||||
|  |  | ||||||
|  | See [godoc](https://godoc.org/github.com/dustin/go-humanize) for | ||||||
|  | complete documentation. | ||||||
|  |  | ||||||
|  | ## Sizes | ||||||
|  |  | ||||||
|  | This lets you take numbers like `82854982` and convert them to useful | ||||||
|  | strings like, `83 MB` or `79 MiB` (whichever you prefer). | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Times | ||||||
|  |  | ||||||
|  | This lets you take a `time.Time` and spit it out in relative terms. | ||||||
|  | For example, `12 seconds ago` or `3 days from now`. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Thanks to Kyle Lemons for the time implementation from an IRC | ||||||
|  | conversation one day. It's pretty neat. | ||||||
|  |  | ||||||
|  | ## Ordinals | ||||||
|  |  | ||||||
|  | From a [mailing list discussion][odisc] where a user wanted to be able | ||||||
|  | to label ordinals. | ||||||
|  |  | ||||||
|  |     0 -> 0th | ||||||
|  |     1 -> 1st | ||||||
|  |     2 -> 2nd | ||||||
|  |     3 -> 3rd | ||||||
|  |     4 -> 4th | ||||||
|  |     [...] | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Commas | ||||||
|  |  | ||||||
|  | Want to shove commas into numbers? Be my guest. | ||||||
|  |  | ||||||
|  |     0 -> 0 | ||||||
|  |     100 -> 100 | ||||||
|  |     1000 -> 1,000 | ||||||
|  |     1000000000 -> 1,000,000,000 | ||||||
|  |     -100000 -> -100,000 | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Ftoa | ||||||
|  |  | ||||||
|  | Nicer float64 formatter that removes trailing zeros. | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | fmt.Printf("%f", 2.24)                // 2.240000 | ||||||
|  | fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 | ||||||
|  | fmt.Printf("%f", 2.0)                 // 2.000000 | ||||||
|  | fmt.Printf("%s", humanize.Ftoa(2.0))  // 2 | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## SI notation | ||||||
|  |  | ||||||
|  | Format numbers with [SI notation][sinotation]. | ||||||
|  |  | ||||||
|  | Example: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | humanize.SI(0.00000000223, "M") // 2.23 nM | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## English-specific functions | ||||||
|  |  | ||||||
|  | The following functions are in the `humanize/english` subpackage. | ||||||
|  |  | ||||||
|  | ### Plurals | ||||||
|  |  | ||||||
|  | Simple English pluralization | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | english.PluralWord(1, "object", "") // object | ||||||
|  | english.PluralWord(42, "object", "") // objects | ||||||
|  | english.PluralWord(2, "bus", "") // buses | ||||||
|  | english.PluralWord(99, "locus", "loci") // loci | ||||||
|  |  | ||||||
|  | english.Plural(1, "object", "") // 1 object | ||||||
|  | english.Plural(42, "object", "") // 42 objects | ||||||
|  | english.Plural(2, "bus", "") // 2 buses | ||||||
|  | english.Plural(99, "locus", "loci") // 99 loci | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### Word series | ||||||
|  |  | ||||||
|  | Format comma-separated words lists with conjuctions: | ||||||
|  |  | ||||||
|  | ```go | ||||||
|  | english.WordSeries([]string{"foo"}, "and") // foo | ||||||
|  | english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar | ||||||
|  | english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz | ||||||
|  |  | ||||||
|  | english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | [odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion | ||||||
|  | [sinotation]: http://en.wikipedia.org/wiki/Metric_prefix | ||||||
							
								
								
									
										31
									
								
								vendor/github.com/dustin/go-humanize/big.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								vendor/github.com/dustin/go-humanize/big.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"math/big" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // order of magnitude (to a max order) | ||||||
|  | func oomm(n, b *big.Int, maxmag int) (float64, int) { | ||||||
|  | 	mag := 0 | ||||||
|  | 	m := &big.Int{} | ||||||
|  | 	for n.Cmp(b) >= 0 { | ||||||
|  | 		n.DivMod(n, b, m) | ||||||
|  | 		mag++ | ||||||
|  | 		if mag == maxmag && maxmag >= 0 { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // total order of magnitude | ||||||
|  | // (same as above, but with no upper limit) | ||||||
|  | func oom(n, b *big.Int) (float64, int) { | ||||||
|  | 	mag := 0 | ||||||
|  | 	m := &big.Int{} | ||||||
|  | 	for n.Cmp(b) >= 0 { | ||||||
|  | 		n.DivMod(n, b, m) | ||||||
|  | 		mag++ | ||||||
|  | 	} | ||||||
|  | 	return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag | ||||||
|  | } | ||||||
							
								
								
									
										173
									
								
								vendor/github.com/dustin/go-humanize/bigbytes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								vendor/github.com/dustin/go-humanize/bigbytes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"math/big" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	bigIECExp = big.NewInt(1024) | ||||||
|  |  | ||||||
|  | 	// BigByte is one byte in bit.Ints | ||||||
|  | 	BigByte = big.NewInt(1) | ||||||
|  | 	// BigKiByte is 1,024 bytes in bit.Ints | ||||||
|  | 	BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) | ||||||
|  | 	// BigMiByte is 1,024 k bytes in bit.Ints | ||||||
|  | 	BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) | ||||||
|  | 	// BigGiByte is 1,024 m bytes in bit.Ints | ||||||
|  | 	BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) | ||||||
|  | 	// BigTiByte is 1,024 g bytes in bit.Ints | ||||||
|  | 	BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) | ||||||
|  | 	// BigPiByte is 1,024 t bytes in bit.Ints | ||||||
|  | 	BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) | ||||||
|  | 	// BigEiByte is 1,024 p bytes in bit.Ints | ||||||
|  | 	BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) | ||||||
|  | 	// BigZiByte is 1,024 e bytes in bit.Ints | ||||||
|  | 	BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) | ||||||
|  | 	// BigYiByte is 1,024 z bytes in bit.Ints | ||||||
|  | 	BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	bigSIExp = big.NewInt(1000) | ||||||
|  |  | ||||||
|  | 	// BigSIByte is one SI byte in big.Ints | ||||||
|  | 	BigSIByte = big.NewInt(1) | ||||||
|  | 	// BigKByte is 1,000 SI bytes in big.Ints | ||||||
|  | 	BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) | ||||||
|  | 	// BigMByte is 1,000 SI k bytes in big.Ints | ||||||
|  | 	BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) | ||||||
|  | 	// BigGByte is 1,000 SI m bytes in big.Ints | ||||||
|  | 	BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) | ||||||
|  | 	// BigTByte is 1,000 SI g bytes in big.Ints | ||||||
|  | 	BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) | ||||||
|  | 	// BigPByte is 1,000 SI t bytes in big.Ints | ||||||
|  | 	BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) | ||||||
|  | 	// BigEByte is 1,000 SI p bytes in big.Ints | ||||||
|  | 	BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) | ||||||
|  | 	// BigZByte is 1,000 SI e bytes in big.Ints | ||||||
|  | 	BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) | ||||||
|  | 	// BigYByte is 1,000 SI z bytes in big.Ints | ||||||
|  | 	BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var bigBytesSizeTable = map[string]*big.Int{ | ||||||
|  | 	"b":   BigByte, | ||||||
|  | 	"kib": BigKiByte, | ||||||
|  | 	"kb":  BigKByte, | ||||||
|  | 	"mib": BigMiByte, | ||||||
|  | 	"mb":  BigMByte, | ||||||
|  | 	"gib": BigGiByte, | ||||||
|  | 	"gb":  BigGByte, | ||||||
|  | 	"tib": BigTiByte, | ||||||
|  | 	"tb":  BigTByte, | ||||||
|  | 	"pib": BigPiByte, | ||||||
|  | 	"pb":  BigPByte, | ||||||
|  | 	"eib": BigEiByte, | ||||||
|  | 	"eb":  BigEByte, | ||||||
|  | 	"zib": BigZiByte, | ||||||
|  | 	"zb":  BigZByte, | ||||||
|  | 	"yib": BigYiByte, | ||||||
|  | 	"yb":  BigYByte, | ||||||
|  | 	// Without suffix | ||||||
|  | 	"":   BigByte, | ||||||
|  | 	"ki": BigKiByte, | ||||||
|  | 	"k":  BigKByte, | ||||||
|  | 	"mi": BigMiByte, | ||||||
|  | 	"m":  BigMByte, | ||||||
|  | 	"gi": BigGiByte, | ||||||
|  | 	"g":  BigGByte, | ||||||
|  | 	"ti": BigTiByte, | ||||||
|  | 	"t":  BigTByte, | ||||||
|  | 	"pi": BigPiByte, | ||||||
|  | 	"p":  BigPByte, | ||||||
|  | 	"ei": BigEiByte, | ||||||
|  | 	"e":  BigEByte, | ||||||
|  | 	"z":  BigZByte, | ||||||
|  | 	"zi": BigZiByte, | ||||||
|  | 	"y":  BigYByte, | ||||||
|  | 	"yi": BigYiByte, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var ten = big.NewInt(10) | ||||||
|  |  | ||||||
|  | func humanateBigBytes(s, base *big.Int, sizes []string) string { | ||||||
|  | 	if s.Cmp(ten) < 0 { | ||||||
|  | 		return fmt.Sprintf("%d B", s) | ||||||
|  | 	} | ||||||
|  | 	c := (&big.Int{}).Set(s) | ||||||
|  | 	val, mag := oomm(c, base, len(sizes)-1) | ||||||
|  | 	suffix := sizes[mag] | ||||||
|  | 	f := "%.0f %s" | ||||||
|  | 	if val < 10 { | ||||||
|  | 		f = "%.1f %s" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return fmt.Sprintf(f, val, suffix) | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // BigBytes produces a human readable representation of an SI size. | ||||||
|  | // | ||||||
|  | // See also: ParseBigBytes. | ||||||
|  | // | ||||||
|  | // BigBytes(82854982) -> 83 MB | ||||||
|  | func BigBytes(s *big.Int) string { | ||||||
|  | 	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} | ||||||
|  | 	return humanateBigBytes(s, bigSIExp, sizes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // BigIBytes produces a human readable representation of an IEC size. | ||||||
|  | // | ||||||
|  | // See also: ParseBigBytes. | ||||||
|  | // | ||||||
|  | // BigIBytes(82854982) -> 79 MiB | ||||||
|  | func BigIBytes(s *big.Int) string { | ||||||
|  | 	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} | ||||||
|  | 	return humanateBigBytes(s, bigIECExp, sizes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ParseBigBytes parses a string representation of bytes into the number | ||||||
|  | // of bytes it represents. | ||||||
|  | // | ||||||
|  | // See also: BigBytes, BigIBytes. | ||||||
|  | // | ||||||
|  | // ParseBigBytes("42 MB") -> 42000000, nil | ||||||
|  | // ParseBigBytes("42 mib") -> 44040192, nil | ||||||
|  | func ParseBigBytes(s string) (*big.Int, error) { | ||||||
|  | 	lastDigit := 0 | ||||||
|  | 	hasComma := false | ||||||
|  | 	for _, r := range s { | ||||||
|  | 		if !(unicode.IsDigit(r) || r == '.' || r == ',') { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if r == ',' { | ||||||
|  | 			hasComma = true | ||||||
|  | 		} | ||||||
|  | 		lastDigit++ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	num := s[:lastDigit] | ||||||
|  | 	if hasComma { | ||||||
|  | 		num = strings.Replace(num, ",", "", -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	val := &big.Rat{} | ||||||
|  | 	_, err := fmt.Sscanf(num, "%f", val) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) | ||||||
|  | 	if m, ok := bigBytesSizeTable[extra]; ok { | ||||||
|  | 		mv := (&big.Rat{}).SetInt(m) | ||||||
|  | 		val.Mul(val, mv) | ||||||
|  | 		rv := &big.Int{} | ||||||
|  | 		rv.Div(val.Num(), val.Denom()) | ||||||
|  | 		return rv, nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return nil, fmt.Errorf("unhandled size name: %v", extra) | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								vendor/github.com/dustin/go-humanize/bytes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								vendor/github.com/dustin/go-humanize/bytes.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | 	"unicode" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // IEC Sizes. | ||||||
|  | // kibis of bits | ||||||
|  | const ( | ||||||
|  | 	Byte = 1 << (iota * 10) | ||||||
|  | 	KiByte | ||||||
|  | 	MiByte | ||||||
|  | 	GiByte | ||||||
|  | 	TiByte | ||||||
|  | 	PiByte | ||||||
|  | 	EiByte | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // SI Sizes. | ||||||
|  | const ( | ||||||
|  | 	IByte = 1 | ||||||
|  | 	KByte = IByte * 1000 | ||||||
|  | 	MByte = KByte * 1000 | ||||||
|  | 	GByte = MByte * 1000 | ||||||
|  | 	TByte = GByte * 1000 | ||||||
|  | 	PByte = TByte * 1000 | ||||||
|  | 	EByte = PByte * 1000 | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var bytesSizeTable = map[string]uint64{ | ||||||
|  | 	"b":   Byte, | ||||||
|  | 	"kib": KiByte, | ||||||
|  | 	"kb":  KByte, | ||||||
|  | 	"mib": MiByte, | ||||||
|  | 	"mb":  MByte, | ||||||
|  | 	"gib": GiByte, | ||||||
|  | 	"gb":  GByte, | ||||||
|  | 	"tib": TiByte, | ||||||
|  | 	"tb":  TByte, | ||||||
|  | 	"pib": PiByte, | ||||||
|  | 	"pb":  PByte, | ||||||
|  | 	"eib": EiByte, | ||||||
|  | 	"eb":  EByte, | ||||||
|  | 	// Without suffix | ||||||
|  | 	"":   Byte, | ||||||
|  | 	"ki": KiByte, | ||||||
|  | 	"k":  KByte, | ||||||
|  | 	"mi": MiByte, | ||||||
|  | 	"m":  MByte, | ||||||
|  | 	"gi": GiByte, | ||||||
|  | 	"g":  GByte, | ||||||
|  | 	"ti": TiByte, | ||||||
|  | 	"t":  TByte, | ||||||
|  | 	"pi": PiByte, | ||||||
|  | 	"p":  PByte, | ||||||
|  | 	"ei": EiByte, | ||||||
|  | 	"e":  EByte, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func logn(n, b float64) float64 { | ||||||
|  | 	return math.Log(n) / math.Log(b) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func humanateBytes(s uint64, base float64, sizes []string) string { | ||||||
|  | 	if s < 10 { | ||||||
|  | 		return fmt.Sprintf("%d B", s) | ||||||
|  | 	} | ||||||
|  | 	e := math.Floor(logn(float64(s), base)) | ||||||
|  | 	suffix := sizes[int(e)] | ||||||
|  | 	val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 | ||||||
|  | 	f := "%.0f %s" | ||||||
|  | 	if val < 10 { | ||||||
|  | 		f = "%.1f %s" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return fmt.Sprintf(f, val, suffix) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Bytes produces a human readable representation of an SI size. | ||||||
|  | // | ||||||
|  | // See also: ParseBytes. | ||||||
|  | // | ||||||
|  | // Bytes(82854982) -> 83 MB | ||||||
|  | func Bytes(s uint64) string { | ||||||
|  | 	sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} | ||||||
|  | 	return humanateBytes(s, 1000, sizes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // IBytes produces a human readable representation of an IEC size. | ||||||
|  | // | ||||||
|  | // See also: ParseBytes. | ||||||
|  | // | ||||||
|  | // IBytes(82854982) -> 79 MiB | ||||||
|  | func IBytes(s uint64) string { | ||||||
|  | 	sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} | ||||||
|  | 	return humanateBytes(s, 1024, sizes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ParseBytes parses a string representation of bytes into the number | ||||||
|  | // of bytes it represents. | ||||||
|  | // | ||||||
|  | // See Also: Bytes, IBytes. | ||||||
|  | // | ||||||
|  | // ParseBytes("42 MB") -> 42000000, nil | ||||||
|  | // ParseBytes("42 mib") -> 44040192, nil | ||||||
|  | func ParseBytes(s string) (uint64, error) { | ||||||
|  | 	lastDigit := 0 | ||||||
|  | 	hasComma := false | ||||||
|  | 	for _, r := range s { | ||||||
|  | 		if !(unicode.IsDigit(r) || r == '.' || r == ',') { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if r == ',' { | ||||||
|  | 			hasComma = true | ||||||
|  | 		} | ||||||
|  | 		lastDigit++ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	num := s[:lastDigit] | ||||||
|  | 	if hasComma { | ||||||
|  | 		num = strings.Replace(num, ",", "", -1) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	f, err := strconv.ParseFloat(num, 64) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return 0, err | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) | ||||||
|  | 	if m, ok := bytesSizeTable[extra]; ok { | ||||||
|  | 		f *= float64(m) | ||||||
|  | 		if f >= math.MaxUint64 { | ||||||
|  | 			return 0, fmt.Errorf("too large: %v", s) | ||||||
|  | 		} | ||||||
|  | 		return uint64(f), nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0, fmt.Errorf("unhandled size name: %v", extra) | ||||||
|  | } | ||||||
							
								
								
									
										116
									
								
								vendor/github.com/dustin/go-humanize/comma.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								vendor/github.com/dustin/go-humanize/comma.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"math" | ||||||
|  | 	"math/big" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Comma produces a string form of the given number in base 10 with | ||||||
|  | // commas after every three orders of magnitude. | ||||||
|  | // | ||||||
|  | // e.g. Comma(834142) -> 834,142 | ||||||
|  | func Comma(v int64) string { | ||||||
|  | 	sign := "" | ||||||
|  |  | ||||||
|  | 	// Min int64 can't be negated to a usable value, so it has to be special cased. | ||||||
|  | 	if v == math.MinInt64 { | ||||||
|  | 		return "-9,223,372,036,854,775,808" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if v < 0 { | ||||||
|  | 		sign = "-" | ||||||
|  | 		v = 0 - v | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	parts := []string{"", "", "", "", "", "", ""} | ||||||
|  | 	j := len(parts) - 1 | ||||||
|  |  | ||||||
|  | 	for v > 999 { | ||||||
|  | 		parts[j] = strconv.FormatInt(v%1000, 10) | ||||||
|  | 		switch len(parts[j]) { | ||||||
|  | 		case 2: | ||||||
|  | 			parts[j] = "0" + parts[j] | ||||||
|  | 		case 1: | ||||||
|  | 			parts[j] = "00" + parts[j] | ||||||
|  | 		} | ||||||
|  | 		v = v / 1000 | ||||||
|  | 		j-- | ||||||
|  | 	} | ||||||
|  | 	parts[j] = strconv.Itoa(int(v)) | ||||||
|  | 	return sign + strings.Join(parts[j:], ",") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Commaf produces a string form of the given number in base 10 with | ||||||
|  | // commas after every three orders of magnitude. | ||||||
|  | // | ||||||
|  | // e.g. Commaf(834142.32) -> 834,142.32 | ||||||
|  | func Commaf(v float64) string { | ||||||
|  | 	buf := &bytes.Buffer{} | ||||||
|  | 	if v < 0 { | ||||||
|  | 		buf.Write([]byte{'-'}) | ||||||
|  | 		v = 0 - v | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	comma := []byte{','} | ||||||
|  |  | ||||||
|  | 	parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") | ||||||
|  | 	pos := 0 | ||||||
|  | 	if len(parts[0])%3 != 0 { | ||||||
|  | 		pos += len(parts[0]) % 3 | ||||||
|  | 		buf.WriteString(parts[0][:pos]) | ||||||
|  | 		buf.Write(comma) | ||||||
|  | 	} | ||||||
|  | 	for ; pos < len(parts[0]); pos += 3 { | ||||||
|  | 		buf.WriteString(parts[0][pos : pos+3]) | ||||||
|  | 		buf.Write(comma) | ||||||
|  | 	} | ||||||
|  | 	buf.Truncate(buf.Len() - 1) | ||||||
|  |  | ||||||
|  | 	if len(parts) > 1 { | ||||||
|  | 		buf.Write([]byte{'.'}) | ||||||
|  | 		buf.WriteString(parts[1]) | ||||||
|  | 	} | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CommafWithDigits works like the Commaf but limits the resulting | ||||||
|  | // string to the given number of decimal places. | ||||||
|  | // | ||||||
|  | // e.g. CommafWithDigits(834142.32, 1) -> 834,142.3 | ||||||
|  | func CommafWithDigits(f float64, decimals int) string { | ||||||
|  | 	return stripTrailingDigits(Commaf(f), decimals) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // BigComma produces a string form of the given big.Int in base 10 | ||||||
|  | // with commas after every three orders of magnitude. | ||||||
|  | func BigComma(b *big.Int) string { | ||||||
|  | 	sign := "" | ||||||
|  | 	if b.Sign() < 0 { | ||||||
|  | 		sign = "-" | ||||||
|  | 		b.Abs(b) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	athousand := big.NewInt(1000) | ||||||
|  | 	c := (&big.Int{}).Set(b) | ||||||
|  | 	_, m := oom(c, athousand) | ||||||
|  | 	parts := make([]string, m+1) | ||||||
|  | 	j := len(parts) - 1 | ||||||
|  |  | ||||||
|  | 	mod := &big.Int{} | ||||||
|  | 	for b.Cmp(athousand) >= 0 { | ||||||
|  | 		b.DivMod(b, athousand, mod) | ||||||
|  | 		parts[j] = strconv.FormatInt(mod.Int64(), 10) | ||||||
|  | 		switch len(parts[j]) { | ||||||
|  | 		case 2: | ||||||
|  | 			parts[j] = "0" + parts[j] | ||||||
|  | 		case 1: | ||||||
|  | 			parts[j] = "00" + parts[j] | ||||||
|  | 		} | ||||||
|  | 		j-- | ||||||
|  | 	} | ||||||
|  | 	parts[j] = strconv.Itoa(int(b.Int64())) | ||||||
|  | 	return sign + strings.Join(parts[j:], ",") | ||||||
|  | } | ||||||
							
								
								
									
										40
									
								
								vendor/github.com/dustin/go-humanize/commaf.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								vendor/github.com/dustin/go-humanize/commaf.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | // +build go1.6 | ||||||
|  |  | ||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"math/big" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // BigCommaf produces a string form of the given big.Float in base 10 | ||||||
|  | // with commas after every three orders of magnitude. | ||||||
|  | func BigCommaf(v *big.Float) string { | ||||||
|  | 	buf := &bytes.Buffer{} | ||||||
|  | 	if v.Sign() < 0 { | ||||||
|  | 		buf.Write([]byte{'-'}) | ||||||
|  | 		v.Abs(v) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	comma := []byte{','} | ||||||
|  |  | ||||||
|  | 	parts := strings.Split(v.Text('f', -1), ".") | ||||||
|  | 	pos := 0 | ||||||
|  | 	if len(parts[0])%3 != 0 { | ||||||
|  | 		pos += len(parts[0]) % 3 | ||||||
|  | 		buf.WriteString(parts[0][:pos]) | ||||||
|  | 		buf.Write(comma) | ||||||
|  | 	} | ||||||
|  | 	for ; pos < len(parts[0]); pos += 3 { | ||||||
|  | 		buf.WriteString(parts[0][pos : pos+3]) | ||||||
|  | 		buf.Write(comma) | ||||||
|  | 	} | ||||||
|  | 	buf.Truncate(buf.Len() - 1) | ||||||
|  |  | ||||||
|  | 	if len(parts) > 1 { | ||||||
|  | 		buf.Write([]byte{'.'}) | ||||||
|  | 		buf.WriteString(parts[1]) | ||||||
|  | 	} | ||||||
|  | 	return buf.String() | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								vendor/github.com/dustin/go-humanize/ftoa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								vendor/github.com/dustin/go-humanize/ftoa.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func stripTrailingZeros(s string) string { | ||||||
|  | 	offset := len(s) - 1 | ||||||
|  | 	for offset > 0 { | ||||||
|  | 		if s[offset] == '.' { | ||||||
|  | 			offset-- | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		if s[offset] != '0' { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		offset-- | ||||||
|  | 	} | ||||||
|  | 	return s[:offset+1] | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func stripTrailingDigits(s string, digits int) string { | ||||||
|  | 	if i := strings.Index(s, "."); i >= 0 { | ||||||
|  | 		if digits <= 0 { | ||||||
|  | 			return s[:i] | ||||||
|  | 		} | ||||||
|  | 		i++ | ||||||
|  | 		if i+digits >= len(s) { | ||||||
|  | 			return s | ||||||
|  | 		} | ||||||
|  | 		return s[:i+digits] | ||||||
|  | 	} | ||||||
|  | 	return s | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Ftoa converts a float to a string with no trailing zeros. | ||||||
|  | func Ftoa(num float64) string { | ||||||
|  | 	return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FtoaWithDigits converts a float to a string but limits the resulting string | ||||||
|  | // to the given number of decimal places, and no trailing zeros. | ||||||
|  | func FtoaWithDigits(num float64, digits int) string { | ||||||
|  | 	return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits)) | ||||||
|  | } | ||||||
							
								
								
									
										8
									
								
								vendor/github.com/dustin/go-humanize/humanize.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								vendor/github.com/dustin/go-humanize/humanize.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | /* | ||||||
|  | Package humanize converts boring ugly numbers to human-friendly strings and back. | ||||||
|  |  | ||||||
|  | Durations can be turned into strings such as "3 days ago", numbers | ||||||
|  | representing sizes like 82854982 into useful strings like, "83 MB" or | ||||||
|  | "79 MiB" (whichever you prefer). | ||||||
|  | */ | ||||||
|  | package humanize | ||||||
							
								
								
									
										192
									
								
								vendor/github.com/dustin/go-humanize/number.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								vendor/github.com/dustin/go-humanize/number.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | Slightly adapted from the source to fit go-humanize. | ||||||
|  |  | ||||||
|  | Author: https://github.com/gorhill | ||||||
|  | Source: https://gist.github.com/gorhill/5285193 | ||||||
|  |  | ||||||
|  | */ | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"math" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	renderFloatPrecisionMultipliers = [...]float64{ | ||||||
|  | 		1, | ||||||
|  | 		10, | ||||||
|  | 		100, | ||||||
|  | 		1000, | ||||||
|  | 		10000, | ||||||
|  | 		100000, | ||||||
|  | 		1000000, | ||||||
|  | 		10000000, | ||||||
|  | 		100000000, | ||||||
|  | 		1000000000, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	renderFloatPrecisionRounders = [...]float64{ | ||||||
|  | 		0.5, | ||||||
|  | 		0.05, | ||||||
|  | 		0.005, | ||||||
|  | 		0.0005, | ||||||
|  | 		0.00005, | ||||||
|  | 		0.000005, | ||||||
|  | 		0.0000005, | ||||||
|  | 		0.00000005, | ||||||
|  | 		0.000000005, | ||||||
|  | 		0.0000000005, | ||||||
|  | 	} | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // FormatFloat produces a formatted number as string based on the following user-specified criteria: | ||||||
|  | // * thousands separator | ||||||
|  | // * decimal separator | ||||||
|  | // * decimal precision | ||||||
|  | // | ||||||
|  | // Usage: s := RenderFloat(format, n) | ||||||
|  | // The format parameter tells how to render the number n. | ||||||
|  | // | ||||||
|  | // See examples: http://play.golang.org/p/LXc1Ddm1lJ | ||||||
|  | // | ||||||
|  | // Examples of format strings, given n = 12345.6789: | ||||||
|  | // "#,###.##" => "12,345.67" | ||||||
|  | // "#,###." => "12,345" | ||||||
|  | // "#,###" => "12345,678" | ||||||
|  | // "#\u202F###,##" => "12 345,68" | ||||||
|  | // "#.###,###### => 12.345,678900 | ||||||
|  | // "" (aka default format) => 12,345.67 | ||||||
|  | // | ||||||
|  | // The highest precision allowed is 9 digits after the decimal symbol. | ||||||
|  | // There is also a version for integer number, FormatInteger(), | ||||||
|  | // which is convenient for calls within template. | ||||||
|  | func FormatFloat(format string, n float64) string { | ||||||
|  | 	// Special cases: | ||||||
|  | 	//   NaN = "NaN" | ||||||
|  | 	//   +Inf = "+Infinity" | ||||||
|  | 	//   -Inf = "-Infinity" | ||||||
|  | 	if math.IsNaN(n) { | ||||||
|  | 		return "NaN" | ||||||
|  | 	} | ||||||
|  | 	if n > math.MaxFloat64 { | ||||||
|  | 		return "Infinity" | ||||||
|  | 	} | ||||||
|  | 	if n < -math.MaxFloat64 { | ||||||
|  | 		return "-Infinity" | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// default format | ||||||
|  | 	precision := 2 | ||||||
|  | 	decimalStr := "." | ||||||
|  | 	thousandStr := "," | ||||||
|  | 	positiveStr := "" | ||||||
|  | 	negativeStr := "-" | ||||||
|  |  | ||||||
|  | 	if len(format) > 0 { | ||||||
|  | 		format := []rune(format) | ||||||
|  |  | ||||||
|  | 		// If there is an explicit format directive, | ||||||
|  | 		// then default values are these: | ||||||
|  | 		precision = 9 | ||||||
|  | 		thousandStr = "" | ||||||
|  |  | ||||||
|  | 		// collect indices of meaningful formatting directives | ||||||
|  | 		formatIndx := []int{} | ||||||
|  | 		for i, char := range format { | ||||||
|  | 			if char != '#' && char != '0' { | ||||||
|  | 				formatIndx = append(formatIndx, i) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(formatIndx) > 0 { | ||||||
|  | 			// Directive at index 0: | ||||||
|  | 			//   Must be a '+' | ||||||
|  | 			//   Raise an error if not the case | ||||||
|  | 			// index: 0123456789 | ||||||
|  | 			//        +0.000,000 | ||||||
|  | 			//        +000,000.0 | ||||||
|  | 			//        +0000.00 | ||||||
|  | 			//        +0000 | ||||||
|  | 			if formatIndx[0] == 0 { | ||||||
|  | 				if format[formatIndx[0]] != '+' { | ||||||
|  | 					panic("RenderFloat(): invalid positive sign directive") | ||||||
|  | 				} | ||||||
|  | 				positiveStr = "+" | ||||||
|  | 				formatIndx = formatIndx[1:] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Two directives: | ||||||
|  | 			//   First is thousands separator | ||||||
|  | 			//   Raise an error if not followed by 3-digit | ||||||
|  | 			// 0123456789 | ||||||
|  | 			// 0.000,000 | ||||||
|  | 			// 000,000.00 | ||||||
|  | 			if len(formatIndx) == 2 { | ||||||
|  | 				if (formatIndx[1] - formatIndx[0]) != 4 { | ||||||
|  | 					panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") | ||||||
|  | 				} | ||||||
|  | 				thousandStr = string(format[formatIndx[0]]) | ||||||
|  | 				formatIndx = formatIndx[1:] | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// One directive: | ||||||
|  | 			//   Directive is decimal separator | ||||||
|  | 			//   The number of digit-specifier following the separator indicates wanted precision | ||||||
|  | 			// 0123456789 | ||||||
|  | 			// 0.00 | ||||||
|  | 			// 000,0000 | ||||||
|  | 			if len(formatIndx) == 1 { | ||||||
|  | 				decimalStr = string(format[formatIndx[0]]) | ||||||
|  | 				precision = len(format) - formatIndx[0] - 1 | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// generate sign part | ||||||
|  | 	var signStr string | ||||||
|  | 	if n >= 0.000000001 { | ||||||
|  | 		signStr = positiveStr | ||||||
|  | 	} else if n <= -0.000000001 { | ||||||
|  | 		signStr = negativeStr | ||||||
|  | 		n = -n | ||||||
|  | 	} else { | ||||||
|  | 		signStr = "" | ||||||
|  | 		n = 0.0 | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// split number into integer and fractional parts | ||||||
|  | 	intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) | ||||||
|  |  | ||||||
|  | 	// generate integer part string | ||||||
|  | 	intStr := strconv.FormatInt(int64(intf), 10) | ||||||
|  |  | ||||||
|  | 	// add thousand separator if required | ||||||
|  | 	if len(thousandStr) > 0 { | ||||||
|  | 		for i := len(intStr); i > 3; { | ||||||
|  | 			i -= 3 | ||||||
|  | 			intStr = intStr[:i] + thousandStr + intStr[i:] | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// no fractional part, we can leave now | ||||||
|  | 	if precision == 0 { | ||||||
|  | 		return signStr + intStr | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// generate fractional part | ||||||
|  | 	fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) | ||||||
|  | 	// may need padding | ||||||
|  | 	if len(fracStr) < precision { | ||||||
|  | 		fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return signStr + intStr + decimalStr + fracStr | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // FormatInteger produces a formatted number as string. | ||||||
|  | // See FormatFloat. | ||||||
|  | func FormatInteger(format string, n int) string { | ||||||
|  | 	return FormatFloat(format, float64(n)) | ||||||
|  | } | ||||||
							
								
								
									
										25
									
								
								vendor/github.com/dustin/go-humanize/ordinals.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/dustin/go-humanize/ordinals.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import "strconv" | ||||||
|  |  | ||||||
|  | // Ordinal gives you the input number in a rank/ordinal format. | ||||||
|  | // | ||||||
|  | // Ordinal(3) -> 3rd | ||||||
|  | func Ordinal(x int) string { | ||||||
|  | 	suffix := "th" | ||||||
|  | 	switch x % 10 { | ||||||
|  | 	case 1: | ||||||
|  | 		if x%100 != 11 { | ||||||
|  | 			suffix = "st" | ||||||
|  | 		} | ||||||
|  | 	case 2: | ||||||
|  | 		if x%100 != 12 { | ||||||
|  | 			suffix = "nd" | ||||||
|  | 		} | ||||||
|  | 	case 3: | ||||||
|  | 		if x%100 != 13 { | ||||||
|  | 			suffix = "rd" | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return strconv.Itoa(x) + suffix | ||||||
|  | } | ||||||
							
								
								
									
										123
									
								
								vendor/github.com/dustin/go-humanize/si.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								vendor/github.com/dustin/go-humanize/si.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,123 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"math" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | var siPrefixTable = map[float64]string{ | ||||||
|  | 	-24: "y", // yocto | ||||||
|  | 	-21: "z", // zepto | ||||||
|  | 	-18: "a", // atto | ||||||
|  | 	-15: "f", // femto | ||||||
|  | 	-12: "p", // pico | ||||||
|  | 	-9:  "n", // nano | ||||||
|  | 	-6:  "µ", // micro | ||||||
|  | 	-3:  "m", // milli | ||||||
|  | 	0:   "", | ||||||
|  | 	3:   "k", // kilo | ||||||
|  | 	6:   "M", // mega | ||||||
|  | 	9:   "G", // giga | ||||||
|  | 	12:  "T", // tera | ||||||
|  | 	15:  "P", // peta | ||||||
|  | 	18:  "E", // exa | ||||||
|  | 	21:  "Z", // zetta | ||||||
|  | 	24:  "Y", // yotta | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var revSIPrefixTable = revfmap(siPrefixTable) | ||||||
|  |  | ||||||
|  | // revfmap reverses the map and precomputes the power multiplier | ||||||
|  | func revfmap(in map[float64]string) map[string]float64 { | ||||||
|  | 	rv := map[string]float64{} | ||||||
|  | 	for k, v := range in { | ||||||
|  | 		rv[v] = math.Pow(10, k) | ||||||
|  | 	} | ||||||
|  | 	return rv | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var riParseRegex *regexp.Regexp | ||||||
|  |  | ||||||
|  | func init() { | ||||||
|  | 	ri := `^([\-0-9.]+)\s?([` | ||||||
|  | 	for _, v := range siPrefixTable { | ||||||
|  | 		ri += v | ||||||
|  | 	} | ||||||
|  | 	ri += `]?)(.*)` | ||||||
|  |  | ||||||
|  | 	riParseRegex = regexp.MustCompile(ri) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // ComputeSI finds the most appropriate SI prefix for the given number | ||||||
|  | // and returns the prefix along with the value adjusted to be within | ||||||
|  | // that prefix. | ||||||
|  | // | ||||||
|  | // See also: SI, ParseSI. | ||||||
|  | // | ||||||
|  | // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") | ||||||
|  | func ComputeSI(input float64) (float64, string) { | ||||||
|  | 	if input == 0 { | ||||||
|  | 		return 0, "" | ||||||
|  | 	} | ||||||
|  | 	mag := math.Abs(input) | ||||||
|  | 	exponent := math.Floor(logn(mag, 10)) | ||||||
|  | 	exponent = math.Floor(exponent/3) * 3 | ||||||
|  |  | ||||||
|  | 	value := mag / math.Pow(10, exponent) | ||||||
|  |  | ||||||
|  | 	// Handle special case where value is exactly 1000.0 | ||||||
|  | 	// Should return 1 M instead of 1000 k | ||||||
|  | 	if value == 1000.0 { | ||||||
|  | 		exponent += 3 | ||||||
|  | 		value = mag / math.Pow(10, exponent) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	value = math.Copysign(value, input) | ||||||
|  |  | ||||||
|  | 	prefix := siPrefixTable[exponent] | ||||||
|  | 	return value, prefix | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SI returns a string with default formatting. | ||||||
|  | // | ||||||
|  | // SI uses Ftoa to format float value, removing trailing zeros. | ||||||
|  | // | ||||||
|  | // See also: ComputeSI, ParseSI. | ||||||
|  | // | ||||||
|  | // e.g. SI(1000000, "B") -> 1 MB | ||||||
|  | // e.g. SI(2.2345e-12, "F") -> 2.2345 pF | ||||||
|  | func SI(input float64, unit string) string { | ||||||
|  | 	value, prefix := ComputeSI(input) | ||||||
|  | 	return Ftoa(value) + " " + prefix + unit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // SIWithDigits works like SI but limits the resulting string to the | ||||||
|  | // given number of decimal places. | ||||||
|  | // | ||||||
|  | // e.g. SIWithDigits(1000000, 0, "B") -> 1 MB | ||||||
|  | // e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF | ||||||
|  | func SIWithDigits(input float64, decimals int, unit string) string { | ||||||
|  | 	value, prefix := ComputeSI(input) | ||||||
|  | 	return FtoaWithDigits(value, decimals) + " " + prefix + unit | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var errInvalid = errors.New("invalid input") | ||||||
|  |  | ||||||
|  | // ParseSI parses an SI string back into the number and unit. | ||||||
|  | // | ||||||
|  | // See also: SI, ComputeSI. | ||||||
|  | // | ||||||
|  | // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) | ||||||
|  | func ParseSI(input string) (float64, string, error) { | ||||||
|  | 	found := riParseRegex.FindStringSubmatch(input) | ||||||
|  | 	if len(found) != 4 { | ||||||
|  | 		return 0, "", errInvalid | ||||||
|  | 	} | ||||||
|  | 	mag := revSIPrefixTable[found[2]] | ||||||
|  | 	unit := found[3] | ||||||
|  |  | ||||||
|  | 	base, err := strconv.ParseFloat(found[1], 64) | ||||||
|  | 	return base * mag, unit, err | ||||||
|  | } | ||||||
							
								
								
									
										117
									
								
								vendor/github.com/dustin/go-humanize/times.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								vendor/github.com/dustin/go-humanize/times.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | package humanize | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"math" | ||||||
|  | 	"sort" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Seconds-based time units | ||||||
|  | const ( | ||||||
|  | 	Day      = 24 * time.Hour | ||||||
|  | 	Week     = 7 * Day | ||||||
|  | 	Month    = 30 * Day | ||||||
|  | 	Year     = 12 * Month | ||||||
|  | 	LongTime = 37 * Year | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | // Time formats a time into a relative string. | ||||||
|  | // | ||||||
|  | // Time(someT) -> "3 weeks ago" | ||||||
|  | func Time(then time.Time) string { | ||||||
|  | 	return RelTime(then, time.Now(), "ago", "from now") | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // A RelTimeMagnitude struct contains a relative time point at which | ||||||
|  | // the relative format of time will switch to a new format string.  A | ||||||
|  | // slice of these in ascending order by their "D" field is passed to | ||||||
|  | // CustomRelTime to format durations. | ||||||
|  | // | ||||||
|  | // The Format field is a string that may contain a "%s" which will be | ||||||
|  | // replaced with the appropriate signed label (e.g. "ago" or "from | ||||||
|  | // now") and a "%d" that will be replaced by the quantity. | ||||||
|  | // | ||||||
|  | // The DivBy field is the amount of time the time difference must be | ||||||
|  | // divided by in order to display correctly. | ||||||
|  | // | ||||||
|  | // e.g. if D is 2*time.Minute and you want to display "%d minutes %s" | ||||||
|  | // DivBy should be time.Minute so whatever the duration is will be | ||||||
|  | // expressed in minutes. | ||||||
|  | type RelTimeMagnitude struct { | ||||||
|  | 	D      time.Duration | ||||||
|  | 	Format string | ||||||
|  | 	DivBy  time.Duration | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var defaultMagnitudes = []RelTimeMagnitude{ | ||||||
|  | 	{time.Second, "now", time.Second}, | ||||||
|  | 	{2 * time.Second, "1 second %s", 1}, | ||||||
|  | 	{time.Minute, "%d seconds %s", time.Second}, | ||||||
|  | 	{2 * time.Minute, "1 minute %s", 1}, | ||||||
|  | 	{time.Hour, "%d minutes %s", time.Minute}, | ||||||
|  | 	{2 * time.Hour, "1 hour %s", 1}, | ||||||
|  | 	{Day, "%d hours %s", time.Hour}, | ||||||
|  | 	{2 * Day, "1 day %s", 1}, | ||||||
|  | 	{Week, "%d days %s", Day}, | ||||||
|  | 	{2 * Week, "1 week %s", 1}, | ||||||
|  | 	{Month, "%d weeks %s", Week}, | ||||||
|  | 	{2 * Month, "1 month %s", 1}, | ||||||
|  | 	{Year, "%d months %s", Month}, | ||||||
|  | 	{18 * Month, "1 year %s", 1}, | ||||||
|  | 	{2 * Year, "2 years %s", 1}, | ||||||
|  | 	{LongTime, "%d years %s", Year}, | ||||||
|  | 	{math.MaxInt64, "a long while %s", 1}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // RelTime formats a time into a relative string. | ||||||
|  | // | ||||||
|  | // It takes two times and two labels.  In addition to the generic time | ||||||
|  | // delta string (e.g. 5 minutes), the labels are used applied so that | ||||||
|  | // the label corresponding to the smaller time is applied. | ||||||
|  | // | ||||||
|  | // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" | ||||||
|  | func RelTime(a, b time.Time, albl, blbl string) string { | ||||||
|  | 	return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // CustomRelTime formats a time into a relative string. | ||||||
|  | // | ||||||
|  | // It takes two times two labels and a table of relative time formats. | ||||||
|  | // In addition to the generic time delta string (e.g. 5 minutes), the | ||||||
|  | // labels are used applied so that the label corresponding to the | ||||||
|  | // smaller time is applied. | ||||||
|  | func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { | ||||||
|  | 	lbl := albl | ||||||
|  | 	diff := b.Sub(a) | ||||||
|  |  | ||||||
|  | 	if a.After(b) { | ||||||
|  | 		lbl = blbl | ||||||
|  | 		diff = a.Sub(b) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	n := sort.Search(len(magnitudes), func(i int) bool { | ||||||
|  | 		return magnitudes[i].D > diff | ||||||
|  | 	}) | ||||||
|  |  | ||||||
|  | 	if n >= len(magnitudes) { | ||||||
|  | 		n = len(magnitudes) - 1 | ||||||
|  | 	} | ||||||
|  | 	mag := magnitudes[n] | ||||||
|  | 	args := []interface{}{} | ||||||
|  | 	escaped := false | ||||||
|  | 	for _, ch := range mag.Format { | ||||||
|  | 		if escaped { | ||||||
|  | 			switch ch { | ||||||
|  | 			case 's': | ||||||
|  | 				args = append(args, lbl) | ||||||
|  | 			case 'd': | ||||||
|  | 				args = append(args, diff/mag.DivBy) | ||||||
|  | 			} | ||||||
|  | 			escaped = false | ||||||
|  | 		} else { | ||||||
|  | 			escaped = ch == '%' | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return fmt.Sprintf(mag.Format, args...) | ||||||
|  | } | ||||||
							
								
								
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/modules.txt
									
									
									
									
										vendored
									
									
								
							| @@ -121,6 +121,8 @@ github.com/denisenkom/go-mssqldb/internal/decimal | |||||||
| github.com/denisenkom/go-mssqldb/internal/querytext | github.com/denisenkom/go-mssqldb/internal/querytext | ||||||
| # github.com/dgrijalva/jwt-go v3.2.0+incompatible | # github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| github.com/dgrijalva/jwt-go | github.com/dgrijalva/jwt-go | ||||||
|  | # github.com/dustin/go-humanize v1.0.0 | ||||||
|  | github.com/dustin/go-humanize | ||||||
| # github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | # github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 | ||||||
| github.com/editorconfig/editorconfig-core-go/v2 | github.com/editorconfig/editorconfig-core-go/v2 | ||||||
| # github.com/edsrzf/mmap-go v1.0.0 | # github.com/edsrzf/mmap-go v1.0.0 | ||||||
|   | |||||||
| @@ -1631,6 +1631,10 @@ | |||||||
|                                 padding-top: 8px; |                                 padding-top: 8px; | ||||||
|                                 padding-bottom: 8px; |                                 padding-bottom: 8px; | ||||||
|                                 border-bottom: 1px solid #eeeeee; |                                 border-bottom: 1px solid #eeeeee; | ||||||
|  |  | ||||||
|  |                                 a > .text.right { | ||||||
|  |                                     margin-right: 5px; | ||||||
|  |                                 } | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Lauris BH
					Lauris BH