├── logo.png ├── stack.yml ├── certinfo.yml ├── certinfo ├── vendor │ └── github.com │ │ └── dustin │ │ └── go-humanize │ │ ├── humanize.go │ │ ├── ordinals.go │ │ ├── ftoa.go │ │ ├── .travis.yml │ │ ├── big.go │ │ ├── commaf.go │ │ ├── LICENSE │ │ ├── comma.go │ │ ├── si.go │ │ ├── bytes.go │ │ ├── README.markdown │ │ ├── times.go │ │ ├── bigbytes.go │ │ └── number.go ├── Gopkg.lock ├── handler_test.go ├── Gopkg.toml └── handler.go ├── .gitignore ├── .travis.yml ├── LICENSE └── README.md /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefanprodan/openfaas-certinfo/HEAD/logo.png -------------------------------------------------------------------------------- /stack.yml: -------------------------------------------------------------------------------- 1 | provider: 2 | name: openfaas 3 | gateway: http://localhost:8080 4 | 5 | functions: 6 | certinfo: 7 | lang: go 8 | handler: ./certinfo 9 | image: stefanprodan/certinfo 10 | -------------------------------------------------------------------------------- /certinfo.yml: -------------------------------------------------------------------------------- 1 | provider: 2 | name: openfaas 3 | gateway: http://localhost:8080 4 | 5 | functions: 6 | certinfo: 7 | lang: go 8 | handler: ./certinfo 9 | image: stefanprodan/certinfo:1.0.1 10 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/humanize.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package humanize converts boring ugly numbers to human-friendly strings and back. 3 | 4 | Durations can be turned into strings such as "3 days ago", numbers 5 | representing sizes like 82854982 into useful strings like, "83 MB" or 6 | "79 MiB" (whichever you prefer). 7 | */ 8 | package humanize 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | .idea 16 | 17 | # OpenFaaS 18 | build/ 19 | template/ 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: go 3 | 4 | go: 5 | - 1.12.x 6 | 7 | services: 8 | - docker 9 | 10 | addons: 11 | apt: 12 | packages: 13 | - docker-ce 14 | 15 | before_install: 16 | - curl -sL cli.openfaas.com | sudo sh 17 | 18 | script: 19 | - faas-cli build -f certinfo.yml 20 | 21 | after_success: 22 | - if [ "$TRAVIS_BRANCH" == "master" ]; then 23 | echo $DOCKER_PASS | docker login -u "$DOCKER_USER" --password-stdin; 24 | faas-cli push -f certinfo.yml; 25 | fi 26 | -------------------------------------------------------------------------------- /certinfo/Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | name = "github.com/dustin/go-humanize" 7 | packages = ["."] 8 | revision = "bb3d318650d48840a39aa21a027c6630e198e626" 9 | 10 | [solve-meta] 11 | analyzer-name = "dep" 12 | analyzer-version = 1 13 | inputs-digest = "a5d65a2bdf47e41b99ad71813142ae93970f5806c77630b2aea665fe631dde23" 14 | solver-name = "gps-cdcl" 15 | solver-version = 1 16 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/ordinals.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import "strconv" 4 | 5 | // Ordinal gives you the input number in a rank/ordinal format. 6 | // 7 | // Ordinal(3) -> 3rd 8 | func Ordinal(x int) string { 9 | suffix := "th" 10 | switch x % 10 { 11 | case 1: 12 | if x%100 != 11 { 13 | suffix = "st" 14 | } 15 | case 2: 16 | if x%100 != 12 { 17 | suffix = "nd" 18 | } 19 | case 3: 20 | if x%100 != 13 { 21 | suffix = "rd" 22 | } 23 | } 24 | return strconv.Itoa(x) + suffix 25 | } 26 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/ftoa.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import "strconv" 4 | 5 | func stripTrailingZeros(s string) string { 6 | offset := len(s) - 1 7 | for offset > 0 { 8 | if s[offset] == '.' { 9 | offset-- 10 | break 11 | } 12 | if s[offset] != '0' { 13 | break 14 | } 15 | offset-- 16 | } 17 | return s[:offset+1] 18 | } 19 | 20 | // Ftoa converts a float to a string with no trailing zeros. 21 | func Ftoa(num float64) string { 22 | return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64)) 23 | } 24 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.3.x 5 | - 1.5.x 6 | - 1.6.x 7 | - 1.7.x 8 | - 1.8.x 9 | - 1.9.x 10 | - master 11 | matrix: 12 | allow_failures: 13 | - go: master 14 | fast_finish: true 15 | install: 16 | - # 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). 17 | script: 18 | - go get -t -v ./... 19 | - diff -u <(echo -n) <(gofmt -d -s .) 20 | - go tool vet . 21 | - go test -v -race ./... 22 | -------------------------------------------------------------------------------- /certinfo/handler_test.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | ) 7 | 8 | func TestHandleReturnsCorrectResponse(t *testing.T) { 9 | expected := "www.google.com" 10 | resp := Handle([]byte("www.google.com/about/")) 11 | 12 | r := regexp.MustCompile("(?m:" + expected + ")") 13 | if !r.MatchString(resp) { 14 | t.Fatalf("\nExpected: \n%v\nGot: \n%v", expected, resp) 15 | } 16 | } 17 | 18 | func TestHandleReturnsMultiSanResponse(t *testing.T) { 19 | expected := ".stefanprodan.com" 20 | resp := Handle([]byte("stefanprodan.com")) 21 | 22 | r := regexp.MustCompile("(?m:" + expected + ")") 23 | if !r.MatchString(resp) { 24 | t.Fatalf("\nExpected: \n%v\nGot: \n%v", expected, resp) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/big.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | // order of magnitude (to a max order) 8 | func oomm(n, b *big.Int, maxmag int) (float64, int) { 9 | mag := 0 10 | m := &big.Int{} 11 | for n.Cmp(b) >= 0 { 12 | n.DivMod(n, b, m) 13 | mag++ 14 | if mag == maxmag && maxmag >= 0 { 15 | break 16 | } 17 | } 18 | return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag 19 | } 20 | 21 | // total order of magnitude 22 | // (same as above, but with no upper limit) 23 | func oom(n, b *big.Int) (float64, int) { 24 | mag := 0 25 | m := &big.Int{} 26 | for n.Cmp(b) >= 0 { 27 | n.DivMod(n, b, m) 28 | mag++ 29 | } 30 | return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag 31 | } 32 | -------------------------------------------------------------------------------- /certinfo/Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/dustin/go-humanize" 31 | 32 | [prune] 33 | go-tests = true 34 | unused-packages = true 35 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/commaf.go: -------------------------------------------------------------------------------- 1 | // +build go1.6 2 | 3 | package humanize 4 | 5 | import ( 6 | "bytes" 7 | "math/big" 8 | "strings" 9 | ) 10 | 11 | // BigCommaf produces a string form of the given big.Float in base 10 12 | // with commas after every three orders of magnitude. 13 | func BigCommaf(v *big.Float) string { 14 | buf := &bytes.Buffer{} 15 | if v.Sign() < 0 { 16 | buf.Write([]byte{'-'}) 17 | v.Abs(v) 18 | } 19 | 20 | comma := []byte{','} 21 | 22 | parts := strings.Split(v.Text('f', -1), ".") 23 | pos := 0 24 | if len(parts[0])%3 != 0 { 25 | pos += len(parts[0]) % 3 26 | buf.WriteString(parts[0][:pos]) 27 | buf.Write(comma) 28 | } 29 | for ; pos < len(parts[0]); pos += 3 { 30 | buf.WriteString(parts[0][pos : pos+3]) 31 | buf.Write(comma) 32 | } 33 | buf.Truncate(buf.Len() - 1) 34 | 35 | if len(parts) > 1 { 36 | buf.Write([]byte{'.'}) 37 | buf.WriteString(parts[1]) 38 | } 39 | return buf.String() 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Stefan Prodan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2005-2008 Dustin Sallings 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # openfaas-certinfo 2 | 3 | [![Build Status](https://travis-ci.org/stefanprodan/openfaas-certinfo.svg?branch=master)](https://travis-ci.org/stefanprodan/openfaas-certinfo) 4 | 5 | OpenFaaS function that returns SSL/TLS certificate information for a given URL 6 | 7 | ### Usage 8 | 9 | Deploy: 10 | 11 | ```bash 12 | $ faas-cli deploy -f ./certinfo.yml --gateway=http:// 13 | ``` 14 | 15 | Invoke: 16 | 17 | ```bash 18 | $ echo -n "www.openfaas.com" | faas-cli invoke certinfo --gateway= 19 | 20 | Host 147.75.74.69 21 | Port 443 22 | Issuer Let's Encrypt Authority X3 23 | CommonName www.openfaas.com 24 | NotBefore 2017-10-06 23:54:56 +0000 UTC 25 | NotAfter 2018-01-04 23:54:56 +0000 UTC 26 | SANs [www.openfaas.com] 27 | ``` 28 | 29 | Local build: 30 | 31 | ```bash 32 | $ git clone https://github.com/stefanprodan/openfaas-certinfo 33 | $ cd openfaas-certinfo 34 | $ faas-cli build -f ./certinfo.yml 35 | ``` 36 | 37 | Test local build: 38 | 39 | ```bash 40 | $ docker run -dp 8080:8080 --name certinfo stefanprodan/certinfo 41 | $ curl -d "cli.openfaas.com" localhost:8080 42 | 43 | Host 147.75.74.69 44 | Port 443 45 | Issuer Let's Encrypt Authority X3 46 | CommonName cli.openfaas.com 47 | NotBefore 2017-10-07 11:55:06 +0000 UTC 48 | NotAfter 2018-01-05 11:55:06 +0000 UTC 49 | SANs [cli.openfaas.com] 50 | ``` 51 | -------------------------------------------------------------------------------- /certinfo/handler.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "net" 8 | "net/url" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/dustin/go-humanize" 14 | ) 15 | 16 | func Handle(req []byte) string { 17 | request := strings.ToLower(string(req)) 18 | if !strings.HasPrefix(request, "http") { 19 | request = "https://" + request 20 | } 21 | 22 | u, err := url.Parse(request) 23 | if err != nil { 24 | return fmt.Sprintf("Error: %v", err) 25 | } 26 | 27 | address := u.Hostname() + ":443" 28 | ipConn, err := net.DialTimeout("tcp", address, 5*time.Second) 29 | if err != nil { 30 | return fmt.Sprintf("SSL/TLS not enabed on %v\nDial error: %v", u.Hostname(), err) 31 | } 32 | 33 | defer ipConn.Close() 34 | conn := tls.Client(ipConn, &tls.Config{ 35 | InsecureSkipVerify: true, 36 | ServerName: u.Hostname(), 37 | }) 38 | if err = conn.Handshake(); err != nil { 39 | return fmt.Sprintf("Invalid SSL/TLS for %v\nHandshake error: %v", address, err) 40 | } 41 | 42 | defer conn.Close() 43 | addr := conn.RemoteAddr() 44 | host, port, err := net.SplitHostPort(addr.String()) 45 | if err != nil { 46 | return fmt.Sprintf("Error: %v", err) 47 | } 48 | 49 | cert := conn.ConnectionState().PeerCertificates[0] 50 | asJson := os.Getenv("Http_Query") 51 | 52 | if len(asJson) > 0 && asJson == "output=json" { 53 | res := struct { 54 | Host string 55 | Port string 56 | Issuer string 57 | CommonName string 58 | NotBefore time.Time 59 | NotAfter time.Time 60 | NotAfterUnix int64 61 | SANs []string 62 | TimeRemaining string 63 | }{ 64 | host, 65 | port, 66 | cert.Issuer.CommonName, 67 | cert.Subject.CommonName, 68 | cert.NotBefore, 69 | cert.NotAfter, 70 | cert.NotAfter.Unix(), 71 | cert.DNSNames, 72 | humanize.Time(cert.NotAfter), 73 | } 74 | 75 | b, err := json.Marshal(res) 76 | if err != nil { 77 | return fmt.Sprintf("Error: %v", err) 78 | } 79 | return string(b) 80 | } 81 | 82 | return fmt.Sprintf("Host %v\nPort %v\nIssuer %v\nCommonName %v\nNotBefore %v\nNotAfter %v\nNotAfterUnix %v\nSANs %v\nTimeRemaining %v", 83 | host, port, cert.Issuer.CommonName, cert.Subject.CommonName, cert.NotBefore, cert.NotAfter, cert.NotAfter.Unix(), cert.DNSNames, humanize.Time(cert.NotAfter)) 84 | } 85 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/comma.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | // Comma produces a string form of the given number in base 10 with 12 | // commas after every three orders of magnitude. 13 | // 14 | // e.g. Comma(834142) -> 834,142 15 | func Comma(v int64) string { 16 | sign := "" 17 | 18 | // Min int64 can't be negated to a usable value, so it has to be special cased. 19 | if v == math.MinInt64 { 20 | return "-9,223,372,036,854,775,808" 21 | } 22 | 23 | if v < 0 { 24 | sign = "-" 25 | v = 0 - v 26 | } 27 | 28 | parts := []string{"", "", "", "", "", "", ""} 29 | j := len(parts) - 1 30 | 31 | for v > 999 { 32 | parts[j] = strconv.FormatInt(v%1000, 10) 33 | switch len(parts[j]) { 34 | case 2: 35 | parts[j] = "0" + parts[j] 36 | case 1: 37 | parts[j] = "00" + parts[j] 38 | } 39 | v = v / 1000 40 | j-- 41 | } 42 | parts[j] = strconv.Itoa(int(v)) 43 | return sign + strings.Join(parts[j:], ",") 44 | } 45 | 46 | // Commaf produces a string form of the given number in base 10 with 47 | // commas after every three orders of magnitude. 48 | // 49 | // e.g. Commaf(834142.32) -> 834,142.32 50 | func Commaf(v float64) string { 51 | buf := &bytes.Buffer{} 52 | if v < 0 { 53 | buf.Write([]byte{'-'}) 54 | v = 0 - v 55 | } 56 | 57 | comma := []byte{','} 58 | 59 | parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".") 60 | pos := 0 61 | if len(parts[0])%3 != 0 { 62 | pos += len(parts[0]) % 3 63 | buf.WriteString(parts[0][:pos]) 64 | buf.Write(comma) 65 | } 66 | for ; pos < len(parts[0]); pos += 3 { 67 | buf.WriteString(parts[0][pos : pos+3]) 68 | buf.Write(comma) 69 | } 70 | buf.Truncate(buf.Len() - 1) 71 | 72 | if len(parts) > 1 { 73 | buf.Write([]byte{'.'}) 74 | buf.WriteString(parts[1]) 75 | } 76 | return buf.String() 77 | } 78 | 79 | // BigComma produces a string form of the given big.Int in base 10 80 | // with commas after every three orders of magnitude. 81 | func BigComma(b *big.Int) string { 82 | sign := "" 83 | if b.Sign() < 0 { 84 | sign = "-" 85 | b.Abs(b) 86 | } 87 | 88 | athousand := big.NewInt(1000) 89 | c := (&big.Int{}).Set(b) 90 | _, m := oom(c, athousand) 91 | parts := make([]string, m+1) 92 | j := len(parts) - 1 93 | 94 | mod := &big.Int{} 95 | for b.Cmp(athousand) >= 0 { 96 | b.DivMod(b, athousand, mod) 97 | parts[j] = strconv.FormatInt(mod.Int64(), 10) 98 | switch len(parts[j]) { 99 | case 2: 100 | parts[j] = "0" + parts[j] 101 | case 1: 102 | parts[j] = "00" + parts[j] 103 | } 104 | j-- 105 | } 106 | parts[j] = strconv.Itoa(int(b.Int64())) 107 | return sign + strings.Join(parts[j:], ",") 108 | } 109 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/si.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "errors" 5 | "math" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | var siPrefixTable = map[float64]string{ 11 | -24: "y", // yocto 12 | -21: "z", // zepto 13 | -18: "a", // atto 14 | -15: "f", // femto 15 | -12: "p", // pico 16 | -9: "n", // nano 17 | -6: "µ", // micro 18 | -3: "m", // milli 19 | 0: "", 20 | 3: "k", // kilo 21 | 6: "M", // mega 22 | 9: "G", // giga 23 | 12: "T", // tera 24 | 15: "P", // peta 25 | 18: "E", // exa 26 | 21: "Z", // zetta 27 | 24: "Y", // yotta 28 | } 29 | 30 | var revSIPrefixTable = revfmap(siPrefixTable) 31 | 32 | // revfmap reverses the map and precomputes the power multiplier 33 | func revfmap(in map[float64]string) map[string]float64 { 34 | rv := map[string]float64{} 35 | for k, v := range in { 36 | rv[v] = math.Pow(10, k) 37 | } 38 | return rv 39 | } 40 | 41 | var riParseRegex *regexp.Regexp 42 | 43 | func init() { 44 | ri := `^([\-0-9.]+)\s?([` 45 | for _, v := range siPrefixTable { 46 | ri += v 47 | } 48 | ri += `]?)(.*)` 49 | 50 | riParseRegex = regexp.MustCompile(ri) 51 | } 52 | 53 | // ComputeSI finds the most appropriate SI prefix for the given number 54 | // and returns the prefix along with the value adjusted to be within 55 | // that prefix. 56 | // 57 | // See also: SI, ParseSI. 58 | // 59 | // e.g. ComputeSI(2.2345e-12) -> (2.2345, "p") 60 | func ComputeSI(input float64) (float64, string) { 61 | if input == 0 { 62 | return 0, "" 63 | } 64 | mag := math.Abs(input) 65 | exponent := math.Floor(logn(mag, 10)) 66 | exponent = math.Floor(exponent/3) * 3 67 | 68 | value := mag / math.Pow(10, exponent) 69 | 70 | // Handle special case where value is exactly 1000.0 71 | // Should return 1 M instead of 1000 k 72 | if value == 1000.0 { 73 | exponent += 3 74 | value = mag / math.Pow(10, exponent) 75 | } 76 | 77 | value = math.Copysign(value, input) 78 | 79 | prefix := siPrefixTable[exponent] 80 | return value, prefix 81 | } 82 | 83 | // SI returns a string with default formatting. 84 | // 85 | // SI uses Ftoa to format float value, removing trailing zeros. 86 | // 87 | // See also: ComputeSI, ParseSI. 88 | // 89 | // e.g. SI(1000000, "B") -> 1 MB 90 | // e.g. SI(2.2345e-12, "F") -> 2.2345 pF 91 | func SI(input float64, unit string) string { 92 | value, prefix := ComputeSI(input) 93 | return Ftoa(value) + " " + prefix + unit 94 | } 95 | 96 | var errInvalid = errors.New("invalid input") 97 | 98 | // ParseSI parses an SI string back into the number and unit. 99 | // 100 | // See also: SI, ComputeSI. 101 | // 102 | // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil) 103 | func ParseSI(input string) (float64, string, error) { 104 | found := riParseRegex.FindStringSubmatch(input) 105 | if len(found) != 4 { 106 | return 0, "", errInvalid 107 | } 108 | mag := revSIPrefixTable[found[2]] 109 | unit := found[3] 110 | 111 | base, err := strconv.ParseFloat(found[1], 64) 112 | return base * mag, unit, err 113 | } 114 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/bytes.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "strconv" 7 | "strings" 8 | "unicode" 9 | ) 10 | 11 | // IEC Sizes. 12 | // kibis of bits 13 | const ( 14 | Byte = 1 << (iota * 10) 15 | KiByte 16 | MiByte 17 | GiByte 18 | TiByte 19 | PiByte 20 | EiByte 21 | ) 22 | 23 | // SI Sizes. 24 | const ( 25 | IByte = 1 26 | KByte = IByte * 1000 27 | MByte = KByte * 1000 28 | GByte = MByte * 1000 29 | TByte = GByte * 1000 30 | PByte = TByte * 1000 31 | EByte = PByte * 1000 32 | ) 33 | 34 | var bytesSizeTable = map[string]uint64{ 35 | "b": Byte, 36 | "kib": KiByte, 37 | "kb": KByte, 38 | "mib": MiByte, 39 | "mb": MByte, 40 | "gib": GiByte, 41 | "gb": GByte, 42 | "tib": TiByte, 43 | "tb": TByte, 44 | "pib": PiByte, 45 | "pb": PByte, 46 | "eib": EiByte, 47 | "eb": EByte, 48 | // Without suffix 49 | "": Byte, 50 | "ki": KiByte, 51 | "k": KByte, 52 | "mi": MiByte, 53 | "m": MByte, 54 | "gi": GiByte, 55 | "g": GByte, 56 | "ti": TiByte, 57 | "t": TByte, 58 | "pi": PiByte, 59 | "p": PByte, 60 | "ei": EiByte, 61 | "e": EByte, 62 | } 63 | 64 | func logn(n, b float64) float64 { 65 | return math.Log(n) / math.Log(b) 66 | } 67 | 68 | func humanateBytes(s uint64, base float64, sizes []string) string { 69 | if s < 10 { 70 | return fmt.Sprintf("%d B", s) 71 | } 72 | e := math.Floor(logn(float64(s), base)) 73 | suffix := sizes[int(e)] 74 | val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10 75 | f := "%.0f %s" 76 | if val < 10 { 77 | f = "%.1f %s" 78 | } 79 | 80 | return fmt.Sprintf(f, val, suffix) 81 | } 82 | 83 | // Bytes produces a human readable representation of an SI size. 84 | // 85 | // See also: ParseBytes. 86 | // 87 | // Bytes(82854982) -> 83 MB 88 | func Bytes(s uint64) string { 89 | sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} 90 | return humanateBytes(s, 1000, sizes) 91 | } 92 | 93 | // IBytes produces a human readable representation of an IEC size. 94 | // 95 | // See also: ParseBytes. 96 | // 97 | // IBytes(82854982) -> 79 MiB 98 | func IBytes(s uint64) string { 99 | sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} 100 | return humanateBytes(s, 1024, sizes) 101 | } 102 | 103 | // ParseBytes parses a string representation of bytes into the number 104 | // of bytes it represents. 105 | // 106 | // See Also: Bytes, IBytes. 107 | // 108 | // ParseBytes("42 MB") -> 42000000, nil 109 | // ParseBytes("42 mib") -> 44040192, nil 110 | func ParseBytes(s string) (uint64, error) { 111 | lastDigit := 0 112 | hasComma := false 113 | for _, r := range s { 114 | if !(unicode.IsDigit(r) || r == '.' || r == ',') { 115 | break 116 | } 117 | if r == ',' { 118 | hasComma = true 119 | } 120 | lastDigit++ 121 | } 122 | 123 | num := s[:lastDigit] 124 | if hasComma { 125 | num = strings.Replace(num, ",", "", -1) 126 | } 127 | 128 | f, err := strconv.ParseFloat(num, 64) 129 | if err != nil { 130 | return 0, err 131 | } 132 | 133 | extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) 134 | if m, ok := bytesSizeTable[extra]; ok { 135 | f *= float64(m) 136 | if f >= math.MaxUint64 { 137 | return 0, fmt.Errorf("too large: %v", s) 138 | } 139 | return uint64(f), nil 140 | } 141 | 142 | return 0, fmt.Errorf("unhandled size name: %v", extra) 143 | } 144 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/README.markdown: -------------------------------------------------------------------------------- 1 | # Humane Units [![Build Status](https://travis-ci.org/dustin/go-humanize.svg?branch=master)](https://travis-ci.org/dustin/go-humanize) [![GoDoc](https://godoc.org/github.com/dustin/go-humanize?status.svg)](https://godoc.org/github.com/dustin/go-humanize) 2 | 3 | Just a few functions for helping humanize times and sizes. 4 | 5 | `go get` it as `github.com/dustin/go-humanize`, import it as 6 | `"github.com/dustin/go-humanize"`, use it as `humanize`. 7 | 8 | See [godoc](https://godoc.org/github.com/dustin/go-humanize) for 9 | complete documentation. 10 | 11 | ## Sizes 12 | 13 | This lets you take numbers like `82854982` and convert them to useful 14 | strings like, `83 MB` or `79 MiB` (whichever you prefer). 15 | 16 | Example: 17 | 18 | ```go 19 | fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB. 20 | ``` 21 | 22 | ## Times 23 | 24 | This lets you take a `time.Time` and spit it out in relative terms. 25 | For example, `12 seconds ago` or `3 days from now`. 26 | 27 | Example: 28 | 29 | ```go 30 | fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago. 31 | ``` 32 | 33 | Thanks to Kyle Lemons for the time implementation from an IRC 34 | conversation one day. It's pretty neat. 35 | 36 | ## Ordinals 37 | 38 | From a [mailing list discussion][odisc] where a user wanted to be able 39 | to label ordinals. 40 | 41 | 0 -> 0th 42 | 1 -> 1st 43 | 2 -> 2nd 44 | 3 -> 3rd 45 | 4 -> 4th 46 | [...] 47 | 48 | Example: 49 | 50 | ```go 51 | fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend. 52 | ``` 53 | 54 | ## Commas 55 | 56 | Want to shove commas into numbers? Be my guest. 57 | 58 | 0 -> 0 59 | 100 -> 100 60 | 1000 -> 1,000 61 | 1000000000 -> 1,000,000,000 62 | -100000 -> -100,000 63 | 64 | Example: 65 | 66 | ```go 67 | fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491. 68 | ``` 69 | 70 | ## Ftoa 71 | 72 | Nicer float64 formatter that removes trailing zeros. 73 | 74 | ```go 75 | fmt.Printf("%f", 2.24) // 2.240000 76 | fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24 77 | fmt.Printf("%f", 2.0) // 2.000000 78 | fmt.Printf("%s", humanize.Ftoa(2.0)) // 2 79 | ``` 80 | 81 | ## SI notation 82 | 83 | Format numbers with [SI notation][sinotation]. 84 | 85 | Example: 86 | 87 | ```go 88 | humanize.SI(0.00000000223, "M") // 2.23 nM 89 | ``` 90 | 91 | ## English-specific functions 92 | 93 | The following functions are in the `humanize/english` subpackage. 94 | 95 | ### Plurals 96 | 97 | Simple English pluralization 98 | 99 | ```go 100 | english.PluralWord(1, "object", "") // object 101 | english.PluralWord(42, "object", "") // objects 102 | english.PluralWord(2, "bus", "") // buses 103 | english.PluralWord(99, "locus", "loci") // loci 104 | 105 | english.Plural(1, "object", "") // 1 object 106 | english.Plural(42, "object", "") // 42 objects 107 | english.Plural(2, "bus", "") // 2 buses 108 | english.Plural(99, "locus", "loci") // 99 loci 109 | ``` 110 | 111 | ### Word series 112 | 113 | Format comma-separated words lists with conjuctions: 114 | 115 | ```go 116 | english.WordSeries([]string{"foo"}, "and") // foo 117 | english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar 118 | english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz 119 | 120 | english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz 121 | ``` 122 | 123 | [odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion 124 | [sinotation]: http://en.wikipedia.org/wiki/Metric_prefix 125 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/times.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sort" 7 | "time" 8 | ) 9 | 10 | // Seconds-based time units 11 | const ( 12 | Day = 24 * time.Hour 13 | Week = 7 * Day 14 | Month = 30 * Day 15 | Year = 12 * Month 16 | LongTime = 37 * Year 17 | ) 18 | 19 | // Time formats a time into a relative string. 20 | // 21 | // Time(someT) -> "3 weeks ago" 22 | func Time(then time.Time) string { 23 | return RelTime(then, time.Now(), "ago", "from now") 24 | } 25 | 26 | // A RelTimeMagnitude struct contains a relative time point at which 27 | // the relative format of time will switch to a new format string. A 28 | // slice of these in ascending order by their "D" field is passed to 29 | // CustomRelTime to format durations. 30 | // 31 | // The Format field is a string that may contain a "%s" which will be 32 | // replaced with the appropriate signed label (e.g. "ago" or "from 33 | // now") and a "%d" that will be replaced by the quantity. 34 | // 35 | // The DivBy field is the amount of time the time difference must be 36 | // divided by in order to display correctly. 37 | // 38 | // e.g. if D is 2*time.Minute and you want to display "%d minutes %s" 39 | // DivBy should be time.Minute so whatever the duration is will be 40 | // expressed in minutes. 41 | type RelTimeMagnitude struct { 42 | D time.Duration 43 | Format string 44 | DivBy time.Duration 45 | } 46 | 47 | var defaultMagnitudes = []RelTimeMagnitude{ 48 | {time.Second, "now", time.Second}, 49 | {2 * time.Second, "1 second %s", 1}, 50 | {time.Minute, "%d seconds %s", time.Second}, 51 | {2 * time.Minute, "1 minute %s", 1}, 52 | {time.Hour, "%d minutes %s", time.Minute}, 53 | {2 * time.Hour, "1 hour %s", 1}, 54 | {Day, "%d hours %s", time.Hour}, 55 | {2 * Day, "1 day %s", 1}, 56 | {Week, "%d days %s", Day}, 57 | {2 * Week, "1 week %s", 1}, 58 | {Month, "%d weeks %s", Week}, 59 | {2 * Month, "1 month %s", 1}, 60 | {Year, "%d months %s", Month}, 61 | {18 * Month, "1 year %s", 1}, 62 | {2 * Year, "2 years %s", 1}, 63 | {LongTime, "%d years %s", Year}, 64 | {math.MaxInt64, "a long while %s", 1}, 65 | } 66 | 67 | // RelTime formats a time into a relative string. 68 | // 69 | // It takes two times and two labels. In addition to the generic time 70 | // delta string (e.g. 5 minutes), the labels are used applied so that 71 | // the label corresponding to the smaller time is applied. 72 | // 73 | // RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier" 74 | func RelTime(a, b time.Time, albl, blbl string) string { 75 | return CustomRelTime(a, b, albl, blbl, defaultMagnitudes) 76 | } 77 | 78 | // CustomRelTime formats a time into a relative string. 79 | // 80 | // It takes two times two labels and a table of relative time formats. 81 | // In addition to the generic time delta string (e.g. 5 minutes), the 82 | // labels are used applied so that the label corresponding to the 83 | // smaller time is applied. 84 | func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string { 85 | lbl := albl 86 | diff := b.Sub(a) 87 | 88 | if a.After(b) { 89 | lbl = blbl 90 | diff = a.Sub(b) 91 | } 92 | 93 | n := sort.Search(len(magnitudes), func(i int) bool { 94 | return magnitudes[i].D > diff 95 | }) 96 | 97 | if n >= len(magnitudes) { 98 | n = len(magnitudes) - 1 99 | } 100 | mag := magnitudes[n] 101 | args := []interface{}{} 102 | escaped := false 103 | for _, ch := range mag.Format { 104 | if escaped { 105 | switch ch { 106 | case 's': 107 | args = append(args, lbl) 108 | case 'd': 109 | args = append(args, diff/mag.DivBy) 110 | } 111 | escaped = false 112 | } else { 113 | escaped = ch == '%' 114 | } 115 | } 116 | return fmt.Sprintf(mag.Format, args...) 117 | } 118 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/bigbytes.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "unicode" 8 | ) 9 | 10 | var ( 11 | bigIECExp = big.NewInt(1024) 12 | 13 | // BigByte is one byte in bit.Ints 14 | BigByte = big.NewInt(1) 15 | // BigKiByte is 1,024 bytes in bit.Ints 16 | BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp) 17 | // BigMiByte is 1,024 k bytes in bit.Ints 18 | BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp) 19 | // BigGiByte is 1,024 m bytes in bit.Ints 20 | BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp) 21 | // BigTiByte is 1,024 g bytes in bit.Ints 22 | BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp) 23 | // BigPiByte is 1,024 t bytes in bit.Ints 24 | BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp) 25 | // BigEiByte is 1,024 p bytes in bit.Ints 26 | BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp) 27 | // BigZiByte is 1,024 e bytes in bit.Ints 28 | BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp) 29 | // BigYiByte is 1,024 z bytes in bit.Ints 30 | BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp) 31 | ) 32 | 33 | var ( 34 | bigSIExp = big.NewInt(1000) 35 | 36 | // BigSIByte is one SI byte in big.Ints 37 | BigSIByte = big.NewInt(1) 38 | // BigKByte is 1,000 SI bytes in big.Ints 39 | BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp) 40 | // BigMByte is 1,000 SI k bytes in big.Ints 41 | BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp) 42 | // BigGByte is 1,000 SI m bytes in big.Ints 43 | BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp) 44 | // BigTByte is 1,000 SI g bytes in big.Ints 45 | BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp) 46 | // BigPByte is 1,000 SI t bytes in big.Ints 47 | BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp) 48 | // BigEByte is 1,000 SI p bytes in big.Ints 49 | BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp) 50 | // BigZByte is 1,000 SI e bytes in big.Ints 51 | BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp) 52 | // BigYByte is 1,000 SI z bytes in big.Ints 53 | BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp) 54 | ) 55 | 56 | var bigBytesSizeTable = map[string]*big.Int{ 57 | "b": BigByte, 58 | "kib": BigKiByte, 59 | "kb": BigKByte, 60 | "mib": BigMiByte, 61 | "mb": BigMByte, 62 | "gib": BigGiByte, 63 | "gb": BigGByte, 64 | "tib": BigTiByte, 65 | "tb": BigTByte, 66 | "pib": BigPiByte, 67 | "pb": BigPByte, 68 | "eib": BigEiByte, 69 | "eb": BigEByte, 70 | "zib": BigZiByte, 71 | "zb": BigZByte, 72 | "yib": BigYiByte, 73 | "yb": BigYByte, 74 | // Without suffix 75 | "": BigByte, 76 | "ki": BigKiByte, 77 | "k": BigKByte, 78 | "mi": BigMiByte, 79 | "m": BigMByte, 80 | "gi": BigGiByte, 81 | "g": BigGByte, 82 | "ti": BigTiByte, 83 | "t": BigTByte, 84 | "pi": BigPiByte, 85 | "p": BigPByte, 86 | "ei": BigEiByte, 87 | "e": BigEByte, 88 | "z": BigZByte, 89 | "zi": BigZiByte, 90 | "y": BigYByte, 91 | "yi": BigYiByte, 92 | } 93 | 94 | var ten = big.NewInt(10) 95 | 96 | func humanateBigBytes(s, base *big.Int, sizes []string) string { 97 | if s.Cmp(ten) < 0 { 98 | return fmt.Sprintf("%d B", s) 99 | } 100 | c := (&big.Int{}).Set(s) 101 | val, mag := oomm(c, base, len(sizes)-1) 102 | suffix := sizes[mag] 103 | f := "%.0f %s" 104 | if val < 10 { 105 | f = "%.1f %s" 106 | } 107 | 108 | return fmt.Sprintf(f, val, suffix) 109 | 110 | } 111 | 112 | // BigBytes produces a human readable representation of an SI size. 113 | // 114 | // See also: ParseBigBytes. 115 | // 116 | // BigBytes(82854982) -> 83 MB 117 | func BigBytes(s *big.Int) string { 118 | sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} 119 | return humanateBigBytes(s, bigSIExp, sizes) 120 | } 121 | 122 | // BigIBytes produces a human readable representation of an IEC size. 123 | // 124 | // See also: ParseBigBytes. 125 | // 126 | // BigIBytes(82854982) -> 79 MiB 127 | func BigIBytes(s *big.Int) string { 128 | sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} 129 | return humanateBigBytes(s, bigIECExp, sizes) 130 | } 131 | 132 | // ParseBigBytes parses a string representation of bytes into the number 133 | // of bytes it represents. 134 | // 135 | // See also: BigBytes, BigIBytes. 136 | // 137 | // ParseBigBytes("42 MB") -> 42000000, nil 138 | // ParseBigBytes("42 mib") -> 44040192, nil 139 | func ParseBigBytes(s string) (*big.Int, error) { 140 | lastDigit := 0 141 | hasComma := false 142 | for _, r := range s { 143 | if !(unicode.IsDigit(r) || r == '.' || r == ',') { 144 | break 145 | } 146 | if r == ',' { 147 | hasComma = true 148 | } 149 | lastDigit++ 150 | } 151 | 152 | num := s[:lastDigit] 153 | if hasComma { 154 | num = strings.Replace(num, ",", "", -1) 155 | } 156 | 157 | val := &big.Rat{} 158 | _, err := fmt.Sscanf(num, "%f", val) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | extra := strings.ToLower(strings.TrimSpace(s[lastDigit:])) 164 | if m, ok := bigBytesSizeTable[extra]; ok { 165 | mv := (&big.Rat{}).SetInt(m) 166 | val.Mul(val, mv) 167 | rv := &big.Int{} 168 | rv.Div(val.Num(), val.Denom()) 169 | return rv, nil 170 | } 171 | 172 | return nil, fmt.Errorf("unhandled size name: %v", extra) 173 | } 174 | -------------------------------------------------------------------------------- /certinfo/vendor/github.com/dustin/go-humanize/number.go: -------------------------------------------------------------------------------- 1 | package humanize 2 | 3 | /* 4 | Slightly adapted from the source to fit go-humanize. 5 | 6 | Author: https://github.com/gorhill 7 | Source: https://gist.github.com/gorhill/5285193 8 | 9 | */ 10 | 11 | import ( 12 | "math" 13 | "strconv" 14 | ) 15 | 16 | var ( 17 | renderFloatPrecisionMultipliers = [...]float64{ 18 | 1, 19 | 10, 20 | 100, 21 | 1000, 22 | 10000, 23 | 100000, 24 | 1000000, 25 | 10000000, 26 | 100000000, 27 | 1000000000, 28 | } 29 | 30 | renderFloatPrecisionRounders = [...]float64{ 31 | 0.5, 32 | 0.05, 33 | 0.005, 34 | 0.0005, 35 | 0.00005, 36 | 0.000005, 37 | 0.0000005, 38 | 0.00000005, 39 | 0.000000005, 40 | 0.0000000005, 41 | } 42 | ) 43 | 44 | // FormatFloat produces a formatted number as string based on the following user-specified criteria: 45 | // * thousands separator 46 | // * decimal separator 47 | // * decimal precision 48 | // 49 | // Usage: s := RenderFloat(format, n) 50 | // The format parameter tells how to render the number n. 51 | // 52 | // See examples: http://play.golang.org/p/LXc1Ddm1lJ 53 | // 54 | // Examples of format strings, given n = 12345.6789: 55 | // "#,###.##" => "12,345.67" 56 | // "#,###." => "12,345" 57 | // "#,###" => "12345,678" 58 | // "#\u202F###,##" => "12 345,68" 59 | // "#.###,###### => 12.345,678900 60 | // "" (aka default format) => 12,345.67 61 | // 62 | // The highest precision allowed is 9 digits after the decimal symbol. 63 | // There is also a version for integer number, FormatInteger(), 64 | // which is convenient for calls within template. 65 | func FormatFloat(format string, n float64) string { 66 | // Special cases: 67 | // NaN = "NaN" 68 | // +Inf = "+Infinity" 69 | // -Inf = "-Infinity" 70 | if math.IsNaN(n) { 71 | return "NaN" 72 | } 73 | if n > math.MaxFloat64 { 74 | return "Infinity" 75 | } 76 | if n < -math.MaxFloat64 { 77 | return "-Infinity" 78 | } 79 | 80 | // default format 81 | precision := 2 82 | decimalStr := "." 83 | thousandStr := "," 84 | positiveStr := "" 85 | negativeStr := "-" 86 | 87 | if len(format) > 0 { 88 | format := []rune(format) 89 | 90 | // If there is an explicit format directive, 91 | // then default values are these: 92 | precision = 9 93 | thousandStr = "" 94 | 95 | // collect indices of meaningful formatting directives 96 | formatIndx := []int{} 97 | for i, char := range format { 98 | if char != '#' && char != '0' { 99 | formatIndx = append(formatIndx, i) 100 | } 101 | } 102 | 103 | if len(formatIndx) > 0 { 104 | // Directive at index 0: 105 | // Must be a '+' 106 | // Raise an error if not the case 107 | // index: 0123456789 108 | // +0.000,000 109 | // +000,000.0 110 | // +0000.00 111 | // +0000 112 | if formatIndx[0] == 0 { 113 | if format[formatIndx[0]] != '+' { 114 | panic("RenderFloat(): invalid positive sign directive") 115 | } 116 | positiveStr = "+" 117 | formatIndx = formatIndx[1:] 118 | } 119 | 120 | // Two directives: 121 | // First is thousands separator 122 | // Raise an error if not followed by 3-digit 123 | // 0123456789 124 | // 0.000,000 125 | // 000,000.00 126 | if len(formatIndx) == 2 { 127 | if (formatIndx[1] - formatIndx[0]) != 4 { 128 | panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers") 129 | } 130 | thousandStr = string(format[formatIndx[0]]) 131 | formatIndx = formatIndx[1:] 132 | } 133 | 134 | // One directive: 135 | // Directive is decimal separator 136 | // The number of digit-specifier following the separator indicates wanted precision 137 | // 0123456789 138 | // 0.00 139 | // 000,0000 140 | if len(formatIndx) == 1 { 141 | decimalStr = string(format[formatIndx[0]]) 142 | precision = len(format) - formatIndx[0] - 1 143 | } 144 | } 145 | } 146 | 147 | // generate sign part 148 | var signStr string 149 | if n >= 0.000000001 { 150 | signStr = positiveStr 151 | } else if n <= -0.000000001 { 152 | signStr = negativeStr 153 | n = -n 154 | } else { 155 | signStr = "" 156 | n = 0.0 157 | } 158 | 159 | // split number into integer and fractional parts 160 | intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) 161 | 162 | // generate integer part string 163 | intStr := strconv.FormatInt(int64(intf), 10) 164 | 165 | // add thousand separator if required 166 | if len(thousandStr) > 0 { 167 | for i := len(intStr); i > 3; { 168 | i -= 3 169 | intStr = intStr[:i] + thousandStr + intStr[i:] 170 | } 171 | } 172 | 173 | // no fractional part, we can leave now 174 | if precision == 0 { 175 | return signStr + intStr 176 | } 177 | 178 | // generate fractional part 179 | fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision])) 180 | // may need padding 181 | if len(fracStr) < precision { 182 | fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr 183 | } 184 | 185 | return signStr + intStr + decimalStr + fracStr 186 | } 187 | 188 | // FormatInteger produces a formatted number as string. 189 | // See FormatFloat. 190 | func FormatInteger(format string, n int) string { 191 | return FormatFloat(format, float64(n)) 192 | } 193 | --------------------------------------------------------------------------------