diff --git a/go.mod b/go.mod index 2dc529b..3b9b644 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.paulbsd.com/paulbsd/vmail go 1.14 require ( + github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d github.com/labstack/echo v3.3.10+incompatible // indirect github.com/labstack/echo/v4 v4.1.16 github.com/lib/pq v1.7.0 diff --git a/go.sum b/go.sum index 97a998e..6d90e4e 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWX github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= diff --git a/src/api/admin.go b/src/api/admin.go new file mode 100644 index 0000000..6fe4385 --- /dev/null +++ b/src/api/admin.go @@ -0,0 +1,12 @@ +package api + +// Admin defines the admin struct +type Admin struct { + Username string `json:"username"` + Created string `json:"created"` + Modified string `json:"modified"` + Active bool `json:"active"` + Superadmin bool `json:"superadmin"` + Phone string `json:"phone"` + EmailOther string `json:"emailOther"` +} diff --git a/src/api/alias.go b/src/api/alias.go new file mode 100644 index 0000000..ad8df45 --- /dev/null +++ b/src/api/alias.go @@ -0,0 +1,11 @@ +package api + +// Alias defines the admin struct +type Alias struct { + Address string `json:"address"` + Goto string `json:"goto"` + Domain string `json:"domain"` + Created string `json:"created"` + Modified string `json:"modified"` + Active bool `json:"active"` +} diff --git a/src/api/log.go b/src/api/log.go new file mode 100644 index 0000000..7aa6a43 --- /dev/null +++ b/src/api/log.go @@ -0,0 +1,11 @@ +package api + +// Log defines the admin struct +type Log struct { + ID int `json:"id"` + Timestamp string `json:"timestamp"` + Username string `json:"username"` + Domain string `json:"domain"` + Action string `json:"action"` + Data string `json:"data"` +} diff --git a/src/api/mailbox.go b/src/api/mailbox.go new file mode 100644 index 0000000..8ed77a8 --- /dev/null +++ b/src/api/mailbox.go @@ -0,0 +1,16 @@ +package api + +// Mailbox defines the admin struct +type Mailbox struct { + Username string `json:"username"` + Name string `json:"name"` + Maildir string `json:"maildir"` + Quota string `json:"quota"` + Created string `json:"created"` + Modified string `json:"modified"` + Active bool `json:"active"` + Domain string `json:"domain"` + LocalPart string `json:"localpart"` + Phone string `json:"phone"` + EmailOther string `json:"emailother"` +} diff --git a/src/config/main.go b/src/config/main.go index 4a012ea..a0c4a6d 100644 --- a/src/config/main.go +++ b/src/config/main.go @@ -40,14 +40,13 @@ func (config *Config) GetConfig() error { // Config is the global config of vmail type Config struct { - Db *xorm.Engine - DbHostname string - DbName string - DbUsername string - DbPassword string - URLBase string - vmailGroups []string - Port int - Init bool - Version string + Db *xorm.Engine + DbHostname string + DbName string + DbUsername string + DbPassword string + URLBase string + Port int + Init bool + Version string } diff --git a/src/database/main.go b/src/database/main.go index a8dd66d..d41dc8e 100644 --- a/src/database/main.go +++ b/src/database/main.go @@ -20,7 +20,7 @@ func Initialize(ctx *context.Context, config *config.Config) (err error) { } config.Db.SetMapper(names.GonicMapper{}) - config.Db.ShowSQL(false) + config.Db.ShowSQL(true) err = models.NewEngine(ctx, config) diff --git a/src/models/admin.go b/src/models/admin.go index a9c1c5e..c4f59f0 100644 --- a/src/models/admin.go +++ b/src/models/admin.go @@ -4,25 +4,57 @@ import ( "context" "time" + "git.paulbsd.com/paulbsd/vmail/src/api" "git.paulbsd.com/paulbsd/vmail/src/config" ) -// GetAdmins ... -func GetAdmins(ctx *context.Context, config *config.Config) (admins []Admin, err error) { - err = config.Db.Cols("username").Find(&admins) +// GetAdmins return list of apiadmins +func GetAdmins(ctx *context.Context, config *config.Config) (apiadmins []*api.Admin, err error) { + var admins []Admin + err = config.Db.Find(&admins) + for _, adm := range admins { + apiadmins = append(apiadmins, adm.APIFormat()) + } return } +// GetAdmin ... +func GetAdmin(ctx *context.Context, config *config.Config, id interface{}) (apiadmin *api.Admin, err error) { + var admin Admin + has, err := config.Db.Where("username = ?", id).Get(&admin) + if !has || err != nil { + return + } + apiadmin = admin.APIFormat() + return +} + +// APIFormat returns a JSON formatted object of Admin +func (admin *Admin) APIFormat() *api.Admin { + if admin == nil { + return nil + } + return &api.Admin{ + Username: admin.Username, + Created: admin.Created.Format(timetostring), + Modified: admin.Modified.Format(timetostring), + Active: admin.Active, + Superadmin: admin.Superadmin, + Phone: admin.Phone, + EmailOther: admin.EmailOther, + } +} + // Admin defines the admin struct type Admin struct { Username string `xorm:"not null pk unique VARCHAR(255)"` - password string `xorm:"not null default '' VARCHAR(255)"` + Password string `xorm:"not null default '' VARCHAR(255)"` Created time.Time `xorm:"default now() TIMESTAMPZ"` Modified time.Time `xorm:"default now() TIMESTAMPZ"` Active bool `xorm:"not null default true BOOL"` Superadmin bool `xorm:"not null default false BOOL"` Phone string `xorm:"not null default '' VARCHAR(30)"` EmailOther string `xorm:"not null default '' VARCHAR(255)"` - token string `xorm:"not null default '' VARCHAR(255)"` - tokenValidity time.Time `xorm:"default '2000-01-01 00:00:00+01' TIMESTAMPZ"` + Token string `xorm:"not null default '' VARCHAR(255)"` + TokenValidity time.Time `xorm:"default '2000-01-01 00:00:00+01' TIMESTAMPZ"` } diff --git a/src/models/alias.go b/src/models/alias.go index 2fd273a..46d1885 100644 --- a/src/models/alias.go +++ b/src/models/alias.go @@ -4,15 +4,46 @@ import ( "context" "time" + "git.paulbsd.com/paulbsd/vmail/src/api" "git.paulbsd.com/paulbsd/vmail/src/config" ) // GetAliases ... -func GetAliases(ctx *context.Context, config *config.Config) (aliases []Alias, err error) { - err = config.Db.Cols("address").Find(&aliases) +func GetAliases(ctx *context.Context, config *config.Config) (apialiases []*api.Alias, err error) { + var aliases []Alias + err = config.Db.Find(&aliases) + for _, al := range aliases { + apialiases = append(apialiases, al.APIFormat()) + } return } +// GetAlias ... +func GetAlias(ctx *context.Context, config *config.Config, id interface{}) (apialias *api.Alias, err error) { + var alias Alias + has, err := config.Db.Where("username = ?", id).Get(&alias) + if !has || err != nil { + return + } + apialias = alias.APIFormat() + return +} + +// APIFormat returns a JSON formatted object of Admin +func (alias *Alias) APIFormat() *api.Alias { + if alias == nil { + return nil + } + return &api.Alias{ + Address: alias.Address, + Goto: alias.Goto, + Domain: alias.Domain, + Created: alias.Created.Format(timetostring), + Modified: alias.Modified.Format(timetostring), + Active: alias.Active, + } +} + // Alias defines the admin struct type Alias struct { Address string `xorm:"not null pk index(alias_address_active) unique VARCHAR(255)"` diff --git a/src/models/log.go b/src/models/log.go index 960aea7..091962f 100644 --- a/src/models/log.go +++ b/src/models/log.go @@ -1,12 +1,44 @@ package models -import "time" +import ( + "context" + "time" + "git.paulbsd.com/paulbsd/vmail/src/api" + "git.paulbsd.com/paulbsd/vmail/src/config" +) + +// GetLogs return list of Log +func GetLogs(ctx *context.Context, config *config.Config) (apilogs []*api.Log, err error) { + var logs []Log + err = config.Db.Find(&logs) + for _, log := range logs { + apilogs = append(apilogs, log.APIFormat()) + } + return +} + +// APIFormat returns a JSON formatted object of Admin +func (log *Log) APIFormat() *api.Log { + if log == nil { + return nil + } + return &api.Log{ + Timestamp: log.Timestamp.Format(timetostring), + Username: log.Username, + Domain: log.Domain, + Action: log.Action, + Data: log.Data, + ID: log.ID, + } +} + +// Log ... type Log struct { + ID int `xorm:"not null pk autoincr INTEGER"` Timestamp time.Time `xorm:"default now() index(log_domain_timestamp_idx) TIMESTAMPZ"` Username string `xorm:"not null default '' VARCHAR(255)"` Domain string `xorm:"not null default '' index(log_domain_timestamp_idx) VARCHAR(255)"` Action string `xorm:"not null default '' VARCHAR(255)"` Data string `xorm:"not null default '' TEXT"` - Id int `xorm:"not null pk autoincr INTEGER"` } diff --git a/src/models/mailbox.go b/src/models/mailbox.go index eced34b..bf4ef0a 100644 --- a/src/models/mailbox.go +++ b/src/models/mailbox.go @@ -4,15 +4,52 @@ import ( "context" "time" + "git.paulbsd.com/paulbsd/vmail/src/api" "git.paulbsd.com/paulbsd/vmail/src/config" ) // GetMailboxes ... -func GetMailboxes(ctx *context.Context, config *config.Config) (mailboxes []Mailbox, err error) { - err = config.Db.Cols("username").Find(&mailboxes) +func GetMailboxes(ctx *context.Context, config *config.Config) (apimailboxes []*api.Mailbox, err error) { + var mailboxes []Mailbox + err = config.Db.Find(&mailboxes) + for _, ml := range mailboxes { + apimailboxes = append(apimailboxes, ml.APIFormat()) + } return } +// GetMailbox ... +func GetMailbox(ctx *context.Context, config *config.Config, id interface{}) (apimailbox *api.Mailbox, err error) { + var mailbox Mailbox + has, err := config.Db.Where("username = ?", id).Get(&mailbox) + if !has || err != nil { + return + } + apimailbox = mailbox.APIFormat() + return +} + +// APIFormat returns a JSON formatted object of Admin +func (mailbox *Mailbox) APIFormat() *api.Mailbox { + if mailbox == nil { + return nil + } + return &api.Mailbox{ + Username: mailbox.Username, + Name: mailbox.Name, + Maildir: mailbox.Maildir, + Quota: quotaFormat(mailbox.Quota), + Created: mailbox.Created.Format(timetostring), + Modified: mailbox.Modified.Format(timetostring), + Active: mailbox.Active, + Domain: mailbox.Domain, + LocalPart: mailbox.LocalPart, + Phone: mailbox.Phone, + EmailOther: mailbox.EmailOther, + } +} + +// Mailbox ... type Mailbox struct { Username string `xorm:"not null pk unique index(mailbox_username_active) VARCHAR(255)"` Password string `xorm:"not null default '' VARCHAR(255)"` diff --git a/src/models/utils.go b/src/models/utils.go new file mode 100644 index 0000000..fac88d8 --- /dev/null +++ b/src/models/utils.go @@ -0,0 +1,18 @@ +package models + +import ( + "fmt" + + "github.com/alecthomas/units" +) + +const timetostring = "02/01/2006 15:04" + +func quotaFormat(in int64) (out string) { + var converted units.Base2Bytes + converted, err := units.ParseBase2Bytes(fmt.Sprintf("%d%s", in, "B")) + if err != nil { + return + } + return converted.String() +} diff --git a/src/routers/main.go b/src/routers/main.go index 627f951..d75d91a 100644 --- a/src/routers/main.go +++ b/src/routers/main.go @@ -23,11 +23,7 @@ func RunServer(ctx *context.Context, cfg *config.Config) (err error) { })) e.GET("/", func(c echo.Context) (err error) { - return c.HTML(http.StatusOK, "Welcome to Vmail") - }) - e.GET("menu", func(c echo.Context) (err error) { - ret := []string{"admins", "domains", "mailboxes"} - return c.JSON(http.StatusOK, ret) + return c.HTML(http.StatusOK, "Welcome to Vmail API") }) e.POST("/auth", func(c echo.Context) (err error) { return c.String(http.StatusOK, "/auth") @@ -36,6 +32,7 @@ func RunServer(ctx *context.Context, cfg *config.Config) (err error) { return c.JSON(http.StatusOK, "/api") }) + // Admins e.GET("/api/admin", func(c echo.Context) (err error) { admins, err := models.GetAdmins(ctx, cfg) return c.JSON(http.StatusOK, admins) @@ -45,15 +42,7 @@ func RunServer(ctx *context.Context, cfg *config.Config) (err error) { return c.JSON(http.StatusOK, admins) }) - e.GET("/api/mailbox", func(c echo.Context) (err error) { - mailboxes, err := models.GetMailboxes(ctx, cfg) - return c.JSON(http.StatusOK, mailboxes) - }) - e.GET("/api/mailbox/:id", func(c echo.Context) (err error) { - mailboxes, err := models.GetMailboxes(ctx, cfg) - return c.JSON(http.StatusOK, mailboxes) - }) - + // Aliases e.GET("/api/alias", func(c echo.Context) (err error) { aliases, err := models.GetAliases(ctx, cfg) return c.JSON(http.StatusOK, aliases) @@ -63,6 +52,22 @@ func RunServer(ctx *context.Context, cfg *config.Config) (err error) { return c.JSON(http.StatusOK, aliases) }) + // Mailboxes + e.GET("/api/mailbox", func(c echo.Context) (err error) { + mailboxes, err := models.GetMailboxes(ctx, cfg) + return c.JSON(http.StatusOK, mailboxes) + }) + e.GET("/api/mailbox/:id", func(c echo.Context) (err error) { + mailbox, err := models.GetMailbox(ctx, cfg, c.Param("id")) + return c.JSON(http.StatusOK, mailbox) + }) + + // Logs + e.GET("/api/log", func(c echo.Context) (err error) { + logs, err := models.GetLogs(ctx, cfg) + return c.JSON(http.StatusOK, logs) + }) + e.Logger.Fatal(e.Start(fmt.Sprintf(":%d", cfg.Port))) return } diff --git a/vendor/github.com/alecthomas/units/COPYING b/vendor/github.com/alecthomas/units/COPYING new file mode 100644 index 0000000..2993ec0 --- /dev/null +++ b/vendor/github.com/alecthomas/units/COPYING @@ -0,0 +1,19 @@ +Copyright (C) 2014 Alec Thomas + +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. diff --git a/vendor/github.com/alecthomas/units/README.md b/vendor/github.com/alecthomas/units/README.md new file mode 100644 index 0000000..bee884e --- /dev/null +++ b/vendor/github.com/alecthomas/units/README.md @@ -0,0 +1,11 @@ +# Units - Helpful unit multipliers and functions for Go + +The goal of this package is to have functionality similar to the [time](http://golang.org/pkg/time/) package. + +It allows for code like this: + +```go +n, err := ParseBase2Bytes("1KB") +// n == 1024 +n = units.Mebibyte * 512 +``` diff --git a/vendor/github.com/alecthomas/units/bytes.go b/vendor/github.com/alecthomas/units/bytes.go new file mode 100644 index 0000000..61d0ca4 --- /dev/null +++ b/vendor/github.com/alecthomas/units/bytes.go @@ -0,0 +1,85 @@ +package units + +// Base2Bytes is the old non-SI power-of-2 byte scale (1024 bytes in a kilobyte, +// etc.). +type Base2Bytes int64 + +// Base-2 byte units. +const ( + Kibibyte Base2Bytes = 1024 + KiB = Kibibyte + Mebibyte = Kibibyte * 1024 + MiB = Mebibyte + Gibibyte = Mebibyte * 1024 + GiB = Gibibyte + Tebibyte = Gibibyte * 1024 + TiB = Tebibyte + Pebibyte = Tebibyte * 1024 + PiB = Pebibyte + Exbibyte = Pebibyte * 1024 + EiB = Exbibyte +) + +var ( + bytesUnitMap = MakeUnitMap("iB", "B", 1024) + oldBytesUnitMap = MakeUnitMap("B", "B", 1024) +) + +// ParseBase2Bytes supports both iB and B in base-2 multipliers. That is, KB +// and KiB are both 1024. +// However "kB", which is the correct SI spelling of 1000 Bytes, is rejected. +func ParseBase2Bytes(s string) (Base2Bytes, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, oldBytesUnitMap) + } + return Base2Bytes(n), err +} + +func (b Base2Bytes) String() string { + return ToString(int64(b), 1024, "iB", "B") +} + +var ( + metricBytesUnitMap = MakeUnitMap("B", "B", 1000) +) + +// MetricBytes are SI byte units (1000 bytes in a kilobyte). +type MetricBytes SI + +// SI base-10 byte units. +const ( + Kilobyte MetricBytes = 1000 + KB = Kilobyte + Megabyte = Kilobyte * 1000 + MB = Megabyte + Gigabyte = Megabyte * 1000 + GB = Gigabyte + Terabyte = Gigabyte * 1000 + TB = Terabyte + Petabyte = Terabyte * 1000 + PB = Petabyte + Exabyte = Petabyte * 1000 + EB = Exabyte +) + +// ParseMetricBytes parses base-10 metric byte units. That is, KB is 1000 bytes. +func ParseMetricBytes(s string) (MetricBytes, error) { + n, err := ParseUnit(s, metricBytesUnitMap) + return MetricBytes(n), err +} + +// TODO: represents 1000B as uppercase "KB", while SI standard requires "kB". +func (m MetricBytes) String() string { + return ToString(int64(m), 1000, "B", "B") +} + +// ParseStrictBytes supports both iB and B suffixes for base 2 and metric, +// respectively. That is, KiB represents 1024 and kB, KB represent 1000. +func ParseStrictBytes(s string) (int64, error) { + n, err := ParseUnit(s, bytesUnitMap) + if err != nil { + n, err = ParseUnit(s, metricBytesUnitMap) + } + return int64(n), err +} diff --git a/vendor/github.com/alecthomas/units/doc.go b/vendor/github.com/alecthomas/units/doc.go new file mode 100644 index 0000000..156ae38 --- /dev/null +++ b/vendor/github.com/alecthomas/units/doc.go @@ -0,0 +1,13 @@ +// Package units provides helpful unit multipliers and functions for Go. +// +// The goal of this package is to have functionality similar to the time [1] package. +// +// +// [1] http://golang.org/pkg/time/ +// +// It allows for code like this: +// +// n, err := ParseBase2Bytes("1KB") +// // n == 1024 +// n = units.Mebibyte * 512 +package units diff --git a/vendor/github.com/alecthomas/units/go.mod b/vendor/github.com/alecthomas/units/go.mod new file mode 100644 index 0000000..c7fb91f --- /dev/null +++ b/vendor/github.com/alecthomas/units/go.mod @@ -0,0 +1,3 @@ +module github.com/alecthomas/units + +require github.com/stretchr/testify v1.4.0 diff --git a/vendor/github.com/alecthomas/units/go.sum b/vendor/github.com/alecthomas/units/go.sum new file mode 100644 index 0000000..8fdee58 --- /dev/null +++ b/vendor/github.com/alecthomas/units/go.sum @@ -0,0 +1,11 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/alecthomas/units/si.go b/vendor/github.com/alecthomas/units/si.go new file mode 100644 index 0000000..99b2fa4 --- /dev/null +++ b/vendor/github.com/alecthomas/units/si.go @@ -0,0 +1,50 @@ +package units + +// SI units. +type SI int64 + +// SI unit multiples. +const ( + Kilo SI = 1000 + Mega = Kilo * 1000 + Giga = Mega * 1000 + Tera = Giga * 1000 + Peta = Tera * 1000 + Exa = Peta * 1000 +) + +func MakeUnitMap(suffix, shortSuffix string, scale int64) map[string]float64 { + res := map[string]float64{ + shortSuffix: 1, + // see below for "k" / "K" + "M" + suffix: float64(scale * scale), + "G" + suffix: float64(scale * scale * scale), + "T" + suffix: float64(scale * scale * scale * scale), + "P" + suffix: float64(scale * scale * scale * scale * scale), + "E" + suffix: float64(scale * scale * scale * scale * scale * scale), + } + + // Standard SI prefixes use lowercase "k" for kilo = 1000. + // For compatibility, and to be fool-proof, we accept both "k" and "K" in metric mode. + // + // However, official binary prefixes are always capitalized - "KiB" - + // and we specifically never parse "kB" as 1024B because: + // + // (1) people pedantic enough to use lowercase according to SI unlikely to abuse "k" to mean 1024 :-) + // + // (2) Use of capital K for 1024 was an informal tradition predating IEC prefixes: + // "The binary meaning of the kilobyte for 1024 bytes typically uses the symbol KB, with an + // uppercase letter K." + // -- https://en.wikipedia.org/wiki/Kilobyte#Base_2_(1024_bytes) + // "Capitalization of the letter K became the de facto standard for binary notation, although this + // could not be extended to higher powers, and use of the lowercase k did persist.[13][14][15]" + // -- https://en.wikipedia.org/wiki/Binary_prefix#History + // See also the extensive https://en.wikipedia.org/wiki/Timeline_of_binary_prefixes. + if scale == 1024 { + res["K"+suffix] = float64(scale) + } else { + res["k"+suffix] = float64(scale) + res["K"+suffix] = float64(scale) + } + return res +} diff --git a/vendor/github.com/alecthomas/units/util.go b/vendor/github.com/alecthomas/units/util.go new file mode 100644 index 0000000..6527e92 --- /dev/null +++ b/vendor/github.com/alecthomas/units/util.go @@ -0,0 +1,138 @@ +package units + +import ( + "errors" + "fmt" + "strings" +) + +var ( + siUnits = []string{"", "K", "M", "G", "T", "P", "E"} +) + +func ToString(n int64, scale int64, suffix, baseSuffix string) string { + mn := len(siUnits) + out := make([]string, mn) + for i, m := range siUnits { + if n%scale != 0 || i == 0 && n == 0 { + s := suffix + if i == 0 { + s = baseSuffix + } + out[mn-1-i] = fmt.Sprintf("%d%s%s", n%scale, m, s) + } + n /= scale + if n == 0 { + break + } + } + return strings.Join(out, "") +} + +// Below code ripped straight from http://golang.org/src/pkg/time/format.go?s=33392:33438#L1123 +var errLeadingInt = errors.New("units: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x int64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x >= (1<<63-10)/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + int64(c) - '0' + } + return x, s[i:], nil +} + +func ParseUnit(s string, unitMap map[string]float64) (int64, error) { + // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+ + orig := s + f := float64(0) + neg := false + + // Consume [-+]? + if s != "" { + c := s[0] + if c == '-' || c == '+' { + neg = c == '-' + s = s[1:] + } + } + // Special case: if all that is left is "0", this is zero. + if s == "0" { + return 0, nil + } + if s == "" { + return 0, errors.New("units: invalid " + orig) + } + for s != "" { + g := float64(0) // this element of the sequence + + var x int64 + var err error + + // The next character must be [0-9.] + if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) { + return 0, errors.New("units: invalid " + orig) + } + // Consume [0-9]* + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + g = float64(x) + pre := pl != len(s) // whether we consumed anything before a period + + // Consume (\.[0-9]*)? + post := false + if s != "" && s[0] == '.' { + s = s[1:] + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("units: invalid " + orig) + } + scale := 1.0 + for n := pl - len(s); n > 0; n-- { + scale *= 10 + } + g += float64(x) / scale + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("units: invalid " + orig) + } + + // Consume unit. + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c == '.' || ('0' <= c && c <= '9') { + break + } + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("units: unknown unit " + u + " in " + orig) + } + + f += g * unit + } + + if neg { + f = -f + } + if f < float64(-1<<63) || f > float64(1<<63-1) { + return 0, errors.New("units: overflow parsing unit") + } + return int64(f), nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index a28ff39..0928a1d 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,6 @@ +# github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d +## explicit +github.com/alecthomas/units # github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go # github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db