updated vmail

This commit is contained in:
Paul 2020-07-11 15:57:22 +02:00
parent 3e8fc9f73e
commit 688846b6c9
23 changed files with 577 additions and 37 deletions

1
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

12
src/api/admin.go Normal file
View File

@ -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"`
}

11
src/api/alias.go Normal file
View File

@ -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"`
}

11
src/api/log.go Normal file
View File

@ -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"`
}

16
src/api/mailbox.go Normal file
View File

@ -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"`
}

View File

@ -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
}

View File

@ -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)

View File

@ -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"`
}

View File

@ -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)"`

View File

@ -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"`
}

View File

@ -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)"`

18
src/models/utils.go Normal file
View File

@ -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()
}

View File

@ -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
}

19
vendor/github.com/alecthomas/units/COPYING generated vendored Normal file
View File

@ -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.

11
vendor/github.com/alecthomas/units/README.md generated vendored Normal file
View File

@ -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
```

85
vendor/github.com/alecthomas/units/bytes.go generated vendored Normal file
View File

@ -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
}

13
vendor/github.com/alecthomas/units/doc.go generated vendored Normal file
View File

@ -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

3
vendor/github.com/alecthomas/units/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/alecthomas/units
require github.com/stretchr/testify v1.4.0

11
vendor/github.com/alecthomas/units/go.sum generated vendored Normal file
View File

@ -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=

50
vendor/github.com/alecthomas/units/si.go generated vendored Normal file
View File

@ -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
}

138
vendor/github.com/alecthomas/units/util.go generated vendored Normal file
View File

@ -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
}

3
vendor/modules.txt vendored
View File

@ -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