⬆️ update dependencies, ✨ implement user store
This commit is contained in:
parent
d2c0ebb043
commit
129df9d7f2
73
config.go
73
config.go
|
@ -1,4 +1,4 @@
|
|||
// Copyright (c) 2019, Andreas Schneider
|
||||
// Copyright (c) 2020, Andreas Schneider
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
|
@ -25,71 +25,10 @@
|
|||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-yaml/yaml"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ListenAddress string `yaml:"listenAddress"`
|
||||
BaseDirectory string `yaml:"baseDirectory"`
|
||||
Shares map[string]Share `yaml:"shares"`
|
||||
type config struct {
|
||||
ListenAddress string `name:"listen-address" default:":3000" help:"Address to listen on for HTTP requests."`
|
||||
DataDirectory string `name:"data-dir" default:"data" help:"Directory to store all files in."`
|
||||
Db string `name:"db" default:"ShareDAV.db" help:"Database file to use."`
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
Directory string `yaml:"directory"`
|
||||
Users map[string]ShareUser `yaml:"users"`
|
||||
}
|
||||
|
||||
type ShareUser struct {
|
||||
Role string `yaml:"role"`
|
||||
Logins map[string]string `yaml:"logins"`
|
||||
}
|
||||
|
||||
func LoadConfig(filename string) Config {
|
||||
c := Config{}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer f.Close()
|
||||
if err := yaml.NewDecoder(f).Decode(&c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) ValidateDAVUser(username, password string) (valid bool, directory string) {
|
||||
/*parts := strings.SplitN(username, "@", 2)
|
||||
if len(parts) != 2 {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
l, ok := c.Logins[parts[0]]
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
cred, ok := l.Credentials[parts[1]]
|
||||
if !ok {
|
||||
return false, ""
|
||||
}
|
||||
|
||||
share, ok := c.Shares[cred.Share]
|
||||
if !ok {
|
||||
log.Printf("Invalid share for user %s: %s\n", username, cred.Share)
|
||||
return false, ""
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(cred.Password), []byte(password))
|
||||
if err == bcrypt.ErrMismatchedHashAndPassword {
|
||||
return false, ""
|
||||
} else if err != nil {
|
||||
log.Printf("Cannot validate password for user %s: %s\n", username, err.Error())
|
||||
return false, ""
|
||||
} else {
|
||||
return true, share.Directory
|
||||
}*/
|
||||
return false, ""
|
||||
}
|
||||
var Config config
|
||||
|
|
18
example.yaml
18
example.yaml
|
@ -1,18 +0,0 @@
|
|||
listenAddress: :3000
|
||||
baseDirectory: data
|
||||
shares:
|
||||
test1:
|
||||
directory: share1
|
||||
users:
|
||||
User1:
|
||||
role: admin
|
||||
logins:
|
||||
dev1: $2a$10$5AuehKad7TxDqW2HXdYaZ.ipFajhl7ULyTR3DLCquTA3B/dxHujIq #test
|
||||
test2:
|
||||
directory: share2
|
||||
users:
|
||||
User1:
|
||||
role: reader
|
||||
users:
|
||||
User1:
|
||||
|
19
go.mod
19
go.mod
|
@ -1,18 +1,19 @@
|
|||
module ShareDAV
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/alecthomas/kong v0.2.11
|
||||
github.com/go-chi/chi v4.0.2+incompatible // indirect
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/sethvargo/go-password v0.1.2
|
||||
github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0 // indirect
|
||||
github.com/tidwall/buntdb v1.1.0
|
||||
github.com/tidwall/gjson v1.3.2 // indirect
|
||||
github.com/satori/go.uuid v1.2.0
|
||||
github.com/sethvargo/go-password v0.1.2 // indirect
|
||||
github.com/tidwall/buntdb v1.1.2
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb // indirect
|
||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e // indirect
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
)
|
||||
|
|
71
go.sum
71
go.sum
|
@ -1,20 +1,78 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM=
|
||||
github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/kong v0.2.11 h1:RKeJXXWfg9N47RYfMm0+igkxBCTF4bzbneAxaqid0c4=
|
||||
github.com/alecthomas/kong v0.2.11/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x8=
|
||||
github.com/dgraph-io/badger/v2 v2.2007.2 h1:EjjK0KqwaFMlPin1ajhP943VPENHJdEz1KLIegjaI3k=
|
||||
github.com/dgraph-io/badger/v2 v2.2007.2/go.mod h1:26P/7fbL4kUZVEVKLAKXkBXKOydDmM2p1e+NhhnBCAE=
|
||||
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA=
|
||||
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
|
||||
github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/sethvargo/go-password v0.1.2 h1:fhBF4thiPVKEZ7R6+CX46GWJiPyCyXshbeqZ7lqEeYo=
|
||||
github.com/sethvargo/go-password v0.1.2/go.mod h1:qKHfdSjT26DpHQWHWWR5+X4BI45jT31dg6j4RI2TEb0=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0 h1:QnyrPZZvPmR0AtJCxxfCtI1qN+fYpKTKJ/5opWmZ34k=
|
||||
github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/buntdb v1.1.0 h1:H6LzK59KiNjf1nHVPFrYj4Qnl8d8YLBsYamdL8N+Bao=
|
||||
github.com/tidwall/buntdb v1.1.0/go.mod h1:Y39xhcDW10WlyYXeLgGftXVbjtM0QP+/kpz8xl9cbzE=
|
||||
github.com/tidwall/buntdb v1.1.2 h1:noCrqQXL9EKMtcdwJcmuVKSEjqu1ua99RHHgbLTEHRo=
|
||||
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
|
||||
github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI=
|
||||
github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw=
|
||||
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
|
@ -25,12 +83,25 @@ github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2K
|
|||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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=
|
||||
|
|
258
store.go
258
store.go
|
@ -1,106 +1,166 @@
|
|||
// Copyright (c) 2020, Andreas Schneider
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
// * Neither the name of the <organization> nor the
|
||||
// names of its contributors may be used to endorse or promote products
|
||||
// derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
|
||||
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
uuid "github.com/satori/go.uuid"
|
||||
"github.com/tidwall/buntdb"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserStore interface {
|
||||
AddUser(username string, role GlobalRole, password string) (err error)
|
||||
AddUser(user User) (err error)
|
||||
GetUser(username string) (user User, err error)
|
||||
GetLogin(username string, password string) (appName string, err error)
|
||||
SetLogin(username string, appName string, password string) (err error)
|
||||
RemoveLogin(username string, appName string) (err error)
|
||||
GetUsers() ([]User, error)
|
||||
Update(user User) error
|
||||
RemoveUser(username string) (err error)
|
||||
}
|
||||
|
||||
type ShareStore interface {
|
||||
|
||||
}
|
||||
|
||||
var _ UserStore = &DBStore{}
|
||||
|
||||
type GlobalRole string
|
||||
|
||||
const (
|
||||
GlobalRoleUser GlobalRole = "user"
|
||||
GlobalRoleManager GlobalRole = "manager"
|
||||
GlobalRoleUser GlobalRole = "user"
|
||||
GlobalRoleAdmin GlobalRole = "admin"
|
||||
)
|
||||
|
||||
type ShareRole string
|
||||
|
||||
const (
|
||||
ShareRoleReader ShareRole = "reader"
|
||||
ShareRoleWriter ShareRole = "writer"
|
||||
ShareRoleAdmin ShareRole = "admin"
|
||||
ShareRoleAdmin ShareRole = "admin"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
Role GlobalRole
|
||||
Apps []App
|
||||
}
|
||||
|
||||
type App struct {
|
||||
Name string
|
||||
CreatedAt time.Time
|
||||
Password string
|
||||
Role GlobalRole
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
UUID uuid.UUID
|
||||
Directory string
|
||||
Users []ShareUser
|
||||
Users []ShareUser
|
||||
}
|
||||
|
||||
type ShareUser struct {
|
||||
Username string
|
||||
Role ShareRole
|
||||
Role ShareRole
|
||||
Logins []Login
|
||||
}
|
||||
|
||||
type Login struct {
|
||||
Loginname string
|
||||
Password string
|
||||
}
|
||||
|
||||
const usersPrefix = "users:"
|
||||
|
||||
type DBStore struct {
|
||||
db *buntdb.DB
|
||||
}
|
||||
|
||||
func (u User) key() string {
|
||||
return usersPrefix + u.Username
|
||||
}
|
||||
|
||||
func (u User) prefix() string {
|
||||
return fmt.Sprintf("user:%s", u.Username)
|
||||
}
|
||||
|
||||
func (u User) roleKey() string {
|
||||
return u.prefix() + ":role"
|
||||
}
|
||||
|
||||
func (u User) passwordKey() string {
|
||||
return u.prefix() + ":password"
|
||||
}
|
||||
|
||||
func NewDBStore(filename string) (*DBStore, error) {
|
||||
db, err := buntdb.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := db.CreateIndex("login", "user:*:login*", buntdb.IndexBinary); err != nil && err != buntdb.ErrIndexExists {
|
||||
return nil, errors.New("cannot create login index: " + err.Error())
|
||||
}
|
||||
|
||||
return &DBStore{db}, nil
|
||||
}
|
||||
|
||||
func (store *DBStore) Close() error {
|
||||
return store.db.Close()
|
||||
}
|
||||
|
||||
var ErrExists = errors.New("key already exists")
|
||||
var ErrUserNotFound = errors.New("user not found")
|
||||
var ErrInvalidUsername = errors.New("invalid username")
|
||||
|
||||
func (store *DBStore) AddUser(username string, role GlobalRole, password string) (err error) {
|
||||
if strings.Contains(username, ":") {
|
||||
func (store *DBStore) setUserValues(tx *buntdb.Tx, user User) (exists bool, err error) {
|
||||
pwString, err := buildPassword(user.Username, user.Password)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot hash password: %w", err)
|
||||
}
|
||||
|
||||
if _, replaced, err := tx.Set(user.roleKey(), string(user.Role), nil); err != nil {
|
||||
return false, err
|
||||
} else if replaced {
|
||||
exists = true
|
||||
}
|
||||
if _, replaced, err := tx.Set(user.passwordKey(), pwString, nil); err != nil {
|
||||
return false, err
|
||||
} else if replaced {
|
||||
exists = true
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
func (store *DBStore) AddUser(user User) (err error) {
|
||||
if strings.Contains(user.Username, ":") {
|
||||
return ErrInvalidUsername
|
||||
}
|
||||
pwString, err := buildPassword(username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = store.db.Update(func(tx *buntdb.Tx) error {
|
||||
if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:role", username), string(role), nil); err != nil {
|
||||
if _, exists, err := tx.Set(user.key(), "", nil); err != nil {
|
||||
return err
|
||||
} else if replaced {
|
||||
} else if exists {
|
||||
return ErrExists
|
||||
}
|
||||
if _, replaced, err := tx.Set(fmt.Sprintf("user:%s:login", username), pwString, nil); err != nil {
|
||||
return err
|
||||
} else if replaced {
|
||||
return ErrExists
|
||||
}
|
||||
// just in case ...
|
||||
if _, err := tx.Delete(fmt.Sprintf("user:%s:login:*", username)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if exists, err := store.setUserValues(tx, user); err != nil {
|
||||
return err
|
||||
} else if exists {
|
||||
return ErrExists
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
|
@ -114,33 +174,20 @@ func (store *DBStore) GetUser(username string) (user User, err error) {
|
|||
}
|
||||
user.Username = username
|
||||
if err := store.db.View(func(tx *buntdb.Tx) error {
|
||||
if val, err := tx.Get(fmt.Sprintf("user:%s:role", username)); err != nil && err != buntdb.ErrNotFound {
|
||||
if val, err := tx.Get(user.roleKey()); err != nil && err != buntdb.ErrNotFound {
|
||||
return err
|
||||
} else if err == buntdb.ErrNotFound {
|
||||
return ErrUserNotFound
|
||||
} else {
|
||||
user.Role = GlobalRole(val)
|
||||
}
|
||||
|
||||
loginPrefix := fmt.Sprintf("user:%s:login:", username)
|
||||
if err := tx.AscendKeys(loginPrefix + "*", func(key, value string) bool {
|
||||
app := App{Name: strings.TrimPrefix(key, loginPrefix)}
|
||||
creationTimeValue, err := tx.Get(fmt.Sprintf("user:%s:app:%s:created_at", username, app.Name))
|
||||
if err != nil {
|
||||
// invalid app ... move along
|
||||
return true
|
||||
}
|
||||
creationTime, err := time.Parse(time.RFC3339Nano, creationTimeValue)
|
||||
if err != nil {
|
||||
// invalid creation time ... invalid app ... move along.
|
||||
return true
|
||||
}
|
||||
app.CreatedAt = creationTime
|
||||
user.Apps = append(user.Apps, app)
|
||||
|
||||
return true
|
||||
}); err != nil {
|
||||
if val, err := tx.Get(user.passwordKey()); err != nil && err != buntdb.ErrNotFound {
|
||||
return err
|
||||
} else if err == buntdb.ErrNotFound {
|
||||
return ErrUserNotFound
|
||||
} else {
|
||||
user.Password = val
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
return user, err
|
||||
|
@ -148,56 +195,39 @@ func (store *DBStore) GetUser(username string) (user User, err error) {
|
|||
return user, nil
|
||||
}
|
||||
|
||||
func (store *DBStore) GetLogin(username string, password string) (appName string, err error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (store *DBStore) SetLogin(username string, appName string, password string) (err error) {
|
||||
if strings.Contains(username, ":") {
|
||||
return ErrInvalidUsername
|
||||
}
|
||||
|
||||
pwString, err := buildPassword(username, password)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return store.db.Update(func(tx *buntdb.Tx) error {
|
||||
mainLoginKey := fmt.Sprintf("user:%s:login", username)
|
||||
// check if we actually know the user. the current login is a good indicator
|
||||
if _, err := tx.Get(mainLoginKey); err != nil {
|
||||
if err == buntdb.ErrNotFound {
|
||||
return ErrUserNotFound
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
func (store *DBStore) GetUsers() (users []User, err error) {
|
||||
err = store.db.View(func(tx *buntdb.Tx) error {
|
||||
if err := tx.AscendKeys(usersPrefix+"*", func(key, value string) bool {
|
||||
var user User
|
||||
user.Username = strings.TrimPrefix(key, usersPrefix)
|
||||
users = append(users, user)
|
||||
return true
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
saneAppName := strings.TrimSpace(appName)
|
||||
|
||||
// if no appname is given, we apparently have to update the password of the whole account.
|
||||
if saneAppName == "" {
|
||||
if _, _, err := tx.Set(mainLoginKey, pwString, nil); err != nil {
|
||||
for i := range users {
|
||||
if roleString, err := tx.Get(users[i].roleKey()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, replaced, err := tx.Set(fmt.Sprintf("%s:%s", mainLoginKey, saneAppName), pwString, nil); err != nil {
|
||||
return err
|
||||
} else if !replaced {
|
||||
// o,h so this is a new key. set the timestamp
|
||||
creationTime := time.Now().Format(time.RFC3339Nano)
|
||||
if _, _, err := tx.Set(fmt.Sprintf("user:%s:app:%s:created_at", username, saneAppName), creationTime, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
users[i].Role = GlobalRole(roleString)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (store *DBStore) RemoveLogin(username string, appName string) (err error) {
|
||||
panic("implement me")
|
||||
func (store *DBStore) Update(user User) error {
|
||||
if strings.Contains(user.Username, ":") {
|
||||
return ErrInvalidUsername
|
||||
}
|
||||
return store.db.Update(func(tx *buntdb.Tx) error {
|
||||
_, err := store.setUserValues(tx, user)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (store *DBStore) RemoveUser(username string) (err error) {
|
||||
|
@ -205,9 +235,29 @@ func (store *DBStore) RemoveUser(username string) (err error) {
|
|||
return ErrInvalidUsername
|
||||
}
|
||||
return store.db.Update(func(tx *buntdb.Tx) error {
|
||||
// TODO won't work ... collect keys first, then delete them one by one
|
||||
_, err := tx.Delete(fmt.Sprintf("user:%s:*", username))
|
||||
return err
|
||||
user := User{Username: username}
|
||||
|
||||
// Delete the main key first. This is a good indicator if the user generally exists.
|
||||
if _, err := tx.Delete(user.key()); err == buntdb.ErrNotFound {
|
||||
return ErrUserNotFound
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Now get all attributes and delete them as well. One by one.
|
||||
var keys []string
|
||||
if err := tx.AscendKeys(user.prefix()+":*", func(key, value string) bool {
|
||||
keys = append(keys, key)
|
||||
return true
|
||||
}); err != nil {
|
||||
return fmt.Errorf("cannot iterate keys: %w", err)
|
||||
}
|
||||
for _, key := range keys {
|
||||
if _, err := tx.Delete(key); err != nil {
|
||||
return fmt.Errorf("cannot remove key: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -217,4 +267,4 @@ func buildPassword(username string, password string) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", username, hash), nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/tidwall/buntdb"
|
||||
)
|
||||
|
||||
func TestStoreUserHandling(t *testing.T) {
|
||||
store, err := NewDBStore(":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("cannot create store: %v", err)
|
||||
}
|
||||
defer store.Close()
|
||||
|
||||
t.Run("store should be empty initially", func(t *testing.T) {
|
||||
users, err := store.GetUsers()
|
||||
if err != nil {
|
||||
t.Errorf("no error should have been returned: %v", err)
|
||||
}
|
||||
if len(users) != 0 {
|
||||
t.Errorf("there should be no users")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("user that doesn't exist should return error", func(t *testing.T) {
|
||||
_, err := store.GetUser("someuser")
|
||||
if err != ErrUserNotFound {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("adding users should work", func(t *testing.T) {
|
||||
if err := store.AddUser(User{"myuser", "mypass", GlobalRoleUser}); err != nil {
|
||||
t.Errorf("cannot add user: %v", err)
|
||||
}
|
||||
|
||||
t.Run("retrieving that single user should work", func(t *testing.T) {
|
||||
user, err := store.GetUser("myuser")
|
||||
if err != nil {
|
||||
t.Errorf("cannot retrieve user: %v", err)
|
||||
}
|
||||
if user.Username != "myuser" {
|
||||
t.Errorf("retrieved user contains unexpected username")
|
||||
}
|
||||
if user.Role != GlobalRoleUser {
|
||||
t.Errorf("retrieved user contains unexpected role")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("retrieving multiple users should work", func(t *testing.T) {
|
||||
users, err := store.GetUsers()
|
||||
if err != nil {
|
||||
t.Errorf("cannot retrieve user list: %v", err)
|
||||
}
|
||||
if len(users) != 1 {
|
||||
t.Errorf("there should be only one user")
|
||||
}
|
||||
if users[0].Username != "myuser" {
|
||||
t.Errorf("retrieved user contains unexpected username")
|
||||
}
|
||||
if users[0].Role != GlobalRoleUser {
|
||||
t.Errorf("retrieved user contains unexpected role")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("deleting that user should work", func(t *testing.T) {
|
||||
err := store.RemoveUser("myuser")
|
||||
if err != nil {
|
||||
t.Errorf("cannot delete user: %v", err)
|
||||
}
|
||||
|
||||
t.Run("user should no longer be found", func(t *testing.T) {
|
||||
_, err := store.GetUser("myuser")
|
||||
if err != ErrUserNotFound {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("user should no longer be listed", func(t *testing.T) {
|
||||
users, err := store.GetUsers()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if len(users) != 0 {
|
||||
t.Errorf("there should be no users")
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("database should be empty now", func(t *testing.T) {
|
||||
// checks that we properly deleted all keys
|
||||
if err := store.db.View(func(tx *buntdb.Tx) error {
|
||||
return tx.Ascend("", func(key, value string) bool {
|
||||
t.Errorf("there should be no keys left")
|
||||
return false
|
||||
})
|
||||
}); err != nil {
|
||||
t.Errorf("iterating keys failed: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
bin
|
|
@ -0,0 +1,42 @@
|
|||
run:
|
||||
tests: true
|
||||
|
||||
output:
|
||||
print-issued-lines: false
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- maligned
|
||||
- lll
|
||||
- gochecknoglobals
|
||||
- wsl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gomnd
|
||||
- goprintffuncname
|
||||
|
||||
linters-settings:
|
||||
govet:
|
||||
check-shadowing: true
|
||||
dupl:
|
||||
threshold: 100
|
||||
goconst:
|
||||
min-len: 5
|
||||
min-occurrences: 3
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
|
||||
issues:
|
||||
max-per-linter: 0
|
||||
max-same: 0
|
||||
exclude-use-default: false
|
||||
exclude:
|
||||
- '^(G104|G204):'
|
||||
# Very commonly not checked.
|
||||
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON) should have comment or be unexported'
|
||||
- 'composite literal uses unkeyed fields'
|
||||
- 'bad syntax for struct tag key'
|
||||
- 'bad syntax for struct tag pair'
|
||||
- 'result .* \(error\) is always nil'
|
|
@ -1,16 +1,4 @@
|
|||
The following files were ported to Go from C files of libyaml, and thus
|
||||
are still covered by their original copyright and license:
|
||||
|
||||
apic.go
|
||||
emitterc.go
|
||||
parserc.go
|
||||
readerc.go
|
||||
scannerc.go
|
||||
writerc.go
|
||||
yamlh.go
|
||||
yamlprivateh.go
|
||||
|
||||
Copyright (c) 2006 Kirill Simonov
|
||||
Copyright (C) 2018 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
|
|
@ -0,0 +1,536 @@
|
|||
<!-- markdownlint-disable MD013 MD033 -->
|
||||
<p align="center"><img width="90%" src="kong.png" /></p>
|
||||
|
||||
# Kong is a command-line parser for Go
|
||||
[![](https://godoc.org/github.com/alecthomas/kong?status.svg)](http://godoc.org/github.com/alecthomas/kong) [![CircleCI](https://img.shields.io/circleci/project/github/alecthomas/kong.svg)](https://circleci.com/gh/alecthomas/kong) [![Go Report Card](https://goreportcard.com/badge/github.com/alecthomas/kong)](https://goreportcard.com/report/github.com/alecthomas/kong) [![Slack chat](https://img.shields.io/static/v1?logo=slack&style=flat&label=slack&color=green&message=gophers)](https://gophers.slack.com/messages/CN9DS8YF3)
|
||||
|
||||
[TOC levels=2-3 numbered]: # "#### Table of Contents"
|
||||
|
||||
#### Table of Contents
|
||||
1. [Introduction](#introduction)
|
||||
1. [Help](#help)
|
||||
1. [Command handling](#command-handling)
|
||||
1. [Switch on the command string](#switch-on-the-command-string)
|
||||
1. [Attach a `Run(...) error` method to each command](#attach-a-run-error-method-to-each-command)
|
||||
1. [Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option)
|
||||
1. [Flags](#flags)
|
||||
1. [Commands and sub-commands](#commands-and-sub-commands)
|
||||
1. [Branching positional arguments](#branching-positional-arguments)
|
||||
1. [Terminating positional arguments](#terminating-positional-arguments)
|
||||
1. [Slices](#slices)
|
||||
1. [Maps](#maps)
|
||||
1. [Custom named decoders](#custom-named-decoders)
|
||||
1. [Custom decoders (mappers)](#custom-decoders-mappers)
|
||||
1. [Supported tags](#supported-tags)
|
||||
1. [Variable interpolation](#variable-interpolation)
|
||||
1. [Modifying Kong's behaviour](#modifying-kongs-behaviour)
|
||||
1. [`Name(help)` and `Description(help)` - set the application name description](#namehelp-and-descriptionhelp---set-the-application-name-description)
|
||||
1. [`Configuration(loader, paths...)` - load defaults from configuration files](#configurationloader-paths---load-defaults-from-configuration-files)
|
||||
1. [`Resolver(...)` - support for default values from external sources](#resolver---support-for-default-values-from-external-sources)
|
||||
1. [`*Mapper(...)` - customising how the command-line is mapped to Go values](#mapper---customising-how-the-command-line-is-mapped-to-go-values)
|
||||
1. [`ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help](#configurehelphelpoptions-and-helphelpfunc---customising-help)
|
||||
1. [`Bind(...)` - bind values for callback hooks and Run() methods](#bind---bind-values-for-callback-hooks-and-run-methods)
|
||||
1. [Other options](#other-options)
|
||||
|
||||
|
||||
## Introduction
|
||||
|
||||
Kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
|
||||
|
||||
To achieve that, command-lines are expressed as Go types, with the structure and tags directing how the command line is mapped onto the struct.
|
||||
|
||||
For example, the following command-line:
|
||||
|
||||
shell rm [-f] [-r] <paths> ...
|
||||
shell ls [<paths> ...]
|
||||
|
||||
Can be represented by the following command-line structure:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/alecthomas/kong"
|
||||
|
||||
var CLI struct {
|
||||
Rm struct {
|
||||
Force bool `help:"Force removal."`
|
||||
Recursive bool `help:"Recursively remove files."`
|
||||
|
||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||
} `cmd help:"Remove files."`
|
||||
|
||||
Ls struct {
|
||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||
} `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := kong.Parse(&CLI)
|
||||
switch ctx.Command() {
|
||||
case "rm <path>":
|
||||
case "ls":
|
||||
default:
|
||||
panic(ctx.Command())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Help
|
||||
|
||||
Help is automatically generated. With no other arguments provided, help will display a full summary of all available commands.
|
||||
|
||||
eg.
|
||||
|
||||
$ shell --help
|
||||
usage: shell <command>
|
||||
|
||||
A shell-like example app.
|
||||
|
||||
Flags:
|
||||
--help Show context-sensitive help.
|
||||
--debug Debug mode.
|
||||
|
||||
Commands:
|
||||
rm <path> ...
|
||||
Remove files.
|
||||
|
||||
ls [<path> ...]
|
||||
List paths.
|
||||
|
||||
If a command is provided, the help will show full detail on the command including all available flags.
|
||||
|
||||
eg.
|
||||
|
||||
$ shell --help rm
|
||||
usage: shell rm <paths> ...
|
||||
|
||||
Remove files.
|
||||
|
||||
Arguments:
|
||||
<paths> ... Paths to remove.
|
||||
|
||||
Flags:
|
||||
--debug Debug mode.
|
||||
|
||||
-f, --force Force removal.
|
||||
-r, --recursive Recursively remove files.
|
||||
|
||||
For flags with associated environment variables, the variable `${env}` can be
|
||||
interpolated into the help string. In the absence of this variable in the help,
|
||||
|
||||
|
||||
## Command handling
|
||||
|
||||
There are two ways to handle commands in Kong.
|
||||
|
||||
### Switch on the command string
|
||||
|
||||
When you call `kong.Parse()` it will return a unique string representation of the command. Each command branch in the hierarchy will be a bare word and each branching argument or required positional argument will be the name surrounded by angle brackets. Here's an example:
|
||||
|
||||
There's an example of this pattern [here](https://github.com/alecthomas/kong/blob/master/_examples/shell/main.go).
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "github.com/alecthomas/kong"
|
||||
|
||||
var CLI struct {
|
||||
Rm struct {
|
||||
Force bool `help:"Force removal."`
|
||||
Recursive bool `help:"Recursively remove files."`
|
||||
|
||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||
} `cmd help:"Remove files."`
|
||||
|
||||
Ls struct {
|
||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||
} `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := kong.Parse(&CLI)
|
||||
switch ctx.Command() {
|
||||
case "rm <path>":
|
||||
case "ls":
|
||||
default:
|
||||
panic(ctx.Command())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This has the advantage that it is convenient, but the downside that if you modify your CLI structure, the strings may change. This can be fragile.
|
||||
|
||||
### Attach a `Run(...) error` method to each command
|
||||
|
||||
A more robust approach is to break each command out into their own structs:
|
||||
|
||||
1. Break leaf commands out into separate structs.
|
||||
2. Attach a `Run(...) error` method to all leaf commands.
|
||||
3. Call `kong.Kong.Parse()` to obtain a `kong.Context`.
|
||||
4. Call `kong.Context.Run(bindings...)` to call the selected parsed command.
|
||||
|
||||
Once a command node is selected by Kong it will search from that node back to the root. Each
|
||||
encountered command node with a `Run(...) error` will be called in reverse order. This allows
|
||||
sub-trees to be re-used fairly conveniently.
|
||||
|
||||
In addition to values bound with the `kong.Bind(...)` option, any values
|
||||
passed through to `kong.Context.Run(...)` are also bindable to the target's
|
||||
`Run()` arguments.
|
||||
|
||||
Finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
||||
|
||||
There's a full example emulating part of the Docker CLI [here](https://github.com/alecthomas/kong/tree/master/_examples/docker).
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
type Context struct {
|
||||
Debug bool
|
||||
}
|
||||
|
||||
type RmCmd struct {
|
||||
Force bool `help:"Force removal."`
|
||||
Recursive bool `help:"Recursively remove files."`
|
||||
|
||||
Paths []string `arg name:"path" help:"Paths to remove." type:"path"`
|
||||
}
|
||||
|
||||
func (r *RmCmd) Run(ctx *Context) error {
|
||||
fmt.Println("rm", r.Paths)
|
||||
return nil
|
||||
}
|
||||
|
||||
type LsCmd struct {
|
||||
Paths []string `arg optional name:"path" help:"Paths to list." type:"path"`
|
||||
}
|
||||
|
||||
func (l *LsCmd) Run(ctx *Context) error {
|
||||
fmt.Println("ls", l.Paths)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
Debug bool `help:"Enable debug mode."`
|
||||
|
||||
Rm RmCmd `cmd help:"Remove files."`
|
||||
Ls LsCmd `cmd help:"List paths."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx := kong.Parse(&cli)
|
||||
// Call the Run() method of the selected parsed command.
|
||||
err := ctx.Run(&Context{Debug: cli.Debug})
|
||||
ctx.FatalIfErrorf(err)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Hooks: BeforeResolve(), BeforeApply(), AfterApply() and the Bind() option
|
||||
|
||||
If a node in the grammar has a `BeforeResolve(...)`, `BeforeApply(...) error` and/or `AfterApply(...) error` method, those methods will be called before validation/assignment and after validation/assignment, respectively.
|
||||
|
||||
The `--help` flag is implemented with a `BeforeApply` hook.
|
||||
|
||||
Arguments to hooks are provided via the `Run(...)` method or `Bind(...)` option. `*Kong`, `*Context` and `*Path` are also bound and finally, hooks can also contribute bindings via `kong.Context.Bind()` and `kong.Context.BindTo()`.
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
// A flag with a hook that, if triggered, will set the debug loggers output to stdout.
|
||||
type debugFlag bool
|
||||
|
||||
func (d debugFlag) BeforeApply(logger *log.Logger) error {
|
||||
logger.SetOutput(os.Stdout)
|
||||
return nil
|
||||
}
|
||||
|
||||
var cli struct {
|
||||
Debug debugFlag `help:"Enable debug logging."`
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Debug logger going to discard.
|
||||
logger := log.New(ioutil.Discard, "", log.LstdFlags)
|
||||
|
||||
ctx := kong.Parse(&cli, kong.Bind(logger))
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Flags
|
||||
|
||||
Any [mapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) field in the command structure *not* tagged with `cmd` or `arg` will be a flag. Flags are optional by default.
|
||||
|
||||
eg. The command-line `app [--flag="foo"]` can be represented by the following.
|
||||
|
||||
```go
|
||||
type CLI struct {
|
||||
Flag string
|
||||
}
|
||||
```
|
||||
|
||||
## Commands and sub-commands
|
||||
|
||||
Sub-commands are specified by tagging a struct field with `cmd`. Kong supports arbitrarily nested commands.
|
||||
|
||||
eg. The following struct represents the CLI structure `command [--flag="str"] sub-command`.
|
||||
|
||||
```go
|
||||
type CLI struct {
|
||||
Command struct {
|
||||
Flag string
|
||||
|
||||
SubCommand struct {
|
||||
} `cmd`
|
||||
} `cmd`
|
||||
}
|
||||
```
|
||||
|
||||
If a sub-command is tagged with `default:"1"` it will be selected if there are no further arguments.
|
||||
|
||||
## Branching positional arguments
|
||||
|
||||
In addition to sub-commands, structs can also be configured as branching positional arguments.
|
||||
|
||||
This is achieved by tagging an [unmapped](#mapper---customising-how-the-command-line-is-mapped-to-go-values) nested struct field with `arg`, then including a positional argument field inside that struct _with the same name_. For example, the following command structure:
|
||||
|
||||
app rename <name> to <name>
|
||||
|
||||
Can be represented with the following:
|
||||
|
||||
```go
|
||||
var CLI struct {
|
||||
Rename struct {
|
||||
Name struct {
|
||||
Name string `arg` // <-- NOTE: identical name to enclosing struct field.
|
||||
To struct {
|
||||
Name struct {
|
||||
Name string `arg`
|
||||
} `arg`
|
||||
} `cmd`
|
||||
} `arg`
|
||||
} `cmd`
|
||||
}
|
||||
```
|
||||
|
||||
This looks a little verbose in this contrived example, but typically this will not be the case.
|
||||
|
||||
## Terminating positional arguments
|
||||
|
||||
If a [mapped type](#mapper---customising-how-the-command-line-is-mapped-to-go-values) is tagged with `arg` it will be treated as the final positional values to be parsed on the command line.
|
||||
|
||||
If a positional argument is a slice, all remaining arguments will be appended to that slice.
|
||||
|
||||
## Slices
|
||||
|
||||
Slice values are treated specially. First the input is split on the `sep:"<rune>"` tag (defaults to `,`), then each element is parsed by the slice element type and appended to the slice. If the same value is encountered multiple times, elements continue to be appended.
|
||||
|
||||
To represent the following command-line:
|
||||
|
||||
cmd ls <file> <file> ...
|
||||
|
||||
You would use the following:
|
||||
|
||||
```go
|
||||
var CLI struct {
|
||||
Ls struct {
|
||||
Files []string `arg type:"existingfile"`
|
||||
} `cmd`
|
||||
}
|
||||
```
|
||||
|
||||
## Maps
|
||||
|
||||
Maps are similar to slices except that only one key/value pair can be assigned per value, and the `sep` tag denotes the assignment character and defaults to `=`.
|
||||
|
||||
To represent the following command-line:
|
||||
|
||||
cmd config set <key>=<value> <key>=<value> ...
|
||||
|
||||
You would use the following:
|
||||
|
||||
```go
|
||||
var CLI struct {
|
||||
Config struct {
|
||||
Set struct {
|
||||
Config map[string]float64 `arg type:"file:"`
|
||||
} `cmd`
|
||||
} `cmd`
|
||||
}
|
||||
```
|
||||
|
||||
For flags, multiple key+value pairs should be separated by `mapsep:"rune"` tag (defaults to `;`) eg. `--set="key1=value1;key2=value2"`.
|
||||
|
||||
## Custom named decoders
|
||||
|
||||
Kong includes a number of builtin custom type mappers. These can be used by
|
||||
specifying the tag `type:"<type>"`. They are registered with the option
|
||||
function `NamedMapper(name, mapper)`.
|
||||
|
||||
| Name | Description
|
||||
|-------------------|---------------------------------------------------
|
||||
| `path` | A path. ~ expansion is applied.
|
||||
| `existingfile` | An existing file. ~ expansion is applied. `-` is accepted for stdin.
|
||||
| `existingdir` | An existing directory. ~ expansion is applied.
|
||||
| `counter` | Increment a numeric field. Useful for `-vvv`. Can accept `-s`, `--long` or `--long=N`.
|
||||
|
||||
|
||||
Slices and maps treat type tags specially. For slices, the `type:""` tag
|
||||
specifies the element type. For maps, the tag has the format
|
||||
`tag:"[<key>]:[<value>]"` where either may be omitted.
|
||||
|
||||
|
||||
## Custom decoders (mappers)
|
||||
|
||||
Any field implementing `encoding.TextUnmarshaler` or `json.Unmarshaler` will use those interfaces
|
||||
for decoding values.
|
||||
|
||||
For more fine-grained control, if a field implements the
|
||||
[MapperValue](https://godoc.org/github.com/alecthomas/kong#MapperValue)
|
||||
interface it will be used to decode arguments into the field.
|
||||
|
||||
## Supported tags
|
||||
|
||||
Tags can be in two forms:
|
||||
|
||||
1. Standard Go syntax, eg. `kong:"required,name='foo'"`.
|
||||
2. Bare tags, eg. `required name:"foo"`
|
||||
|
||||
Both can coexist with standard Tag parsing.
|
||||
|
||||
Tag | Description
|
||||
-----------------------| -------------------------------------------
|
||||
`cmd` | If present, struct is a command.
|
||||
`arg` | If present, field is an argument.
|
||||
`env:"X"` | Specify envar to use for default value.
|
||||
`name:"X"` | Long name, for overriding field name.
|
||||
`help:"X"` | Help text.
|
||||
`type:"X"` | Specify [named types](#custom-named-decoders) to use.
|
||||
`placeholder:"X"` | Placeholder text.
|
||||
`default:"X"` | Default value.
|
||||
`default:"1"` | On a command, make it the default.
|
||||
`short:"X"` | Short name, if flag.
|
||||
`required` | If present, flag/arg is required.
|
||||
`optional` | If present, flag/arg is optional.
|
||||
`hidden` | If present, command or flag is hidden.
|
||||
`format:"X"` | Format for parsing input, if supported.
|
||||
`sep:"X"` | Separator for sequences (defaults to ","). May be `none` to disable splitting.
|
||||
`mapsep:"X"` | Separator for maps (defaults to ";"). May be `none` to disable splitting.
|
||||
`enum:"X,Y,..."` | Set of valid values allowed for this flag.
|
||||
`group:"X"` | Logical group for a flag or command.
|
||||
`xor:"X"` | Exclusive OR group for flags. Only one flag in the group can be used which is restricted within the same command.
|
||||
`prefix:"X"` | Prefix for all sub-flags.
|
||||
`set:"K=V"` | Set a variable for expansion by child elements. Multiples can occur.
|
||||
`embed` | If present, this field's children will be embedded in the parent. Useful for composition.
|
||||
`-` | Ignore the field. Useful for adding non-CLI fields to a configuration struct.
|
||||
|
||||
## Variable interpolation
|
||||
|
||||
Kong supports limited variable interpolation into help strings, enum lists and
|
||||
default values.
|
||||
|
||||
Variables are in the form:
|
||||
|
||||
${<name>}
|
||||
${<name>=<default>}
|
||||
|
||||
Variables are set with the `Vars{"key": "value", ...}` option. Undefined
|
||||
variable references in the grammar without a default will result in an error at
|
||||
construction time.
|
||||
|
||||
Variables can also be set via the `set:"K=V"` tag. In this case, those variables will be available for that
|
||||
node and all children. This is useful for composition by allowing the same struct to be reused.
|
||||
|
||||
When interpolating into flag or argument help strings, some extra variables
|
||||
are defined from the value itself:
|
||||
|
||||
${default}
|
||||
${enum}
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
type cli struct {
|
||||
Config string `type:"path" default:"${config_file}"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
kong.Parse(&cli,
|
||||
kong.Vars{
|
||||
"config_file": "~/.app.conf",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## Modifying Kong's behaviour
|
||||
|
||||
Each Kong parser can be configured via functional options passed to `New(cli interface{}, options...Option)`.
|
||||
|
||||
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
|
||||
|
||||
### `Name(help)` and `Description(help)` - set the application name description
|
||||
|
||||
Set the application name and/or description.
|
||||
|
||||
The name of the application will default to the binary name, but can be overridden with `Name(name)`.
|
||||
|
||||
As with all help in Kong, text will be wrapped to the terminal.
|
||||
|
||||
### `Configuration(loader, paths...)` - load defaults from configuration files
|
||||
|
||||
This option provides Kong with support for loading defaults from a set of configuration files. Each file is opened, if possible, and the loader called to create a resolver for that file.
|
||||
|
||||
eg.
|
||||
|
||||
```go
|
||||
kong.Parse(&cli, kong.Configuration(kong.JSON, "/etc/myapp.json", "~/.myapp.json"))
|
||||
```
|
||||
|
||||
[See the tests](https://github.com/alecthomas/kong/blob/master/resolver_test.go#L103) for an example of how the JSON file is structured.
|
||||
|
||||
### `Resolver(...)` - support for default values from external sources
|
||||
|
||||
Resolvers are Kong's extension point for providing default values from external sources. As an example, support for environment variables via the `env` tag is provided by a resolver. There's also a builtin resolver for JSON configuration files.
|
||||
|
||||
Example resolvers can be found in [resolver.go](https://github.com/alecthomas/kong/blob/master/resolver.go).
|
||||
|
||||
### `*Mapper(...)` - customising how the command-line is mapped to Go values
|
||||
|
||||
Command-line arguments are mapped to Go values via the Mapper interface:
|
||||
|
||||
```go
|
||||
// A Mapper knows how to map command-line input to Go.
|
||||
type Mapper interface {
|
||||
// Decode scan into target.
|
||||
//
|
||||
// "ctx" contains context about the value being decoded that may be useful
|
||||
// to some mapperss.
|
||||
Decode(ctx *MapperContext, scan *Scanner, target reflect.Value) error
|
||||
}
|
||||
```
|
||||
|
||||
All builtin Go types (as well as a bunch of useful stdlib types like `time.Time`) have mappers registered by default. Mappers for custom types can be added using `kong.??Mapper(...)` options. Mappers are applied to fields in four ways:
|
||||
|
||||
1. `NamedMapper(string, Mapper)` and using the tag key `type:"<name>"`.
|
||||
2. `KindMapper(reflect.Kind, Mapper)`.
|
||||
3. `TypeMapper(reflect.Type, Mapper)`.
|
||||
4. `ValueMapper(interface{}, Mapper)`, passing in a pointer to a field of the grammar.
|
||||
|
||||
### `ConfigureHelp(HelpOptions)` and `Help(HelpFunc)` - customising help
|
||||
|
||||
The default help output is usually sufficient, but if not there are two solutions.
|
||||
|
||||
1. Use `ConfigureHelp(HelpOptions)` to configure how help is formatted (see [HelpOptions](https://godoc.org/github.com/alecthomas/kong#HelpOptions) for details).
|
||||
2. Custom help can be wired into Kong via the `Help(HelpFunc)` option. The `HelpFunc` is passed a `Context`, which contains the parsed context for the current command-line. See the implementation of `PrintHelp` for an example.
|
||||
3. Use `HelpFormatter(HelpValueFormatter)` if you want to just customize the help text that is accompanied by flags and arguments.
|
||||
|
||||
### `Bind(...)` - bind values for callback hooks and Run() methods
|
||||
|
||||
See the [section on hooks](#hooks-beforeresolve-beforeapply-afterapply-and-the-bind-option) for details.
|
||||
|
||||
### Other options
|
||||
|
||||
The full set of options can be found [here](https://godoc.org/github.com/alecthomas/kong#Option).
|
|
@ -0,0 +1,211 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func build(k *Kong, ast interface{}) (app *Application, err error) {
|
||||
defer catch(&err)
|
||||
v := reflect.ValueOf(ast)
|
||||
iv := reflect.Indirect(v)
|
||||
if v.Kind() != reflect.Ptr || iv.Kind() != reflect.Struct {
|
||||
return nil, fmt.Errorf("expected a pointer to a struct but got %T", ast)
|
||||
}
|
||||
|
||||
app = &Application{}
|
||||
extraFlags := k.extraFlags()
|
||||
seenFlags := map[string]bool{}
|
||||
for _, flag := range extraFlags {
|
||||
seenFlags[flag.Name] = true
|
||||
}
|
||||
node := buildNode(k, iv, ApplicationNode, seenFlags)
|
||||
if len(node.Positional) > 0 && len(node.Children) > 0 {
|
||||
return nil, fmt.Errorf("can't mix positional arguments and branching arguments on %T", ast)
|
||||
}
|
||||
app.Node = node
|
||||
app.Node.Flags = append(extraFlags, app.Node.Flags...)
|
||||
app.Tag = newEmptyTag()
|
||||
app.Tag.Vars = k.vars
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func dashedString(s string) string {
|
||||
return strings.Join(camelCase(s), "-")
|
||||
}
|
||||
|
||||
type flattenedField struct {
|
||||
field reflect.StructField
|
||||
value reflect.Value
|
||||
tag *Tag
|
||||
}
|
||||
|
||||
func flattenedFields(v reflect.Value) (out []flattenedField) {
|
||||
v = reflect.Indirect(v)
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := v.Type().Field(i)
|
||||
fv := v.Field(i)
|
||||
tag := parseTag(fv, ft)
|
||||
if tag.Ignored {
|
||||
continue
|
||||
}
|
||||
if ft.Anonymous || tag.Embed {
|
||||
if fv.Kind() == reflect.Interface {
|
||||
fv = fv.Elem()
|
||||
}
|
||||
sub := flattenedFields(fv)
|
||||
for _, subf := range sub {
|
||||
// Assign parent if it's not already set.
|
||||
if subf.tag.Group == "" {
|
||||
subf.tag.Group = tag.Group
|
||||
}
|
||||
// Accumulate prefixes.
|
||||
subf.tag.Prefix = tag.Prefix + subf.tag.Prefix
|
||||
// Combine parent vars.
|
||||
subf.tag.Vars = tag.Vars.CloneWith(subf.tag.Vars)
|
||||
}
|
||||
out = append(out, sub...)
|
||||
continue
|
||||
}
|
||||
if !fv.CanSet() {
|
||||
continue
|
||||
}
|
||||
out = append(out, flattenedField{field: ft, value: fv, tag: tag})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func buildNode(k *Kong, v reflect.Value, typ NodeType, seenFlags map[string]bool) *Node {
|
||||
node := &Node{
|
||||
Type: typ,
|
||||
Target: v,
|
||||
Tag: newEmptyTag(),
|
||||
}
|
||||
for _, field := range flattenedFields(v) {
|
||||
ft := field.field
|
||||
fv := field.value
|
||||
|
||||
tag := field.tag
|
||||
name := tag.Name
|
||||
if name == "" {
|
||||
name = tag.Prefix + strings.ToLower(dashedString(ft.Name))
|
||||
} else {
|
||||
name = tag.Prefix + name
|
||||
}
|
||||
|
||||
// Nested structs are either commands or args, unless they implement the Mapper interface.
|
||||
if ft.Type.Kind() == reflect.Struct && (tag.Cmd || tag.Arg) && k.registry.ForValue(fv) == nil {
|
||||
typ := CommandNode
|
||||
if tag.Arg {
|
||||
typ = ArgumentNode
|
||||
}
|
||||
buildChild(k, node, typ, v, ft, fv, tag, name, seenFlags)
|
||||
} else {
|
||||
buildField(k, node, v, ft, fv, tag, name, seenFlags)
|
||||
}
|
||||
}
|
||||
|
||||
// "Unsee" flags.
|
||||
for _, flag := range node.Flags {
|
||||
delete(seenFlags, flag.Name)
|
||||
}
|
||||
|
||||
// Scan through argument positionals to ensure optional is never before a required.
|
||||
last := true
|
||||
for i, p := range node.Positional {
|
||||
if !last && p.Required {
|
||||
fail("argument %q can not be required after an optional", p.Name)
|
||||
}
|
||||
|
||||
last = p.Required
|
||||
p.Position = i
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func buildChild(k *Kong, node *Node, typ NodeType, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||
child := buildNode(k, fv, typ, seenFlags)
|
||||
child.Tag = tag
|
||||
child.Parent = node
|
||||
child.Help = tag.Help
|
||||
child.Hidden = tag.Hidden
|
||||
child.Group = tag.Group
|
||||
|
||||
if provider, ok := fv.Addr().Interface().(HelpProvider); ok {
|
||||
child.Detail = provider.Help()
|
||||
}
|
||||
|
||||
// A branching argument. This is a bit hairy, as we let buildNode() do the parsing, then check that
|
||||
// a positional argument is provided to the child, and move it to the branching argument field.
|
||||
if tag.Arg {
|
||||
if len(child.Positional) == 0 {
|
||||
fail("positional branch %s.%s must have at least one child positional argument named %q",
|
||||
v.Type().Name(), ft.Name, name)
|
||||
}
|
||||
|
||||
value := child.Positional[0]
|
||||
child.Positional = child.Positional[1:]
|
||||
if child.Help == "" {
|
||||
child.Help = value.Help
|
||||
}
|
||||
|
||||
child.Name = value.Name
|
||||
if child.Name != name {
|
||||
fail("first field in positional branch %s.%s must have the same name as the parent field (%s).",
|
||||
v.Type().Name(), ft.Name, child.Name)
|
||||
}
|
||||
|
||||
child.Argument = value
|
||||
} else {
|
||||
child.Name = name
|
||||
}
|
||||
node.Children = append(node.Children, child)
|
||||
|
||||
if len(child.Positional) > 0 && len(child.Children) > 0 {
|
||||
fail("can't mix positional arguments and branching arguments on %s.%s", v.Type().Name(), ft.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func buildField(k *Kong, node *Node, v reflect.Value, ft reflect.StructField, fv reflect.Value, tag *Tag, name string, seenFlags map[string]bool) {
|
||||
mapper := k.registry.ForNamedValue(tag.Type, fv)
|
||||
if mapper == nil {
|
||||
fail("unsupported field type %s.%s (of type %s)", v.Type(), ft.Name, ft.Type)
|
||||
}
|
||||
|
||||
value := &Value{
|
||||
Name: name,
|
||||
Help: tag.Help,
|
||||
Default: tag.Default,
|
||||
DefaultValue: reflect.New(fv.Type()).Elem(),
|
||||
Mapper: mapper,
|
||||
Tag: tag,
|
||||
Target: fv,
|
||||
Enum: tag.Enum,
|
||||
|
||||
// Flags are optional by default, and args are required by default.
|
||||
Required: (!tag.Arg && tag.Required) || (tag.Arg && !tag.Optional),
|
||||
Format: tag.Format,
|
||||
}
|
||||
|
||||
if tag.Arg {
|
||||
node.Positional = append(node.Positional, value)
|
||||
} else {
|
||||
if seenFlags[value.Name] {
|
||||
fail("duplicate flag --%s", value.Name)
|
||||
}
|
||||
seenFlags[value.Name] = true
|
||||
flag := &Flag{
|
||||
Value: value,
|
||||
Short: tag.Short,
|
||||
PlaceHolder: tag.PlaceHolder,
|
||||
Env: tag.Env,
|
||||
Group: tag.Group,
|
||||
Xor: tag.Xor,
|
||||
Hidden: tag.Hidden,
|
||||
}
|
||||
value.Flag = flag
|
||||
node.Flags = append(node.Flags, flag)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type bindings map[reflect.Type]func() (reflect.Value, error)
|
||||
|
||||
func (b bindings) String() string {
|
||||
out := []string{}
|
||||
for k := range b {
|
||||
out = append(out, k.String())
|
||||
}
|
||||
return "bindings{" + strings.Join(out, ", ") + "}"
|
||||
}
|
||||
|
||||
func (b bindings) add(values ...interface{}) bindings {
|
||||
for _, v := range values {
|
||||
v := v
|
||||
b[reflect.TypeOf(v)] = func() (reflect.Value, error) { return reflect.ValueOf(v), nil }
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Clone and add values.
|
||||
func (b bindings) clone() bindings {
|
||||
out := make(bindings, len(b))
|
||||
for k, v := range b {
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (b bindings) merge(other bindings) bindings {
|
||||
for k, v := range other {
|
||||
b[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func getMethod(value reflect.Value, name string) reflect.Value {
|
||||
method := value.MethodByName(name)
|
||||
if !method.IsValid() {
|
||||
if value.CanAddr() {
|
||||
method = value.Addr().MethodByName(name)
|
||||
}
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
func callMethod(name string, v, f reflect.Value, bindings bindings) error {
|
||||
in := []reflect.Value{}
|
||||
t := f.Type()
|
||||
if t.NumOut() != 1 || t.Out(0) != callbackReturnSignature {
|
||||
return fmt.Errorf("return value of %T.%s() must be exactly \"error\"", v.Type(), name)
|
||||
}
|
||||
for i := 0; i < t.NumIn(); i++ {
|
||||
pt := t.In(i)
|
||||
if argf, ok := bindings[pt]; ok {
|
||||
argv, err := argf()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
in = append(in, argv)
|
||||
} else {
|
||||
return fmt.Errorf("couldn't find binding of type %s for parameter %d of %s.%s(), use kong.Bind(%s)", pt, i, v.Type(), name, pt)
|
||||
}
|
||||
}
|
||||
out := f.Call(in)
|
||||
if out[0].IsNil() {
|
||||
return nil
|
||||
}
|
||||
return out[0].Interface().(error)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package kong
|
||||
|
||||
// NOTE: This code is from https://github.com/fatih/camelcase. MIT license.
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Split splits the camelcase word and returns a list of words. It also
|
||||
// supports digits. Both lower camel case and upper camel case are supported.
|
||||
// For more info please check: http://en.wikipedia.org/wiki/CamelCase
|
||||
//
|
||||
// Examples
|
||||
//
|
||||
// "" => [""]
|
||||
// "lowercase" => ["lowercase"]
|
||||
// "Class" => ["Class"]
|
||||
// "MyClass" => ["My", "Class"]
|
||||
// "MyC" => ["My", "C"]
|
||||
// "HTML" => ["HTML"]
|
||||
// "PDFLoader" => ["PDF", "Loader"]
|
||||
// "AString" => ["A", "String"]
|
||||
// "SimpleXMLParser" => ["Simple", "XML", "Parser"]
|
||||
// "vimRPCPlugin" => ["vim", "RPC", "Plugin"]
|
||||
// "GL11Version" => ["GL", "11", "Version"]
|
||||
// "99Bottles" => ["99", "Bottles"]
|
||||
// "May5" => ["May", "5"]
|
||||
// "BFG9000" => ["BFG", "9000"]
|
||||
// "BöseÜberraschung" => ["Böse", "Überraschung"]
|
||||
// "Two spaces" => ["Two", " ", "spaces"]
|
||||
// "BadUTF8\xe2\xe2\xa1" => ["BadUTF8\xe2\xe2\xa1"]
|
||||
//
|
||||
// Splitting rules
|
||||
//
|
||||
// 1) If string is not valid UTF-8, return it without splitting as
|
||||
// single item array.
|
||||
// 2) Assign all unicode characters into one of 4 sets: lower case
|
||||
// letters, upper case letters, numbers, and all other characters.
|
||||
// 3) Iterate through characters of string, introducing splits
|
||||
// between adjacent characters that belong to different sets.
|
||||
// 4) Iterate through array of split strings, and if a given string
|
||||
// is upper case:
|
||||
// if subsequent string is lower case:
|
||||
// move last character of upper case string to beginning of
|
||||
// lower case string
|
||||
func camelCase(src string) (entries []string) {
|
||||
// don't split invalid utf8
|
||||
if !utf8.ValidString(src) {
|
||||
return []string{src}
|
||||
}
|
||||
entries = []string{}
|
||||
var runes [][]rune
|
||||
lastClass := 0
|
||||
// split into fields based on class of unicode character
|
||||
for _, r := range src {
|
||||
var class int
|
||||
switch {
|
||||
case unicode.IsLower(r):
|
||||
class = 1
|
||||
case unicode.IsUpper(r):
|
||||
class = 2
|
||||
case unicode.IsDigit(r):
|
||||
class = 3
|
||||
default:
|
||||
class = 4
|
||||
}
|
||||
if class == lastClass {
|
||||
runes[len(runes)-1] = append(runes[len(runes)-1], r)
|
||||
} else {
|
||||
runes = append(runes, []rune{r})
|
||||
}
|
||||
lastClass = class
|
||||
}
|
||||
// handle upper case -> lower case sequences, e.g.
|
||||
// "PDFL", "oader" -> "PDF", "Loader"
|
||||
for i := 0; i < len(runes)-1; i++ {
|
||||
if unicode.IsUpper(runes[i][0]) && unicode.IsLower(runes[i+1][0]) {
|
||||
runes[i+1] = append([]rune{runes[i][len(runes[i])-1]}, runes[i+1]...)
|
||||
runes[i] = runes[i][:len(runes[i])-1]
|
||||
}
|
||||
}
|
||||
// construct []string from results
|
||||
for _, s := range runes {
|
||||
if len(s) > 0 {
|
||||
entries = append(entries, string(s))
|
||||
}
|
||||
}
|
||||
return entries
|
||||
}
|
|
@ -0,0 +1,787 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Path records the nodes and parsed values from the current command-line.
|
||||
type Path struct {
|
||||
Parent *Node
|
||||
|
||||
// One of these will be non-nil.
|
||||
App *Application
|
||||
Positional *Positional
|
||||
Flag *Flag
|
||||
Argument *Argument
|
||||
Command *Command
|
||||
|
||||
// Flags added by this node.
|
||||
Flags []*Flag
|
||||
|
||||
// True if this Path element was created as the result of a resolver.
|
||||
Resolved bool
|
||||
}
|
||||
|
||||
// Node returns the Node associated with this Path, or nil if Path is a non-Node.
|
||||
func (p *Path) Node() *Node {
|
||||
switch {
|
||||
case p.App != nil:
|
||||
return p.App.Node
|
||||
|
||||
case p.Argument != nil:
|
||||
return p.Argument
|
||||
|
||||
case p.Command != nil:
|
||||
return p.Command
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Context contains the current parse context.
|
||||
type Context struct {
|
||||
*Kong
|
||||
// A trace through parsed nodes.
|
||||
Path []*Path
|
||||
// Original command-line arguments.
|
||||
Args []string
|
||||
// Error that occurred during trace, if any.
|
||||
Error error
|
||||
|
||||
values map[*Value]reflect.Value // Temporary values during tracing.
|
||||
bindings bindings
|
||||
resolvers []Resolver // Extra context-specific resolvers.
|
||||
scan *Scanner
|
||||
}
|
||||
|
||||
// Trace path of "args" through the grammar tree.
|
||||
//
|
||||
// The returned Context will include a Path of all commands, arguments, positionals and flags.
|
||||
//
|
||||
// This just constructs a new trace. To fully apply the trace you must call Reset(), Resolve(),
|
||||
// Validate() and Apply().
|
||||
func Trace(k *Kong, args []string) (*Context, error) {
|
||||
c := &Context{
|
||||
Kong: k,
|
||||
Args: args,
|
||||
Path: []*Path{
|
||||
{App: k.Model, Flags: k.Model.Flags},
|
||||
},
|
||||
values: map[*Value]reflect.Value{},
|
||||
scan: Scan(args...),
|
||||
bindings: bindings{},
|
||||
}
|
||||
c.Error = c.trace(c.Model.Node)
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// Bind adds bindings to the Context.
|
||||
func (c *Context) Bind(args ...interface{}) {
|
||||
c.bindings.add(args...)
|
||||
}
|
||||
|
||||
// BindTo adds a binding to the Context.
|
||||
//
|
||||
// This will typically have to be called like so:
|
||||
//
|
||||
// BindTo(impl, (*MyInterface)(nil))
|
||||
func (c *Context) BindTo(impl, iface interface{}) {
|
||||
valueOf := reflect.ValueOf(impl)
|
||||
c.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
||||
}
|
||||
|
||||
// Value returns the value for a particular path element.
|
||||
func (c *Context) Value(path *Path) reflect.Value {
|
||||
switch {
|
||||
case path.Positional != nil:
|
||||
return c.values[path.Positional]
|
||||
case path.Flag != nil:
|
||||
return c.values[path.Flag.Value]
|
||||
case path.Argument != nil:
|
||||
return c.values[path.Argument.Argument]
|
||||
}
|
||||
panic("can only retrieve value for flag, argument or positional")
|
||||
}
|
||||
|
||||
// Selected command or argument.
|
||||
func (c *Context) Selected() *Node {
|
||||
var selected *Node
|
||||
for _, path := range c.Path {
|
||||
switch {
|
||||
case path.Command != nil:
|
||||
selected = path.Command
|
||||
case path.Argument != nil:
|
||||
selected = path.Argument
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
// Empty returns true if there were no arguments provided.
|
||||
func (c *Context) Empty() bool {
|
||||
for _, path := range c.Path {
|
||||
if !path.Resolved && path.App == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Validate the current context.
|
||||
func (c *Context) Validate() error { // nolint: gocyclo
|
||||
err := Visit(c.Model, func(node Visitable, next Next) error {
|
||||
if value, ok := node.(*Value); ok {
|
||||
if value.Enum != "" && (!value.Required || value.Default != "") {
|
||||
if err := checkEnum(value, value.Target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, resolver := range c.combineResolvers() {
|
||||
if err := resolver.Validate(c.Model); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, path := range c.Path {
|
||||
var value *Value
|
||||
switch {
|
||||
case path.Flag != nil:
|
||||
value = path.Flag.Value
|
||||
|
||||
case path.Positional != nil:
|
||||
value = path.Positional
|
||||
}
|
||||
if value != nil && value.Tag.Enum != "" {
|
||||
if err := checkEnum(value, value.Target); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := checkMissingFlags(path.Flags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Check the terminal node.
|
||||
node := c.Selected()
|
||||
if node == nil {
|
||||
node = c.Model.Node
|
||||
}
|
||||
|
||||
// Find deepest positional argument so we can check if all required positionals have been provided.
|
||||
positionals := 0
|
||||
for _, path := range c.Path {
|
||||
if path.Positional != nil {
|
||||
positionals = path.Positional.Position + 1
|
||||
}
|
||||
}
|
||||
|
||||
if err := checkMissingChildren(node); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkMissingPositionals(positionals, node.Positional); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := checkXorDuplicates(c.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if node.Type == ArgumentNode {
|
||||
value := node.Argument
|
||||
if value.Required && !value.Set {
|
||||
return fmt.Errorf("%s is required", node.Summary())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Flags returns the accumulated available flags.
|
||||
func (c *Context) Flags() (flags []*Flag) {
|
||||
for _, trace := range c.Path {
|
||||
flags = append(flags, trace.Flags...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Command returns the full command path.
|
||||
func (c *Context) Command() string {
|
||||
command := []string{}
|
||||
for _, trace := range c.Path {
|
||||
switch {
|
||||
case trace.Positional != nil:
|
||||
command = append(command, "<"+trace.Positional.Name+">")
|
||||
|
||||
case trace.Argument != nil:
|
||||
command = append(command, "<"+trace.Argument.Name+">")
|
||||
|
||||
case trace.Command != nil:
|
||||
command = append(command, trace.Command.Name)
|
||||
}
|
||||
}
|
||||
return strings.Join(command, " ")
|
||||
}
|
||||
|
||||
// AddResolver adds a context-specific resolver.
|
||||
//
|
||||
// This is most useful in the BeforeResolve() hook.
|
||||
func (c *Context) AddResolver(resolver Resolver) {
|
||||
c.resolvers = append(c.resolvers, resolver)
|
||||
}
|
||||
|
||||
// FlagValue returns the set value of a flag if it was encountered and exists, or its default value.
|
||||
func (c *Context) FlagValue(flag *Flag) interface{} {
|
||||
for _, trace := range c.Path {
|
||||
if trace.Flag == flag {
|
||||
v, ok := c.values[trace.Flag.Value]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
return v.Interface()
|
||||
}
|
||||
}
|
||||
if flag.Target.IsValid() {
|
||||
return flag.Target.Interface()
|
||||
}
|
||||
return flag.DefaultValue.Interface()
|
||||
}
|
||||
|
||||
// Reset recursively resets values to defaults (as specified in the grammar) or the zero value.
|
||||
func (c *Context) Reset() error {
|
||||
return Visit(c.Model.Node, func(node Visitable, next Next) error {
|
||||
if value, ok := node.(*Value); ok {
|
||||
return next(value.Reset())
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Context) trace(node *Node) (err error) { // nolint: gocyclo
|
||||
positional := 0
|
||||
|
||||
flags := []*Flag{}
|
||||
for _, group := range node.AllFlags(false) {
|
||||
flags = append(flags, group...)
|
||||
}
|
||||
|
||||
for !c.scan.Peek().IsEOL() {
|
||||
token := c.scan.Peek()
|
||||
switch token.Type {
|
||||
case UntypedToken:
|
||||
switch v := token.Value.(type) {
|
||||
case string:
|
||||
|
||||
switch {
|
||||
case v == "-":
|
||||
fallthrough
|
||||
default: // nolint
|
||||
c.scan.Pop()
|
||||
c.scan.PushTyped(token.Value, PositionalArgumentToken)
|
||||
|
||||
// Indicates end of parsing. All remaining arguments are treated as positional arguments only.
|
||||
case v == "--":
|
||||
c.scan.Pop()
|
||||
args := []string{}
|
||||
for {
|
||||
token = c.scan.Pop()
|
||||
if token.Type == EOLToken {
|
||||
break
|
||||
}
|
||||
args = append(args, token.String())
|
||||
}
|
||||
// Note: tokens must be pushed in reverse order.
|
||||
for i := range args {
|
||||
c.scan.PushTyped(args[len(args)-1-i], PositionalArgumentToken)
|
||||
}
|
||||
|
||||
// Long flag.
|
||||
case strings.HasPrefix(v, "--"):
|
||||
c.scan.Pop()
|
||||
// Parse it and push the tokens.
|
||||
parts := strings.SplitN(v[2:], "=", 2)
|
||||
if len(parts) > 1 {
|
||||
c.scan.PushTyped(parts[1], FlagValueToken)
|
||||
}
|
||||
c.scan.PushTyped(parts[0], FlagToken)
|
||||
|
||||
// Short flag.
|
||||
case strings.HasPrefix(v, "-"):
|
||||
c.scan.Pop()
|
||||
// Note: tokens must be pushed in reverse order.
|
||||
if tail := v[2:]; tail != "" {
|
||||
c.scan.PushTyped(tail, ShortFlagTailToken)
|
||||
}
|
||||
c.scan.PushTyped(v[1:2], ShortFlagToken)
|
||||
}
|
||||
default:
|
||||
c.scan.Pop()
|
||||
c.scan.PushTyped(token.Value, PositionalArgumentToken)
|
||||
}
|
||||
|
||||
case ShortFlagTailToken:
|
||||
c.scan.Pop()
|
||||
// Note: tokens must be pushed in reverse order.
|
||||
if tail := token.String()[1:]; tail != "" {
|
||||
c.scan.PushTyped(tail, ShortFlagTailToken)
|
||||
}
|
||||
c.scan.PushTyped(token.String()[0:1], ShortFlagToken)
|
||||
|
||||
case FlagToken:
|
||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case ShortFlagToken:
|
||||
if err := c.parseFlag(flags, token.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case FlagValueToken:
|
||||
return fmt.Errorf("unexpected flag argument %q", token.Value)
|
||||
|
||||
case PositionalArgumentToken:
|
||||
candidates := []string{}
|
||||
|
||||
// Ensure we've consumed all positional arguments.
|
||||
if positional < len(node.Positional) {
|
||||
arg := node.Positional[positional]
|
||||
err := arg.Parse(c.scan, c.getValue(arg))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Positional: arg,
|
||||
})
|
||||
positional++
|
||||
break
|
||||
}
|
||||
|
||||
// After positional arguments have been consumed, check commands next...
|
||||
for _, branch := range node.Children {
|
||||
if branch.Type == CommandNode && !branch.Hidden {
|
||||
candidates = append(candidates, branch.Name)
|
||||
}
|
||||
if branch.Type == CommandNode && branch.Name == token.Value {
|
||||
c.scan.Pop()
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Command: branch,
|
||||
Flags: branch.Flags,
|
||||
})
|
||||
return c.trace(branch)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, check arguments.
|
||||
for _, branch := range node.Children {
|
||||
if branch.Type == ArgumentNode {
|
||||
arg := branch.Argument
|
||||
if err := arg.Parse(c.scan, c.getValue(arg)); err == nil {
|
||||
c.Path = append(c.Path, &Path{
|
||||
Parent: node,
|
||||
Argument: branch,
|
||||
Flags: branch.Flags,
|
||||
})
|
||||
return c.trace(branch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return findPotentialCandidates(token.String(), candidates, "unexpected argument %s", token)
|
||||
default:
|
||||
return fmt.Errorf("unexpected token %s", token)
|
||||
}
|
||||
}
|
||||
return c.maybeSelectDefault(flags, node)
|
||||
}
|
||||
|
||||
// End of the line, check for a default command, but only if we're not displaying help,
|
||||
// otherwise we'd only ever display the help for the default command.
|
||||
func (c *Context) maybeSelectDefault(flags []*Flag, node *Node) error {
|
||||
for _, flag := range flags {
|
||||
if flag.Name == "help" && flag.Set {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
var defaultNode *Path
|
||||
for _, child := range node.Children {
|
||||
if child.Type == CommandNode && child.Tag.Default != "" {
|
||||
if defaultNode != nil {
|
||||
return fmt.Errorf("can't have more than one default command under %s", node.Summary())
|
||||
}
|
||||
defaultNode = &Path{
|
||||
Parent: child,
|
||||
Command: child,
|
||||
Flags: child.Flags,
|
||||
}
|
||||
}
|
||||
}
|
||||
if defaultNode != nil {
|
||||
c.Path = append(c.Path, defaultNode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resolve walks through the traced path, applying resolvers to any unset flags.
|
||||
func (c *Context) Resolve() error {
|
||||
resolvers := c.combineResolvers()
|
||||
if len(resolvers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
inserted := []*Path{}
|
||||
for _, path := range c.Path {
|
||||
for _, flag := range path.Flags {
|
||||
// Flag has already been set on the command-line.
|
||||
if _, ok := c.values[flag.Value]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Pick the last resolved value.
|
||||
var selected interface{}
|
||||
for _, resolver := range resolvers {
|
||||
s, err := resolver.Resolve(c, path, flag)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, flag.ShortSummary())
|
||||
}
|
||||
if s == nil {
|
||||
continue
|
||||
}
|
||||
selected = s
|
||||
}
|
||||
|
||||
if selected == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
scan := Scan().PushTyped(selected, FlagValueToken)
|
||||
delete(c.values, flag.Value)
|
||||
err := flag.Parse(scan, c.getValue(flag.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
inserted = append(inserted, &Path{
|
||||
Flag: flag,
|
||||
Resolved: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
c.Path = append(inserted, c.Path...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Combine application-level resolvers and context resolvers.
|
||||
func (c *Context) combineResolvers() []Resolver {
|
||||
resolvers := []Resolver{}
|
||||
resolvers = append(resolvers, c.Kong.resolvers...)
|
||||
resolvers = append(resolvers, c.resolvers...)
|
||||
return resolvers
|
||||
}
|
||||
|
||||
func (c *Context) getValue(value *Value) reflect.Value {
|
||||
v, ok := c.values[value]
|
||||
if !ok {
|
||||
v = reflect.New(value.Target.Type()).Elem()
|
||||
c.values[value] = v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// ApplyDefaults if they are not already set.
|
||||
func (c *Context) ApplyDefaults() error {
|
||||
return Visit(c.Model.Node, func(node Visitable, next Next) error {
|
||||
var value *Value
|
||||
switch node := node.(type) {
|
||||
case *Flag:
|
||||
value = node.Value
|
||||
case *Node:
|
||||
value = node.Argument
|
||||
case *Value:
|
||||
value = node
|
||||
default:
|
||||
}
|
||||
if value != nil {
|
||||
if err := value.ApplyDefault(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
}
|
||||
|
||||
// Apply traced context to the target grammar.
|
||||
func (c *Context) Apply() (string, error) {
|
||||
path := []string{}
|
||||
|
||||
for _, trace := range c.Path {
|
||||
var value *Value
|
||||
switch {
|
||||
case trace.App != nil:
|
||||
case trace.Argument != nil:
|
||||
path = append(path, "<"+trace.Argument.Name+">")
|
||||
value = trace.Argument.Argument
|
||||
case trace.Command != nil:
|
||||
path = append(path, trace.Command.Name)
|
||||
case trace.Flag != nil:
|
||||
value = trace.Flag.Value
|
||||
case trace.Positional != nil:
|
||||
path = append(path, "<"+trace.Positional.Name+">")
|
||||
value = trace.Positional
|
||||
default:
|
||||
panic("unsupported path ?!")
|
||||
}
|
||||
if value != nil {
|
||||
value.Apply(c.getValue(value))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(path, " "), nil
|
||||
}
|
||||
|
||||
func (c *Context) parseFlag(flags []*Flag, match string) (err error) {
|
||||
defer catch(&err)
|
||||
candidates := []string{}
|
||||
for _, flag := range flags {
|
||||
long := "--" + flag.Name
|
||||
short := "-" + string(flag.Short)
|
||||
candidates = append(candidates, long)
|
||||
if flag.Short != 0 {
|
||||
candidates = append(candidates, short)
|
||||
}
|
||||
if short != match && long != match {
|
||||
continue
|
||||
}
|
||||
// Found a matching flag.
|
||||
c.scan.Pop()
|
||||
err := flag.Parse(c.scan, c.getValue(flag.Value))
|
||||
if err != nil {
|
||||
if e, ok := errors.Cause(err).(*expectedError); ok && e.token.InferredType().IsAny(FlagToken, ShortFlagToken) {
|
||||
return errors.Errorf("%s; perhaps try %s=%q?", err, flag.ShortSummary(), e.token)
|
||||
}
|
||||
return err
|
||||
}
|
||||
c.Path = append(c.Path, &Path{Flag: flag})
|
||||
return nil
|
||||
}
|
||||
return findPotentialCandidates(match, candidates, "unknown flag %s", match)
|
||||
}
|
||||
|
||||
// RunNode calls the Run() method on an arbitrary node.
|
||||
//
|
||||
// This is useful in conjunction with Visit(), for dynamically running commands.
|
||||
//
|
||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||
// all parent nodes in the command structure will be bound.
|
||||
func (c *Context) RunNode(node *Node, binds ...interface{}) (err error) {
|
||||
type targetMethod struct {
|
||||
node *Node
|
||||
method reflect.Value
|
||||
binds bindings
|
||||
}
|
||||
methodBinds := c.Kong.bindings.clone().add(binds...).add(c).merge(c.bindings)
|
||||
methods := []targetMethod{}
|
||||
for i := 0; node != nil; i, node = i+1, node.Parent {
|
||||
method := getMethod(node.Target, "Run")
|
||||
methodBinds = methodBinds.clone()
|
||||
for p := node; p != nil; p = p.Parent {
|
||||
methodBinds = methodBinds.add(p.Target.Addr().Interface())
|
||||
}
|
||||
if method.IsValid() {
|
||||
methods = append(methods, targetMethod{node, method, methodBinds})
|
||||
}
|
||||
}
|
||||
if len(methods) == 0 {
|
||||
return fmt.Errorf("no Run() method found in hierarchy of %s", c.Selected().Summary())
|
||||
}
|
||||
_, err = c.Apply()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, method := range methods {
|
||||
if err = callMethod("Run", method.node.Target, method.method, method.binds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes the Run() method on the selected command, which must exist.
|
||||
//
|
||||
// Any passed values will be bindable to arguments of the target Run() method. Additionally,
|
||||
// all parent nodes in the command structure will be bound.
|
||||
func (c *Context) Run(binds ...interface{}) (err error) {
|
||||
defer catch(&err)
|
||||
node := c.Selected()
|
||||
if node == nil {
|
||||
return fmt.Errorf("no command selected")
|
||||
}
|
||||
return c.RunNode(node, binds...)
|
||||
}
|
||||
|
||||
// PrintUsage to Kong's stdout.
|
||||
//
|
||||
// If summary is true, a summarised version of the help will be output.
|
||||
func (c *Context) PrintUsage(summary bool) error {
|
||||
options := c.helpOptions
|
||||
options.Summary = summary
|
||||
_ = c.help(options, c)
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkMissingFlags(flags []*Flag) error {
|
||||
missing := []string{}
|
||||
for _, flag := range flags {
|
||||
if !flag.Required || flag.Set {
|
||||
continue
|
||||
}
|
||||
missing = append(missing, flag.Summary())
|
||||
}
|
||||
if len(missing) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("missing flags: %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
func checkMissingChildren(node *Node) error {
|
||||
missing := []string{}
|
||||
|
||||
missingArgs := []string{}
|
||||
for _, arg := range node.Positional {
|
||||
if arg.Required && !arg.Set {
|
||||
missingArgs = append(missingArgs, arg.Summary())
|
||||
}
|
||||
}
|
||||
if len(missingArgs) > 0 {
|
||||
missing = append(missing, strconv.Quote(strings.Join(missingArgs, " ")))
|
||||
}
|
||||
|
||||
haveDefault := 0
|
||||
for _, child := range node.Children {
|
||||
if child.Hidden {
|
||||
continue
|
||||
}
|
||||
if child.Argument != nil {
|
||||
if !child.Argument.Required {
|
||||
continue
|
||||
}
|
||||
missing = append(missing, strconv.Quote(child.Summary()))
|
||||
} else {
|
||||
if child.Tag.Default != "" {
|
||||
if len(child.Children) > 0 {
|
||||
return fmt.Errorf("default command %s must not have subcommands or arguments", child.Summary())
|
||||
}
|
||||
haveDefault++
|
||||
}
|
||||
missing = append(missing, strconv.Quote(child.Name))
|
||||
}
|
||||
}
|
||||
if haveDefault > 1 {
|
||||
return fmt.Errorf("more than one default command found under %s", node.Summary())
|
||||
}
|
||||
if len(missing) == 0 || haveDefault > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(missing) > 5 {
|
||||
missing = append(missing[:5], "...")
|
||||
}
|
||||
if len(missing) == 1 {
|
||||
return fmt.Errorf("expected %s", missing[0])
|
||||
}
|
||||
return fmt.Errorf("expected one of %s", strings.Join(missing, ", "))
|
||||
}
|
||||
|
||||
// If we're missing any positionals and they're required, return an error.
|
||||
func checkMissingPositionals(positional int, values []*Value) error {
|
||||
// All the positionals are in.
|
||||
if positional >= len(values) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// We're low on supplied positionals, but the missing one is optional.
|
||||
if !values[positional].Required {
|
||||
return nil
|
||||
}
|
||||
|
||||
missing := []string{}
|
||||
for ; positional < len(values); positional++ {
|
||||
missing = append(missing, "<"+values[positional].Name+">")
|
||||
}
|
||||
return fmt.Errorf("missing positional arguments %s", strings.Join(missing, " "))
|
||||
}
|
||||
|
||||
func checkEnum(value *Value, target reflect.Value) error {
|
||||
switch target.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
for i := 0; i < target.Len(); i++ {
|
||||
if err := checkEnum(value, target.Index(i)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case reflect.Map, reflect.Struct:
|
||||
return errors.Errorf("enum can only be applied to a slice or value")
|
||||
|
||||
default:
|
||||
enumMap := value.EnumMap()
|
||||
v := fmt.Sprintf("%v", target)
|
||||
if enumMap[v] {
|
||||
return nil
|
||||
}
|
||||
enums := []string{}
|
||||
for enum := range enumMap {
|
||||
enums = append(enums, fmt.Sprintf("%q", enum))
|
||||
}
|
||||
sort.Strings(enums)
|
||||
return fmt.Errorf("%s must be one of %s but got %q", value.ShortSummary(), strings.Join(enums, ","), target.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
func checkXorDuplicates(paths []*Path) error {
|
||||
for _, path := range paths {
|
||||
seen := map[string]*Flag{}
|
||||
for _, flag := range path.Flags {
|
||||
if !flag.Set {
|
||||
continue
|
||||
}
|
||||
if flag.Xor == "" {
|
||||
continue
|
||||
}
|
||||
if seen[flag.Xor] != nil {
|
||||
return fmt.Errorf("--%s and --%s can't be used together", seen[flag.Xor].Name, flag.Name)
|
||||
}
|
||||
seen[flag.Xor] = flag
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func findPotentialCandidates(needle string, haystack []string, format string, args ...interface{}) error {
|
||||
if len(haystack) == 0 {
|
||||
return fmt.Errorf(format, args...)
|
||||
}
|
||||
closestCandidates := []string{}
|
||||
for _, candidate := range haystack {
|
||||
if strings.HasPrefix(candidate, needle) || levenshtein(candidate, needle) <= 2 {
|
||||
closestCandidates = append(closestCandidates, fmt.Sprintf("%q", candidate))
|
||||
}
|
||||
}
|
||||
prefix := fmt.Sprintf(format, args...)
|
||||
if len(closestCandidates) == 1 {
|
||||
return fmt.Errorf("%s, did you mean %s?", prefix, closestCandidates[0])
|
||||
} else if len(closestCandidates) > 1 {
|
||||
return fmt.Errorf("%s, did you mean one of %s?", prefix, strings.Join(closestCandidates, ", "))
|
||||
}
|
||||
return fmt.Errorf("%s", prefix)
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package kong
|
||||
|
||||
// ApplyDefaults if they are not already set.
|
||||
func ApplyDefaults(target interface{}, options ...Option) error {
|
||||
app, err := New(target, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx, err := Trace(app, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ctx.Resolve()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ctx.ApplyDefaults(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Validate()
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
// Package kong aims to support arbitrarily complex command-line structures with as little developer effort as possible.
|
||||
//
|
||||
// Here's an example:
|
||||
//
|
||||
// shell rm [-f] [-r] <paths> ...
|
||||
// shell ls [<paths> ...]
|
||||
//
|
||||
// This can be represented by the following command-line structure:
|
||||
//
|
||||
// package main
|
||||
//
|
||||
// import "github.com/alecthomas/kong"
|
||||
//
|
||||
// var CLI struct {
|
||||
// Rm struct {
|
||||
// Force bool `short:"f" help:"Force removal."`
|
||||
// Recursive bool `short:"r" help:"Recursively remove files."`
|
||||
//
|
||||
// Paths []string `arg help:"Paths to remove." type:"path"`
|
||||
// } `cmd help:"Remove files."`
|
||||
//
|
||||
// Ls struct {
|
||||
// Paths []string `arg optional help:"Paths to list." type:"path"`
|
||||
// } `cmd help:"List paths."`
|
||||
// }
|
||||
//
|
||||
// func main() {
|
||||
// kong.Parse(&CLI)
|
||||
// }
|
||||
//
|
||||
// See https://github.com/alecthomas/kong for details.
|
||||
package kong
|
|
@ -0,0 +1,12 @@
|
|||
package kong
|
||||
|
||||
// ParseError is the error type returned by Kong.Parse().
|
||||
//
|
||||
// It contains the parse Context that triggered the error.
|
||||
type ParseError struct {
|
||||
error
|
||||
Context *Context
|
||||
}
|
||||
|
||||
// Cause returns the original cause of the error.
|
||||
func (p *ParseError) Cause() error { return p.error }
|
|
@ -0,0 +1,16 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// Parse constructs a new parser and parses the default command-line.
|
||||
func Parse(cli interface{}, options ...Option) *Context {
|
||||
parser, err := New(cli, options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
ctx, err := parser.Parse(os.Args[1:])
|
||||
parser.FatalIfErrorf(err)
|
||||
return ctx
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
module github.com/alecthomas/kong
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/testify v1.2.2
|
||||
)
|
||||
|
||||
go 1.13
|
|
@ -0,0 +1,8 @@
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
|
@ -0,0 +1,9 @@
|
|||
// +build appengine !linux,!freebsd,!darwin,!dragonfly,!netbsd,!openbsd
|
||||
|
||||
package kong
|
||||
|
||||
import "io"
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
return 80
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
// +build !appengine,linux freebsd darwin dragonfly netbsd openbsd
|
||||
|
||||
package kong
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func guessWidth(w io.Writer) int {
|
||||
// check if COLUMNS env is set to comply with
|
||||
// http://pubs.opengroup.org/onlinepubs/009604499/basedefs/xbd_chap08.html
|
||||
colsStr := os.Getenv("COLUMNS")
|
||||
if colsStr != "" {
|
||||
if cols, err := strconv.Atoi(colsStr); err == nil {
|
||||
return cols
|
||||
}
|
||||
}
|
||||
|
||||
if t, ok := w.(*os.File); ok {
|
||||
fd := t.Fd()
|
||||
var dimensions [4]uint16
|
||||
|
||||
if _, _, err := syscall.Syscall6(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(fd), // nolint: unconvert
|
||||
uintptr(syscall.TIOCGWINSZ),
|
||||
uintptr(unsafe.Pointer(&dimensions)), // nolint: gas
|
||||
0, 0, 0,
|
||||
); err == 0 {
|
||||
if dimensions[1] == 0 {
|
||||
return 80
|
||||
}
|
||||
return int(dimensions[1])
|
||||
}
|
||||
}
|
||||
return 80
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/doc"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIndent = 2
|
||||
defaultColumnPadding = 4
|
||||
)
|
||||
|
||||
// Help flag.
|
||||
type helpValue bool
|
||||
|
||||
func (h helpValue) BeforeApply(ctx *Context) error {
|
||||
options := ctx.Kong.helpOptions
|
||||
options.Summary = false
|
||||
err := ctx.Kong.help(options, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.Kong.Exit(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HelpOptions for HelpPrinters.
|
||||
type HelpOptions struct {
|
||||
// Don't print top-level usage summary.
|
||||
NoAppSummary bool
|
||||
|
||||
// Write a one-line summary of the context.
|
||||
Summary bool
|
||||
|
||||
// Write help in a more compact, but still fully-specified, form.
|
||||
Compact bool
|
||||
|
||||
// Tree writes command chains in a tree structure instead of listing them separately.
|
||||
Tree bool
|
||||
|
||||
// Indenter modulates the given prefix for the next layer in the tree view.
|
||||
// The following exported templates can be used: kong.SpaceIndenter, kong.LineIndenter, kong.TreeIndenter
|
||||
// The kong.SpaceIndenter will be used by default.
|
||||
Indenter HelpIndenter
|
||||
}
|
||||
|
||||
// Apply options to Kong as a configuration option.
|
||||
func (h HelpOptions) Apply(k *Kong) error {
|
||||
k.helpOptions = h
|
||||
return nil
|
||||
}
|
||||
|
||||
// HelpProvider can be implemented by commands/args to provide detailed help.
|
||||
type HelpProvider interface {
|
||||
// This string is formatted by go/doc and thus has the same formatting rules.
|
||||
Help() string
|
||||
}
|
||||
|
||||
// HelpIndenter is used to indent new layers in the help tree.
|
||||
type HelpIndenter func(prefix string) string
|
||||
|
||||
// HelpPrinter is used to print context-sensitive help.
|
||||
type HelpPrinter func(options HelpOptions, ctx *Context) error
|
||||
|
||||
// HelpValueFormatter is used to format the help text of flags and positional arguments.
|
||||
type HelpValueFormatter func(value *Value) string
|
||||
|
||||
// DefaultHelpValueFormatter is the default HelpValueFormatter.
|
||||
func DefaultHelpValueFormatter(value *Value) string {
|
||||
if value.Tag.Env == "" {
|
||||
return value.Help
|
||||
}
|
||||
suffix := "($" + value.Tag.Env + ")"
|
||||
switch {
|
||||
case strings.HasSuffix(value.Help, "."):
|
||||
return value.Help[:len(value.Help)-1] + " " + suffix + "."
|
||||
case value.Help == "":
|
||||
return suffix
|
||||
default:
|
||||
return value.Help + " " + suffix
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultHelpPrinter is the default HelpPrinter.
|
||||
func DefaultHelpPrinter(options HelpOptions, ctx *Context) error {
|
||||
if ctx.Empty() {
|
||||
options.Summary = false
|
||||
}
|
||||
w := newHelpWriter(ctx, options)
|
||||
selected := ctx.Selected()
|
||||
if selected == nil {
|
||||
printApp(w, ctx.Model)
|
||||
} else {
|
||||
printCommand(w, ctx.Model, selected)
|
||||
}
|
||||
return w.Write(ctx.Stdout)
|
||||
}
|
||||
|
||||
func printApp(w *helpWriter, app *Application) {
|
||||
if !w.NoAppSummary {
|
||||
w.Printf("Usage: %s%s", app.Name, app.Summary())
|
||||
}
|
||||
printNodeDetail(w, app.Node, true)
|
||||
cmds := app.Leaves(true)
|
||||
if len(cmds) > 0 && app.HelpFlag != nil {
|
||||
w.Print("")
|
||||
if w.Summary {
|
||||
w.Printf(`Run "%s --help" for more information.`, app.Name)
|
||||
} else {
|
||||
w.Printf(`Run "%s <command> --help" for more information on a command.`, app.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func printCommand(w *helpWriter, app *Application, cmd *Command) {
|
||||
if !w.NoAppSummary {
|
||||
w.Printf("Usage: %s %s", app.Name, cmd.Summary())
|
||||
}
|
||||
printNodeDetail(w, cmd, true)
|
||||
if w.Summary && app.HelpFlag != nil {
|
||||
w.Print("")
|
||||
w.Printf(`Run "%s --help" for more information.`, cmd.FullPath())
|
||||
}
|
||||
}
|
||||
|
||||
func printNodeDetail(w *helpWriter, node *Node, hide bool) {
|
||||
if node.Help != "" {
|
||||
w.Print("")
|
||||
w.Wrap(node.Help)
|
||||
}
|
||||
if w.Summary {
|
||||
return
|
||||
}
|
||||
if node.Detail != "" {
|
||||
w.Print("")
|
||||
w.Wrap(node.Detail)
|
||||
}
|
||||
if len(node.Positional) > 0 {
|
||||
w.Print("")
|
||||
w.Print("Arguments:")
|
||||
writePositionals(w.Indent(), node.Positional)
|
||||
}
|
||||
if flags := node.AllFlags(true); len(flags) > 0 {
|
||||
w.Print("")
|
||||
w.Print("Flags:")
|
||||
writeFlags(w.Indent(), flags)
|
||||
}
|
||||
cmds := node.Leaves(hide)
|
||||
if len(cmds) > 0 {
|
||||
w.Print("")
|
||||
w.Print("Commands:")
|
||||
if w.Tree {
|
||||
writeCommandTree(w, node)
|
||||
} else {
|
||||
iw := w.Indent()
|
||||
if w.Compact {
|
||||
writeCompactCommandList(cmds, iw)
|
||||
} else {
|
||||
writeCommandList(cmds, iw)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeCommandList(cmds []*Node, iw *helpWriter) {
|
||||
for i, cmd := range cmds {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
printCommandSummary(iw, cmd)
|
||||
if i != len(cmds)-1 {
|
||||
iw.Print("")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeCompactCommandList(cmds []*Node, iw *helpWriter) {
|
||||
rows := [][2]string{}
|
||||
for _, cmd := range cmds {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, [2]string{cmd.Path(), cmd.Help})
|
||||
}
|
||||
writeTwoColumns(iw, rows)
|
||||
}
|
||||
|
||||
func writeCommandTree(w *helpWriter, node *Node) {
|
||||
iw := w.Indent()
|
||||
rows := make([][2]string, 0, len(node.Children)*2)
|
||||
for i, cmd := range node.Children {
|
||||
if cmd.Hidden {
|
||||
continue
|
||||
}
|
||||
rows = append(rows, w.CommandTree(cmd, "")...)
|
||||
if i != len(node.Children)-1 {
|
||||
rows = append(rows, [2]string{"", ""})
|
||||
}
|
||||
}
|
||||
writeTwoColumns(iw, rows)
|
||||
}
|
||||
|
||||
// nolint: unused
|
||||
type helpCommandGroup struct {
|
||||
Name string
|
||||
Commands []*Node
|
||||
}
|
||||
|
||||
// nolint: unused, deadcode
|
||||
func collectCommandGroups(nodes []*Node) []helpCommandGroup {
|
||||
groups := map[string][]*Node{}
|
||||
for _, node := range nodes {
|
||||
groups[node.Group] = append(groups[node.Group], node)
|
||||
}
|
||||
out := []helpCommandGroup{}
|
||||
for name, nodes := range groups {
|
||||
if name == "" {
|
||||
name = "Commands"
|
||||
}
|
||||
out = append(out, helpCommandGroup{Name: name, Commands: nodes})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func printCommandSummary(w *helpWriter, cmd *Command) {
|
||||
w.Print(cmd.Summary())
|
||||
if cmd.Help != "" {
|
||||
w.Indent().Wrap(cmd.Help)
|
||||
}
|
||||
}
|
||||
|
||||
type helpWriter struct {
|
||||
indent string
|
||||
width int
|
||||
lines *[]string
|
||||
helpFormatter HelpValueFormatter
|
||||
HelpOptions
|
||||
}
|
||||
|
||||
func newHelpWriter(ctx *Context, options HelpOptions) *helpWriter {
|
||||
lines := []string{}
|
||||
w := &helpWriter{
|
||||
indent: "",
|
||||
width: guessWidth(ctx.Stdout),
|
||||
lines: &lines,
|
||||
helpFormatter: ctx.Kong.helpFormatter,
|
||||
HelpOptions: options,
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (h *helpWriter) Printf(format string, args ...interface{}) {
|
||||
h.Print(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
func (h *helpWriter) Print(text string) {
|
||||
*h.lines = append(*h.lines, strings.TrimRight(h.indent+text, " "))
|
||||
}
|
||||
|
||||
func (h *helpWriter) Indent() *helpWriter {
|
||||
return &helpWriter{indent: h.indent + " ", lines: h.lines, width: h.width - 2, HelpOptions: h.HelpOptions, helpFormatter: h.helpFormatter}
|
||||
}
|
||||
|
||||
func (h *helpWriter) String() string {
|
||||
return strings.Join(*h.lines, "\n")
|
||||
}
|
||||
|
||||
func (h *helpWriter) Write(w io.Writer) error {
|
||||
for _, line := range *h.lines {
|
||||
_, err := io.WriteString(w, line+"\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *helpWriter) Wrap(text string) {
|
||||
w := bytes.NewBuffer(nil)
|
||||
doc.ToText(w, strings.TrimSpace(text), "", " ", h.width)
|
||||
for _, line := range strings.Split(strings.TrimSpace(w.String()), "\n") {
|
||||
h.Print(line)
|
||||
}
|
||||
}
|
||||
|
||||
func writePositionals(w *helpWriter, args []*Positional) {
|
||||
rows := [][2]string{}
|
||||
for _, arg := range args {
|
||||
rows = append(rows, [2]string{arg.Summary(), w.helpFormatter(arg)})
|
||||
}
|
||||
writeTwoColumns(w, rows)
|
||||
}
|
||||
|
||||
func writeFlags(w *helpWriter, groups [][]*Flag) {
|
||||
rows := [][2]string{}
|
||||
haveShort := false
|
||||
for _, group := range groups {
|
||||
for _, flag := range group {
|
||||
if flag.Short != 0 {
|
||||
haveShort = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, group := range groups {
|
||||
if i > 0 {
|
||||
rows = append(rows, [2]string{"", ""})
|
||||
}
|
||||
for _, flag := range group {
|
||||
if !flag.Hidden {
|
||||
rows = append(rows, [2]string{formatFlag(haveShort, flag), w.helpFormatter(flag.Value)})
|
||||
}
|
||||
}
|
||||
}
|
||||
writeTwoColumns(w, rows)
|
||||
}
|
||||
|
||||
func writeTwoColumns(w *helpWriter, rows [][2]string) {
|
||||
maxLeft := 375 * w.width / 1000
|
||||
if maxLeft < 30 {
|
||||
maxLeft = 30
|
||||
}
|
||||
// Find size of first column.
|
||||
leftSize := 0
|
||||
for _, row := range rows {
|
||||
if c := len(row[0]); c > leftSize && c < maxLeft {
|
||||
leftSize = c
|
||||
}
|
||||
}
|
||||
|
||||
offsetStr := strings.Repeat(" ", leftSize+defaultColumnPadding)
|
||||
|
||||
for _, row := range rows {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
doc.ToText(buf, row[1], "", strings.Repeat(" ", defaultIndent), w.width-leftSize-defaultColumnPadding)
|
||||
lines := strings.Split(strings.TrimRight(buf.String(), "\n"), "\n")
|
||||
|
||||
line := fmt.Sprintf("%-*s", leftSize, row[0])
|
||||
if len(row[0]) < maxLeft {
|
||||
line += fmt.Sprintf("%*s%s", defaultColumnPadding, "", lines[0])
|
||||
lines = lines[1:]
|
||||
}
|
||||
w.Print(line)
|
||||
for _, line := range lines {
|
||||
w.Printf("%s%s", offsetStr, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// haveShort will be true if there are short flags present at all in the help. Useful for column alignment.
|
||||
func formatFlag(haveShort bool, flag *Flag) string {
|
||||
flagString := ""
|
||||
name := flag.Name
|
||||
isBool := flag.IsBool()
|
||||
if flag.Short != 0 {
|
||||
flagString += fmt.Sprintf("-%c, --%s", flag.Short, name)
|
||||
} else {
|
||||
if haveShort {
|
||||
flagString += fmt.Sprintf(" --%s", name)
|
||||
} else {
|
||||
flagString += fmt.Sprintf("--%s", name)
|
||||
}
|
||||
}
|
||||
if !isBool {
|
||||
flagString += fmt.Sprintf("=%s", flag.FormatPlaceHolder())
|
||||
}
|
||||
return flagString
|
||||
}
|
||||
|
||||
// CommandTree creates a tree with the given node name as root and its children's arguments and sub commands as leaves.
|
||||
func (h *HelpOptions) CommandTree(node *Node, prefix string) (rows [][2]string) {
|
||||
var nodeName string
|
||||
switch node.Type {
|
||||
default:
|
||||
nodeName += prefix + node.Name
|
||||
case ArgumentNode:
|
||||
nodeName += prefix + "<" + node.Name + ">"
|
||||
}
|
||||
rows = append(rows, [2]string{nodeName, node.Help})
|
||||
if h.Indenter == nil {
|
||||
prefix = SpaceIndenter(prefix)
|
||||
} else {
|
||||
prefix = h.Indenter(prefix)
|
||||
}
|
||||
for _, arg := range node.Positional {
|
||||
rows = append(rows, [2]string{prefix + arg.Summary(), arg.Help})
|
||||
}
|
||||
for _, subCmd := range node.Children {
|
||||
rows = append(rows, h.CommandTree(subCmd, prefix)...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SpaceIndenter adds a space indent to the given prefix.
|
||||
func SpaceIndenter(prefix string) string {
|
||||
return prefix + strings.Repeat(" ", defaultIndent)
|
||||
}
|
||||
|
||||
// LineIndenter adds line points to every new indent.
|
||||
func LineIndenter(prefix string) string {
|
||||
if prefix == "" {
|
||||
return "- "
|
||||
}
|
||||
return strings.Repeat(" ", defaultIndent) + prefix
|
||||
}
|
||||
|
||||
// TreeIndenter adds line points to every new indent and vertical lines to every layer.
|
||||
func TreeIndenter(prefix string) string {
|
||||
if prefix == "" {
|
||||
return "|- "
|
||||
}
|
||||
return "|" + strings.Repeat(" ", defaultIndent) + prefix
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package kong
|
||||
|
||||
// BeforeResolve is a documentation-only interface describing hooks that run before values are set.
|
||||
type BeforeResolve interface {
|
||||
// This is not the correct signature - see README for details.
|
||||
BeforeResolve(args ...interface{}) error
|
||||
}
|
||||
|
||||
// BeforeApply is a documentation-only interface describing hooks that run before values are set.
|
||||
type BeforeApply interface {
|
||||
// This is not the correct signature - see README for details.
|
||||
BeforeApply(args ...interface{}) error
|
||||
}
|
||||
|
||||
// AfterApply is a documentation-only interface describing hooks that run after values are set.
|
||||
type AfterApply interface {
|
||||
// This is not the correct signature - see README for details.
|
||||
AfterApply(args ...interface{}) error
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var interpolationRegex = regexp.MustCompile(`((?:\${([[:alpha:]_][[:word:]]*))(?:=([^}]+))?})|(\$)|([^$]+)`)
|
||||
|
||||
// Interpolate variables from vars into s for substrings in the form ${var} or ${var=default}.
|
||||
func interpolate(s string, vars Vars, updatedVars map[string]string) (string, error) {
|
||||
out := ""
|
||||
matches := interpolationRegex.FindAllStringSubmatch(s, -1)
|
||||
if len(matches) == 0 {
|
||||
return s, nil
|
||||
}
|
||||
for key, val := range updatedVars {
|
||||
if vars[key] != val {
|
||||
vars = vars.CloneWith(updatedVars)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, match := range matches {
|
||||
if name := match[2]; name != "" {
|
||||
value, ok := vars[name]
|
||||
if !ok {
|
||||
// No default value.
|
||||
if match[3] == "" {
|
||||
return "", fmt.Errorf("undefined variable ${%s}", name)
|
||||
}
|
||||
value = match[3]
|
||||
}
|
||||
out += value
|
||||
} else {
|
||||
out += match[0]
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
|
@ -0,0 +1,368 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
callbackReturnSignature = reflect.TypeOf((*error)(nil)).Elem()
|
||||
)
|
||||
|
||||
// Error reported by Kong.
|
||||
type Error struct{ msg string }
|
||||
|
||||
func (e Error) Error() string { return e.msg }
|
||||
|
||||
func fail(format string, args ...interface{}) {
|
||||
panic(Error{msg: fmt.Sprintf(format, args...)})
|
||||
}
|
||||
|
||||
// Must creates a new Parser or panics if there is an error.
|
||||
func Must(ast interface{}, options ...Option) *Kong {
|
||||
k, err := New(ast, options...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
// Kong is the main parser type.
|
||||
type Kong struct {
|
||||
// Grammar model.
|
||||
Model *Application
|
||||
|
||||
// Termination function (defaults to os.Exit)
|
||||
Exit func(int)
|
||||
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
|
||||
bindings bindings
|
||||
loader ConfigurationLoader
|
||||
resolvers []Resolver
|
||||
registry *Registry
|
||||
|
||||
noDefaultHelp bool
|
||||
usageOnError bool
|
||||
help HelpPrinter
|
||||
helpFormatter HelpValueFormatter
|
||||
helpOptions HelpOptions
|
||||
helpFlag *Flag
|
||||
vars Vars
|
||||
|
||||
// Set temporarily by Options. These are applied after build().
|
||||
postBuildOptions []Option
|
||||
}
|
||||
|
||||
// New creates a new Kong parser on grammar.
|
||||
//
|
||||
// See the README (https://github.com/alecthomas/kong) for usage instructions.
|
||||
func New(grammar interface{}, options ...Option) (*Kong, error) {
|
||||
k := &Kong{
|
||||
Exit: os.Exit,
|
||||
Stdout: os.Stdout,
|
||||
Stderr: os.Stderr,
|
||||
registry: NewRegistry().RegisterDefaults(),
|
||||
vars: Vars{},
|
||||
bindings: bindings{},
|
||||
helpFormatter: DefaultHelpValueFormatter,
|
||||
}
|
||||
|
||||
options = append(options, Bind(k))
|
||||
|
||||
for _, option := range options {
|
||||
if err := option.Apply(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if k.help == nil {
|
||||
k.help = DefaultHelpPrinter
|
||||
}
|
||||
|
||||
model, err := build(k, grammar)
|
||||
if err != nil {
|
||||
return k, err
|
||||
}
|
||||
model.Name = filepath.Base(os.Args[0])
|
||||
k.Model = model
|
||||
k.Model.HelpFlag = k.helpFlag
|
||||
|
||||
for _, option := range k.postBuildOptions {
|
||||
if err = option.Apply(k); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
k.postBuildOptions = nil
|
||||
|
||||
if err = k.interpolate(k.Model.Node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.bindings.add(k.vars)
|
||||
|
||||
return k, nil
|
||||
}
|
||||
|
||||
type varStack []Vars
|
||||
|
||||
func (v *varStack) head() Vars { return (*v)[len(*v)-1] }
|
||||
func (v *varStack) pop() { *v = (*v)[:len(*v)-1] }
|
||||
func (v *varStack) push(vars Vars) Vars {
|
||||
if len(*v) != 0 {
|
||||
vars = (*v)[len(*v)-1].CloneWith(vars)
|
||||
}
|
||||
*v = append(*v, vars)
|
||||
return vars
|
||||
}
|
||||
|
||||
// Interpolate variables into model.
|
||||
func (k *Kong) interpolate(node *Node) (err error) {
|
||||
stack := varStack{}
|
||||
return Visit(node, func(node Visitable, next Next) error {
|
||||
switch node := node.(type) {
|
||||
case *Node:
|
||||
vars := stack.push(node.Vars())
|
||||
node.Help, err = interpolate(node.Help, vars, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("help for %s: %s", node.Path(), err)
|
||||
}
|
||||
err = next(nil)
|
||||
stack.pop()
|
||||
return err
|
||||
|
||||
case *Value:
|
||||
return next(k.interpolateValue(node, stack.head()))
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
}
|
||||
|
||||
func (k *Kong) interpolateValue(value *Value, vars Vars) (err error) {
|
||||
if len(value.Tag.Vars) > 0 {
|
||||
vars = vars.CloneWith(value.Tag.Vars)
|
||||
}
|
||||
if value.Default, err = interpolate(value.Default, vars, nil); err != nil {
|
||||
return fmt.Errorf("default value for %s: %s", value.Summary(), err)
|
||||
}
|
||||
if value.Enum, err = interpolate(value.Enum, vars, nil); err != nil {
|
||||
return fmt.Errorf("enum value for %s: %s", value.Summary(), err)
|
||||
}
|
||||
value.Help, err = interpolate(value.Help, vars, map[string]string{
|
||||
"default": value.Default,
|
||||
"enum": value.Enum,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("help for %s: %s", value.Summary(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Provide additional builtin flags, if any.
|
||||
func (k *Kong) extraFlags() []*Flag {
|
||||
if k.noDefaultHelp {
|
||||
return nil
|
||||
}
|
||||
var helpTarget helpValue
|
||||
value := reflect.ValueOf(&helpTarget).Elem()
|
||||
helpFlag := &Flag{
|
||||
Short: 'h',
|
||||
Value: &Value{
|
||||
Name: "help",
|
||||
Help: "Show context-sensitive help.",
|
||||
Target: value,
|
||||
Tag: &Tag{},
|
||||
Mapper: k.registry.ForValue(value),
|
||||
DefaultValue: reflect.ValueOf(false),
|
||||
},
|
||||
}
|
||||
helpFlag.Flag = helpFlag
|
||||
k.helpFlag = helpFlag
|
||||
return []*Flag{helpFlag}
|
||||
}
|
||||
|
||||
// Parse arguments into target.
|
||||
//
|
||||
// The return Context can be used to further inspect the parsed command-line, to format help, to find the
|
||||
// selected command, to run command Run() methods, and so on. See Context and README for more information.
|
||||
//
|
||||
// Will return a ParseError if a *semantically* invalid command-line is encountered (as opposed to a syntactically
|
||||
// invalid one, which will report a normal error).
|
||||
func (k *Kong) Parse(args []string) (ctx *Context, err error) {
|
||||
defer catch(&err)
|
||||
ctx, err = Trace(k, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ctx.Error != nil {
|
||||
return nil, &ParseError{error: ctx.Error, Context: ctx}
|
||||
}
|
||||
if err = ctx.Reset(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = k.applyHook(ctx, "BeforeResolve"); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = ctx.Resolve(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = k.applyHook(ctx, "BeforeApply"); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if _, err = ctx.Apply(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = ctx.Validate(); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
if err = k.applyHook(ctx, "AfterApply"); err != nil {
|
||||
return nil, &ParseError{error: err, Context: ctx}
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (k *Kong) applyHook(ctx *Context, name string) error {
|
||||
for _, trace := range ctx.Path {
|
||||
var value reflect.Value
|
||||
switch {
|
||||
case trace.App != nil:
|
||||
value = trace.App.Target
|
||||
case trace.Argument != nil:
|
||||
value = trace.Argument.Target
|
||||
case trace.Command != nil:
|
||||
value = trace.Command.Target
|
||||
case trace.Positional != nil:
|
||||
value = trace.Positional.Target
|
||||
case trace.Flag != nil:
|
||||
value = trace.Flag.Value.Target
|
||||
default:
|
||||
panic("unsupported Path")
|
||||
}
|
||||
method := getMethod(value, name)
|
||||
if !method.IsValid() {
|
||||
continue
|
||||
}
|
||||
binds := k.bindings.clone()
|
||||
binds.add(ctx, trace)
|
||||
binds.add(trace.Node().Vars().CloneWith(k.vars))
|
||||
binds.merge(ctx.bindings)
|
||||
if err := callMethod(name, value, method, binds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Path[0] will always be the app root.
|
||||
return k.applyHookToDefaultFlags(ctx, ctx.Path[0].Node(), name)
|
||||
}
|
||||
|
||||
// Call hook on any unset flags with default values.
|
||||
func (k *Kong) applyHookToDefaultFlags(ctx *Context, node *Node, name string) error {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return Visit(node, func(n Visitable, next Next) error {
|
||||
node, ok := n.(*Node)
|
||||
if !ok {
|
||||
return next(nil)
|
||||
}
|
||||
binds := k.bindings.clone().add(ctx).add(node.Vars().CloneWith(k.vars))
|
||||
for _, flag := range node.Flags {
|
||||
if flag.Default == "" || ctx.values[flag.Value].IsValid() || !flag.Target.IsValid() {
|
||||
continue
|
||||
}
|
||||
method := getMethod(flag.Target, name)
|
||||
if !method.IsValid() {
|
||||
continue
|
||||
}
|
||||
path := &Path{Flag: flag}
|
||||
if err := callMethod(name, flag.Target, method, binds.clone().add(path)); err != nil {
|
||||
return next(err)
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
}
|
||||
|
||||
func formatMultilineMessage(w io.Writer, leaders []string, format string, args ...interface{}) {
|
||||
lines := strings.Split(fmt.Sprintf(format, args...), "\n")
|
||||
leader := ""
|
||||
for _, l := range leaders {
|
||||
if l == "" {
|
||||
continue
|
||||
}
|
||||
leader += l + ": "
|
||||
}
|
||||
fmt.Fprintf(w, "%s%s\n", leader, lines[0])
|
||||
for _, line := range lines[1:] {
|
||||
fmt.Fprintf(w, "%*s%s\n", len(leader), " ", line)
|
||||
}
|
||||
}
|
||||
|
||||
// Printf writes a message to Kong.Stdout with the application name prefixed.
|
||||
func (k *Kong) Printf(format string, args ...interface{}) *Kong {
|
||||
formatMultilineMessage(k.Stdout, []string{k.Model.Name}, format, args...)
|
||||
return k
|
||||
}
|
||||
|
||||
// Errorf writes a message to Kong.Stderr with the application name prefixed.
|
||||
func (k *Kong) Errorf(format string, args ...interface{}) *Kong {
|
||||
formatMultilineMessage(k.Stderr, []string{k.Model.Name, "error"}, format, args...)
|
||||
return k
|
||||
}
|
||||
|
||||
// Fatalf writes a message to Kong.Stderr with the application name prefixed then exits with a non-zero status.
|
||||
func (k *Kong) Fatalf(format string, args ...interface{}) {
|
||||
k.Errorf(format, args...)
|
||||
k.Exit(1)
|
||||
}
|
||||
|
||||
// FatalIfErrorf terminates with an error message if err != nil.
|
||||
func (k *Kong) FatalIfErrorf(err error, args ...interface{}) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
msg := err.Error()
|
||||
if len(args) > 0 {
|
||||
msg = fmt.Sprintf(args[0].(string), args[1:]...) + ": " + err.Error()
|
||||
}
|
||||
// Maybe display usage information.
|
||||
if err, ok := err.(*ParseError); ok && k.usageOnError {
|
||||
options := k.helpOptions
|
||||
_ = k.help(options, err.Context)
|
||||
fmt.Fprintln(k.Stdout)
|
||||
}
|
||||
k.Errorf("%s", msg)
|
||||
k.Exit(1)
|
||||
}
|
||||
|
||||
// LoadConfig from path using the loader configured via Configuration(loader).
|
||||
//
|
||||
// "path" will have ~ and any variables expanded.
|
||||
func (k *Kong) LoadConfig(path string) (Resolver, error) {
|
||||
var err error
|
||||
path = ExpandPath(path)
|
||||
path, err = interpolate(path, k.vars, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := os.Open(path) // nolint: gas
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
return k.loader(r)
|
||||
}
|
||||
|
||||
func catch(err *error) {
|
||||
msg := recover()
|
||||
if test, ok := msg.(Error); ok {
|
||||
*err = test
|
||||
} else if msg != nil {
|
||||
panic(msg)
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
@ -0,0 +1,39 @@
|
|||
package kong
|
||||
|
||||
import "unicode/utf8"
|
||||
|
||||
// https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Go
|
||||
// License: https://creativecommons.org/licenses/by-sa/3.0/
|
||||
func levenshtein(a, b string) int {
|
||||
f := make([]int, utf8.RuneCountInString(b)+1)
|
||||
|
||||
for j := range f {
|
||||
f[j] = j
|
||||
}
|
||||
|
||||
for _, ca := range a {
|
||||
j := 1
|
||||
fj1 := f[0] // fj1 is the value of f[j - 1] in last iteration
|
||||
f[0]++
|
||||
for _, cb := range b {
|
||||
mn := min(f[j]+1, f[j-1]+1) // delete & insert
|
||||
if cb != ca {
|
||||
mn = min(mn, fj1+1) // change
|
||||
} else {
|
||||
mn = min(mn, fj1) // matched
|
||||
}
|
||||
|
||||
fj1, f[j] = f[j], mn // save f[j] to fj1(j is about to increase), update f[j] to mn
|
||||
j++
|
||||
}
|
||||
}
|
||||
|
||||
return f[len(f)-1]
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a <= b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
|
@ -0,0 +1,727 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/bits"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
mapperValueType = reflect.TypeOf((*MapperValue)(nil)).Elem()
|
||||
boolMapperType = reflect.TypeOf((*BoolMapper)(nil)).Elem()
|
||||
textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
binaryUnmarshalerType = reflect.TypeOf((*encoding.BinaryUnmarshaler)(nil)).Elem()
|
||||
)
|
||||
|
||||
// DecodeContext is passed to a Mapper's Decode().
|
||||
//
|
||||
// It contains the Value being decoded into and the Scanner to parse from.
|
||||
type DecodeContext struct {
|
||||
// Value being decoded into.
|
||||
Value *Value
|
||||
// Scan contains the input to scan into Target.
|
||||
Scan *Scanner
|
||||
}
|
||||
|
||||
// WithScanner creates a clone of this context with a new Scanner.
|
||||
func (r *DecodeContext) WithScanner(scan *Scanner) *DecodeContext {
|
||||
return &DecodeContext{
|
||||
Value: r.Value,
|
||||
Scan: scan,
|
||||
}
|
||||
}
|
||||
|
||||
// MapperValue may be implemented by fields in order to provide custom mapping.
|
||||
type MapperValue interface {
|
||||
Decode(ctx *DecodeContext) error
|
||||
}
|
||||
|
||||
type mapperValueAdapter struct {
|
||||
isBool bool
|
||||
}
|
||||
|
||||
func (m *mapperValueAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Type().Implements(mapperValueType) {
|
||||
return target.Interface().(MapperValue).Decode(ctx)
|
||||
}
|
||||
return target.Addr().Interface().(MapperValue).Decode(ctx)
|
||||
}
|
||||
|
||||
func (m *mapperValueAdapter) IsBool() bool {
|
||||
return m.isBool
|
||||
}
|
||||
|
||||
type textUnmarshalerAdapter struct{}
|
||||
|
||||
func (m *textUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
err := ctx.Scan.PopValueInto("value", &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Type().Implements(textUnmarshalerType) {
|
||||
return target.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
|
||||
}
|
||||
return target.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value))
|
||||
}
|
||||
|
||||
type binaryUnmarshalerAdapter struct{}
|
||||
|
||||
func (m *binaryUnmarshalerAdapter) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
err := ctx.Scan.PopValueInto("value", &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if target.Type().Implements(textUnmarshalerType) {
|
||||
return target.Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
|
||||
}
|
||||
return target.Addr().Interface().(encoding.BinaryUnmarshaler).UnmarshalBinary([]byte(value))
|
||||
}
|
||||
|
||||
// A Mapper represents how a field is mapped from command-line values to Go.
|
||||
//
|
||||
// Mappers can be associated with concrete fields via pointer, reflect.Type, reflect.Kind, or via a "type" tag.
|
||||
//
|
||||
// Additionally, if a type implements the MappverValue interface, it will be used.
|
||||
type Mapper interface {
|
||||
// Decode ctx.Value with ctx.Scanner into target.
|
||||
Decode(ctx *DecodeContext, target reflect.Value) error
|
||||
}
|
||||
|
||||
// A BoolMapper is a Mapper to a value that is a boolean.
|
||||
//
|
||||
// This is used solely for formatting help.
|
||||
type BoolMapper interface {
|
||||
IsBool() bool
|
||||
}
|
||||
|
||||
// A MapperFunc is a single function that complies with the Mapper interface.
|
||||
type MapperFunc func(ctx *DecodeContext, target reflect.Value) error
|
||||
|
||||
func (m MapperFunc) Decode(ctx *DecodeContext, target reflect.Value) error { // nolint: golint
|
||||
return m(ctx, target)
|
||||
}
|
||||
|
||||
// A Registry contains a set of mappers and supporting lookup methods.
|
||||
type Registry struct {
|
||||
names map[string]Mapper
|
||||
types map[reflect.Type]Mapper
|
||||
kinds map[reflect.Kind]Mapper
|
||||
values map[reflect.Value]Mapper
|
||||
}
|
||||
|
||||
// NewRegistry creates a new (empty) Registry.
|
||||
func NewRegistry() *Registry {
|
||||
return &Registry{
|
||||
names: map[string]Mapper{},
|
||||
types: map[reflect.Type]Mapper{},
|
||||
kinds: map[reflect.Kind]Mapper{},
|
||||
values: map[reflect.Value]Mapper{},
|
||||
}
|
||||
}
|
||||
|
||||
// ForNamedValue finds a mapper for a value with a user-specified name.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForNamedValue(name string, value reflect.Value) Mapper {
|
||||
if mapper, ok := r.names[name]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForValue(value)
|
||||
}
|
||||
|
||||
// ForValue looks up the Mapper for a reflect.Value.
|
||||
func (r *Registry) ForValue(value reflect.Value) Mapper {
|
||||
if mapper, ok := r.values[value]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForType(value.Type())
|
||||
}
|
||||
|
||||
// ForNamedType finds a mapper for a type with a user-specified name.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForNamedType(name string, typ reflect.Type) Mapper {
|
||||
if mapper, ok := r.names[name]; ok {
|
||||
return mapper
|
||||
}
|
||||
return r.ForType(typ)
|
||||
}
|
||||
|
||||
// ForType finds a mapper from a type, by type, then kind.
|
||||
//
|
||||
// Will return nil if a mapper can not be determined.
|
||||
func (r *Registry) ForType(typ reflect.Type) Mapper {
|
||||
// Check if the type implements MapperValue.
|
||||
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
|
||||
if impl.Implements(mapperValueType) {
|
||||
return &mapperValueAdapter{impl.Implements(boolMapperType)}
|
||||
}
|
||||
}
|
||||
// Next, try explicitly registered types.
|
||||
var mapper Mapper
|
||||
var ok bool
|
||||
if mapper, ok = r.types[typ]; ok {
|
||||
return mapper
|
||||
}
|
||||
// Next try stdlib unmarshaler interfaces.
|
||||
for _, impl := range []reflect.Type{typ, reflect.PtrTo(typ)} {
|
||||
if impl.Implements(textUnmarshalerType) {
|
||||
return &textUnmarshalerAdapter{}
|
||||
} else if impl.Implements(binaryUnmarshalerType) {
|
||||
return &binaryUnmarshalerAdapter{}
|
||||
}
|
||||
}
|
||||
// Finally try registered kinds.
|
||||
if mapper, ok = r.kinds[typ.Kind()]; ok {
|
||||
return mapper
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterKind registers a Mapper for a reflect.Kind.
|
||||
func (r *Registry) RegisterKind(kind reflect.Kind, mapper Mapper) *Registry {
|
||||
r.kinds[kind] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterName registers a mapper to be used if the value mapper has a "type" tag matching name.
|
||||
//
|
||||
// eg.
|
||||
//
|
||||
// Mapper string `kong:"type='colour'`
|
||||
// registry.RegisterName("colour", ...)
|
||||
func (r *Registry) RegisterName(name string, mapper Mapper) *Registry {
|
||||
r.names[name] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterType registers a Mapper for a reflect.Type.
|
||||
func (r *Registry) RegisterType(typ reflect.Type, mapper Mapper) *Registry {
|
||||
r.types[typ] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterValue registers a Mapper by pointer to the field value.
|
||||
func (r *Registry) RegisterValue(ptr interface{}, mapper Mapper) *Registry {
|
||||
key := reflect.ValueOf(ptr)
|
||||
if key.Kind() != reflect.Ptr {
|
||||
panic("expected a pointer")
|
||||
}
|
||||
key = key.Elem()
|
||||
r.values[key] = mapper
|
||||
return r
|
||||
}
|
||||
|
||||
// RegisterDefaults registers Mappers for all builtin supported Go types and some common stdlib types.
|
||||
func (r *Registry) RegisterDefaults() *Registry {
|
||||
return r.RegisterKind(reflect.Int, intDecoder(bits.UintSize)).
|
||||
RegisterKind(reflect.Int8, intDecoder(8)).
|
||||
RegisterKind(reflect.Int16, intDecoder(16)).
|
||||
RegisterKind(reflect.Int32, intDecoder(32)).
|
||||
RegisterKind(reflect.Int64, intDecoder(64)).
|
||||
RegisterKind(reflect.Uint, uintDecoder(64)).
|
||||
RegisterKind(reflect.Uint8, uintDecoder(bits.UintSize)).
|
||||
RegisterKind(reflect.Uint16, uintDecoder(16)).
|
||||
RegisterKind(reflect.Uint32, uintDecoder(32)).
|
||||
RegisterKind(reflect.Uint64, uintDecoder(64)).
|
||||
RegisterKind(reflect.Float32, floatDecoder(32)).
|
||||
RegisterKind(reflect.Float64, floatDecoder(64)).
|
||||
RegisterKind(reflect.String, MapperFunc(func(ctx *DecodeContext, target reflect.Value) error {
|
||||
err := ctx.Scan.PopValueInto("string", target.Addr().Interface())
|
||||
return err
|
||||
})).
|
||||
RegisterKind(reflect.Bool, boolMapper{}).
|
||||
RegisterKind(reflect.Slice, sliceDecoder(r)).
|
||||
RegisterKind(reflect.Map, mapDecoder(r)).
|
||||
RegisterType(reflect.TypeOf(time.Time{}), timeDecoder()).
|
||||
RegisterType(reflect.TypeOf(time.Duration(0)), durationDecoder()).
|
||||
RegisterType(reflect.TypeOf(&url.URL{}), urlMapper()).
|
||||
RegisterName("path", pathMapper(r)).
|
||||
RegisterName("existingfile", existingFileMapper(r)).
|
||||
RegisterName("existingdir", existingDirMapper(r)).
|
||||
RegisterName("counter", counterMapper())
|
||||
}
|
||||
|
||||
type boolMapper struct{}
|
||||
|
||||
func (boolMapper) Decode(ctx *DecodeContext, target reflect.Value) error {
|
||||
if ctx.Scan.Peek().Type == FlagValueToken {
|
||||
token := ctx.Scan.Pop()
|
||||
switch v := token.Value.(type) {
|
||||
case string:
|
||||
v = strings.ToLower(v)
|
||||
switch v {
|
||||
case "true", "1", "yes":
|
||||
target.SetBool(true)
|
||||
|
||||
case "false", "0", "no":
|
||||
target.SetBool(false)
|
||||
|
||||
default:
|
||||
return errors.Errorf("bool value must be true, 1, yes, false, 0 or no but got %q", v)
|
||||
}
|
||||
|
||||
case bool:
|
||||
target.SetBool(v)
|
||||
|
||||
default:
|
||||
return errors.Errorf("expected bool but got %q (%T)", token.Value, token.Value)
|
||||
}
|
||||
} else {
|
||||
target.SetBool(true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (boolMapper) IsBool() bool { return true }
|
||||
|
||||
func durationDecoder() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
var value string
|
||||
if err := ctx.Scan.PopValueInto("duration", &value); err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("expected duration but got %q: %s", value, err)
|
||||
}
|
||||
target.Set(reflect.ValueOf(r))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func timeDecoder() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
format := time.RFC3339
|
||||
if ctx.Value.Format != "" {
|
||||
format = ctx.Value.Format
|
||||
}
|
||||
var value string
|
||||
if err := ctx.Scan.PopValueInto("time", &value); err != nil {
|
||||
return err
|
||||
}
|
||||
t, err := time.Parse(format, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target.Set(reflect.ValueOf(t))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func intDecoder(bits int) MapperFunc { // nolint: dupl
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("int")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sv string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
sv = v
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
sv = fmt.Sprintf("%v", v)
|
||||
|
||||
default:
|
||||
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
n, err := strconv.ParseInt(sv, 10, bits)
|
||||
if err != nil {
|
||||
return errors.Errorf("expected a valid %d bit int but got %q", bits, sv)
|
||||
}
|
||||
target.SetInt(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func uintDecoder(bits int) MapperFunc { // nolint: dupl
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("uint")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var sv string
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
sv = v
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
|
||||
sv = fmt.Sprintf("%v", v)
|
||||
|
||||
default:
|
||||
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
n, err := strconv.ParseUint(sv, 10, bits)
|
||||
if err != nil {
|
||||
return errors.Errorf("expected a valid %d bit uint but got %q", bits, sv)
|
||||
}
|
||||
target.SetUint(n)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func floatDecoder(bits int) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
t, err := ctx.Scan.PopValue("float")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
n, err := strconv.ParseFloat(v, bits)
|
||||
if err != nil {
|
||||
return errors.Errorf("expected a float but got %q (%T)", t, t.Value)
|
||||
}
|
||||
target.SetFloat(n)
|
||||
|
||||
case float32:
|
||||
target.SetFloat(float64(v))
|
||||
|
||||
case float64:
|
||||
target.SetFloat(v)
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
target.Set(reflect.ValueOf(v))
|
||||
|
||||
default:
|
||||
return errors.Errorf("expected an int but got %q (%T)", t, t.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func mapDecoder(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.IsNil() {
|
||||
target.Set(reflect.MakeMap(target.Type()))
|
||||
}
|
||||
el := target.Type()
|
||||
sep := ctx.Value.Tag.MapSep
|
||||
var childScanner *Scanner
|
||||
if ctx.Value.Flag != nil {
|
||||
t := ctx.Scan.Pop()
|
||||
// If decoding a flag, we need an argument.
|
||||
if t.IsEOL() {
|
||||
return errors.Errorf("unexpected EOL")
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
childScanner = Scan(SplitEscaped(v, sep)...)
|
||||
|
||||
case []map[string]interface{}:
|
||||
for _, m := range v {
|
||||
err := jsonTranscode(m, target.Addr().Interface())
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
case map[string]interface{}:
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
|
||||
default:
|
||||
return errors.Errorf("invalid map value %q (of type %T)", t, t.Value)
|
||||
}
|
||||
} else {
|
||||
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
||||
childScanner = ScanFromTokens(tokens...)
|
||||
}
|
||||
for !childScanner.Peek().IsEOL() {
|
||||
var token string
|
||||
err := childScanner.PopValueInto("map", &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parts := strings.SplitN(token, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return errors.Errorf("expected \"<key>=<value>\" but got %q", token)
|
||||
}
|
||||
key, value := parts[0], parts[1]
|
||||
|
||||
keyTypeName, valueTypeName := "", ""
|
||||
if typ := ctx.Value.Tag.Type; typ != "" {
|
||||
parts := strings.Split(typ, ":")
|
||||
if len(parts) != 2 {
|
||||
return errors.Errorf("type:\"\" on map field must be in the form \"[<keytype>]:[<valuetype>]\"")
|
||||
}
|
||||
keyTypeName, valueTypeName = parts[0], parts[1]
|
||||
}
|
||||
|
||||
keyScanner := Scan(key)
|
||||
keyDecoder := r.ForNamedType(keyTypeName, el.Key())
|
||||
keyValue := reflect.New(el.Key()).Elem()
|
||||
if err := keyDecoder.Decode(ctx.WithScanner(keyScanner), keyValue); err != nil {
|
||||
return errors.Errorf("invalid map key %q", key)
|
||||
}
|
||||
|
||||
valueScanner := Scan(value)
|
||||
valueDecoder := r.ForNamedType(valueTypeName, el.Elem())
|
||||
valueValue := reflect.New(el.Elem()).Elem()
|
||||
if err := valueDecoder.Decode(ctx.WithScanner(valueScanner), valueValue); err != nil {
|
||||
return errors.Errorf("invalid map value %q", value)
|
||||
}
|
||||
|
||||
target.SetMapIndex(keyValue, valueValue)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func sliceDecoder(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
el := target.Type().Elem()
|
||||
sep := ctx.Value.Tag.Sep
|
||||
var childScanner *Scanner
|
||||
if ctx.Value.Flag != nil {
|
||||
t := ctx.Scan.Pop()
|
||||
// If decoding a flag, we need an argument.
|
||||
if t.IsEOL() {
|
||||
return errors.Errorf("unexpected EOL")
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
childScanner = Scan(SplitEscaped(v, sep)...)
|
||||
|
||||
case []interface{}:
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
|
||||
default:
|
||||
v = []interface{}{v}
|
||||
return jsonTranscode(v, target.Addr().Interface())
|
||||
}
|
||||
} else {
|
||||
tokens := ctx.Scan.PopWhile(func(t Token) bool { return t.IsValue() })
|
||||
childScanner = ScanFromTokens(tokens...)
|
||||
}
|
||||
childDecoder := r.ForNamedType(ctx.Value.Tag.Type, el)
|
||||
if childDecoder == nil {
|
||||
return errors.Errorf("no mapper for element type of %s", target.Type())
|
||||
}
|
||||
for !childScanner.Peek().IsEOL() {
|
||||
childValue := reflect.New(el).Elem()
|
||||
err := childDecoder.Decode(ctx.WithScanner(childScanner), childValue)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
target.Set(reflect.Append(target, childValue))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func pathMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return errors.Errorf("\"path\" type must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = ExpandPath(path)
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func existingFileMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return errors.Errorf("\"existingfile\" type must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if path != "-" {
|
||||
path = ExpandPath(path)
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stat.IsDir() {
|
||||
return errors.Errorf("%q exists but is a directory", path)
|
||||
}
|
||||
}
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func existingDirMapper(r *Registry) MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if target.Kind() == reflect.Slice {
|
||||
return sliceDecoder(r)(ctx, target)
|
||||
}
|
||||
if target.Kind() != reflect.String {
|
||||
return errors.Errorf("\"existingdir\" must be applied to a string not %s", target.Type())
|
||||
}
|
||||
var path string
|
||||
err := ctx.Scan.PopValueInto("file", &path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path = ExpandPath(path)
|
||||
stat, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !stat.IsDir() {
|
||||
return errors.Errorf("%q exists but is not a directory", path)
|
||||
}
|
||||
target.SetString(path)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func counterMapper() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
if ctx.Scan.Peek().Type == FlagValueToken {
|
||||
t, err := ctx.Scan.PopValue("counter")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch v := t.Value.(type) {
|
||||
case string:
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
|
||||
}
|
||||
target.SetInt(n)
|
||||
|
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
|
||||
target.Set(reflect.ValueOf(v))
|
||||
|
||||
default:
|
||||
return errors.Errorf("expected a counter but got %q (%T)", t, t.Value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
switch target.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
target.SetInt(target.Int() + 1)
|
||||
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
target.SetUint(target.Uint() + 1)
|
||||
|
||||
case reflect.Float32, reflect.Float64:
|
||||
target.SetFloat(target.Float() + 1)
|
||||
|
||||
default:
|
||||
return errors.Errorf("type:\"counter\" must be used with a numeric field")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func urlMapper() MapperFunc {
|
||||
return func(ctx *DecodeContext, target reflect.Value) error {
|
||||
var urlStr string
|
||||
err := ctx.Scan.PopValueInto("url", &urlStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
url, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
target.Set(reflect.ValueOf(url))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SplitEscaped splits a string on a separator.
|
||||
//
|
||||
// It differs from strings.Split() in that the separator can exist in a field by escaping it with a \. eg.
|
||||
//
|
||||
// SplitEscaped(`hello\,there,bob`, ',') == []string{"hello,there", "bob"}
|
||||
func SplitEscaped(s string, sep rune) (out []string) {
|
||||
if sep == -1 {
|
||||
return []string{s}
|
||||
}
|
||||
escaped := false
|
||||
token := ""
|
||||
for _, ch := range s {
|
||||
switch {
|
||||
case escaped:
|
||||
token += string(ch)
|
||||
escaped = false
|
||||
case ch == '\\':
|
||||
escaped = true
|
||||
case ch == sep && !escaped:
|
||||
out = append(out, token)
|
||||
token = ""
|
||||
escaped = false
|
||||
default:
|
||||
token += string(ch)
|
||||
}
|
||||
}
|
||||
if token != "" {
|
||||
out = append(out, token)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// JoinEscaped joins a slice of strings on sep, but also escapes any instances of sep in the fields with \. eg.
|
||||
//
|
||||
// JoinEscaped([]string{"hello,there", "bob"}, ',') == `hello\,there,bob`
|
||||
func JoinEscaped(s []string, sep rune) string {
|
||||
escaped := []string{}
|
||||
for _, e := range s {
|
||||
escaped = append(escaped, strings.Replace(e, string(sep), `\`+string(sep), -1))
|
||||
}
|
||||
return strings.Join(escaped, string(sep))
|
||||
}
|
||||
|
||||
// FileContentFlag is a flag value that loads a file's contents into its value.
|
||||
type FileContentFlag []byte
|
||||
|
||||
func (f *FileContentFlag) Decode(ctx *DecodeContext) error { // nolint: golint
|
||||
var filename string
|
||||
err := ctx.Scan.PopValueInto("filename", &filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// This allows unsetting of file content flags.
|
||||
if filename == "" {
|
||||
*f = nil
|
||||
return nil
|
||||
}
|
||||
filename = ExpandPath(filename)
|
||||
data, err := ioutil.ReadFile(filename) // nolint: gosec
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to open %q: %s", filename, err)
|
||||
}
|
||||
*f = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonTranscode(in, out interface{}) error {
|
||||
data, err := json.Marshal(in)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return errors.Wrapf(json.Unmarshal(data, out), "%#v -> %T", in, out)
|
||||
}
|
|
@ -0,0 +1,433 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// A Visitable component in the model.
|
||||
type Visitable interface {
|
||||
node()
|
||||
}
|
||||
|
||||
// Application is the root of the Kong model.
|
||||
type Application struct {
|
||||
*Node
|
||||
// Help flag, if the NoDefaultHelp() option is not specified.
|
||||
HelpFlag *Flag
|
||||
}
|
||||
|
||||
// Argument represents a branching positional argument.
|
||||
type Argument = Node
|
||||
|
||||
// Command represents a command in the CLI.
|
||||
type Command = Node
|
||||
|
||||
// NodeType is an enum representing the type of a Node.
|
||||
type NodeType int
|
||||
|
||||
// Node type enumerations.
|
||||
const (
|
||||
ApplicationNode NodeType = iota
|
||||
CommandNode
|
||||
ArgumentNode
|
||||
)
|
||||
|
||||
// Node is a branch in the CLI. ie. a command or positional argument.
|
||||
type Node struct {
|
||||
Type NodeType
|
||||
Parent *Node
|
||||
Name string
|
||||
Help string // Short help displayed in summaries.
|
||||
Detail string // Detailed help displayed when describing command/arg alone.
|
||||
Group string
|
||||
Hidden bool
|
||||
Flags []*Flag
|
||||
Positional []*Positional
|
||||
Children []*Node
|
||||
Target reflect.Value // Pointer to the value in the grammar that this Node is associated with.
|
||||
Tag *Tag
|
||||
|
||||
Argument *Value // Populated when Type is ArgumentNode.
|
||||
}
|
||||
|
||||
func (*Node) node() {}
|
||||
|
||||
// Leaf returns true if this Node is a leaf node.
|
||||
func (n *Node) Leaf() bool {
|
||||
return len(n.Children) == 0
|
||||
}
|
||||
|
||||
// Find a command/argument/flag by pointer to its field.
|
||||
//
|
||||
// Returns nil if not found. Panics if ptr is not a pointer.
|
||||
func (n *Node) Find(ptr interface{}) *Node {
|
||||
key := reflect.ValueOf(ptr)
|
||||
if key.Kind() != reflect.Ptr {
|
||||
panic("expected a pointer")
|
||||
}
|
||||
return n.findNode(key)
|
||||
}
|
||||
|
||||
func (n *Node) findNode(key reflect.Value) *Node {
|
||||
if n.Target == key {
|
||||
return n
|
||||
}
|
||||
for _, child := range n.Children {
|
||||
if found := child.findNode(key); found != nil {
|
||||
return found
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AllFlags returns flags from all ancestor branches encountered.
|
||||
//
|
||||
// If "hide" is true hidden flags will be omitted.
|
||||
func (n *Node) AllFlags(hide bool) (out [][]*Flag) {
|
||||
if n.Parent != nil {
|
||||
out = append(out, n.Parent.AllFlags(hide)...)
|
||||
}
|
||||
group := []*Flag{}
|
||||
for _, flag := range n.Flags {
|
||||
if !hide || !flag.Hidden {
|
||||
group = append(group, flag)
|
||||
}
|
||||
}
|
||||
if len(group) > 0 {
|
||||
out = append(out, group)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Leaves returns the leaf commands/arguments under Node.
|
||||
//
|
||||
// If "hidden" is true hidden leaves will be omitted.
|
||||
func (n *Node) Leaves(hide bool) (out []*Node) {
|
||||
_ = Visit(n, func(nd Visitable, next Next) error {
|
||||
if nd == n {
|
||||
return next(nil)
|
||||
}
|
||||
if node, ok := nd.(*Node); ok {
|
||||
if hide && node.Hidden {
|
||||
return nil
|
||||
}
|
||||
if len(node.Children) == 0 && node.Type != ApplicationNode {
|
||||
out = append(out, node)
|
||||
}
|
||||
}
|
||||
return next(nil)
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Depth of the command from the application root.
|
||||
func (n *Node) Depth() int {
|
||||
depth := 0
|
||||
p := n.Parent
|
||||
for p != nil && p.Type != ApplicationNode {
|
||||
depth++
|
||||
p = p.Parent
|
||||
}
|
||||
return depth
|
||||
}
|
||||
|
||||
// Summary help string for the node (not including application name).
|
||||
func (n *Node) Summary() string {
|
||||
summary := n.Path()
|
||||
if flags := n.FlagSummary(true); flags != "" {
|
||||
summary += " " + flags
|
||||
}
|
||||
args := []string{}
|
||||
for _, arg := range n.Positional {
|
||||
args = append(args, arg.Summary())
|
||||
}
|
||||
if len(args) != 0 {
|
||||
summary += " " + strings.Join(args, " ")
|
||||
} else if len(n.Children) > 0 {
|
||||
summary += " <command>"
|
||||
}
|
||||
return summary
|
||||
}
|
||||
|
||||
// FlagSummary for the node.
|
||||
func (n *Node) FlagSummary(hide bool) string {
|
||||
required := []string{}
|
||||
count := 0
|
||||
for _, group := range n.AllFlags(hide) {
|
||||
for _, flag := range group {
|
||||
count++
|
||||
if flag.Required {
|
||||
required = append(required, flag.Summary())
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(required, " ")
|
||||
}
|
||||
|
||||
// FullPath is like Path() but includes the Application root node.
|
||||
func (n *Node) FullPath() string {
|
||||
root := n
|
||||
for root.Parent != nil {
|
||||
root = root.Parent
|
||||
}
|
||||
return strings.TrimSpace(root.Name + " " + n.Path())
|
||||
}
|
||||
|
||||
// Vars returns the combined Vars defined by all ancestors of this Node.
|
||||
func (n *Node) Vars() Vars {
|
||||
if n == nil {
|
||||
return Vars{}
|
||||
}
|
||||
return n.Parent.Vars().CloneWith(n.Tag.Vars)
|
||||
}
|
||||
|
||||
// Path through ancestors to this Node.
|
||||
func (n *Node) Path() (out string) {
|
||||
if n.Parent != nil {
|
||||
out += " " + n.Parent.Path()
|
||||
}
|
||||
switch n.Type {
|
||||
case CommandNode:
|
||||
out += " " + n.Name
|
||||
case ArgumentNode:
|
||||
out += " " + "<" + n.Name + ">"
|
||||
}
|
||||
return strings.TrimSpace(out)
|
||||
}
|
||||
|
||||
// A Value is either a flag or a variable positional argument.
|
||||
type Value struct {
|
||||
Flag *Flag // Nil if positional argument.
|
||||
Name string
|
||||
Help string
|
||||
Default string
|
||||
DefaultValue reflect.Value
|
||||
Enum string
|
||||
Mapper Mapper
|
||||
Tag *Tag
|
||||
Target reflect.Value
|
||||
Required bool
|
||||
Set bool // Set to true when this value is set through some mechanism.
|
||||
Format string // Formatting directive, if applicable.
|
||||
Position int // Position (for positional arguments).
|
||||
}
|
||||
|
||||
// EnumMap returns a map of the enums in this value.
|
||||
func (v *Value) EnumMap() map[string]bool {
|
||||
parts := strings.Split(v.Enum, ",")
|
||||
out := make(map[string]bool, len(parts))
|
||||
for _, part := range parts {
|
||||
out[strings.TrimSpace(part)] = true
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ShortSummary returns a human-readable summary of the value, not including any placeholders/defaults.
|
||||
func (v *Value) ShortSummary() string {
|
||||
if v.Flag != nil {
|
||||
return fmt.Sprintf("--%s", v.Name)
|
||||
}
|
||||
argText := "<" + v.Name + ">"
|
||||
if v.IsCumulative() {
|
||||
argText += " ..."
|
||||
}
|
||||
if !v.Required {
|
||||
argText = "[" + argText + "]"
|
||||
}
|
||||
return argText
|
||||
}
|
||||
|
||||
// Summary returns a human-readable summary of the value.
|
||||
func (v *Value) Summary() string {
|
||||
if v.Flag != nil {
|
||||
if v.IsBool() {
|
||||
return fmt.Sprintf("--%s", v.Name)
|
||||
}
|
||||
return fmt.Sprintf("--%s=%s", v.Name, v.Flag.FormatPlaceHolder())
|
||||
}
|
||||
argText := "<" + v.Name + ">"
|
||||
if v.IsCumulative() {
|
||||
argText += " ..."
|
||||
}
|
||||
if !v.Required {
|
||||
argText = "[" + argText + "]"
|
||||
}
|
||||
return argText
|
||||
}
|
||||
|
||||
// IsCumulative returns true if the type can be accumulated into.
|
||||
func (v *Value) IsCumulative() bool {
|
||||
return v.IsSlice() || v.IsMap()
|
||||
}
|
||||
|
||||
// IsSlice returns true if the value is a slice.
|
||||
func (v *Value) IsSlice() bool {
|
||||
return v.Target.Type().Name() == "" && v.Target.Kind() == reflect.Slice
|
||||
}
|
||||
|
||||
// IsMap returns true if the value is a map.
|
||||
func (v *Value) IsMap() bool {
|
||||
return v.Target.Kind() == reflect.Map
|
||||
}
|
||||
|
||||
// IsBool returns true if the underlying value is a boolean.
|
||||
func (v *Value) IsBool() bool {
|
||||
if m, ok := v.Mapper.(BoolMapper); ok && m.IsBool() {
|
||||
return true
|
||||
}
|
||||
return v.Target.Kind() == reflect.Bool
|
||||
}
|
||||
|
||||
// Parse tokens into value, parse, and validate, but do not write to the field.
|
||||
func (v *Value) Parse(scan *Scanner, target reflect.Value) (err error) {
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
switch rerr := rerr.(type) {
|
||||
case Error:
|
||||
err = errors.Wrap(rerr, v.ShortSummary())
|
||||
default:
|
||||
panic(fmt.Sprintf("mapper %T failed to apply to %s: %s", v.Mapper, v.Summary(), rerr))
|
||||
}
|
||||
}
|
||||
}()
|
||||
err = v.Mapper.Decode(&DecodeContext{Value: v, Scan: scan}, target)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, v.ShortSummary())
|
||||
}
|
||||
v.Set = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply value to field.
|
||||
func (v *Value) Apply(value reflect.Value) {
|
||||
v.Target.Set(value)
|
||||
v.Set = true
|
||||
}
|
||||
|
||||
// ApplyDefault value to field if it is not already set.
|
||||
func (v *Value) ApplyDefault() error {
|
||||
if reflectValueIsZero(v.Target) {
|
||||
return v.Reset()
|
||||
}
|
||||
v.Set = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset this value to its default, either the zero value or the parsed result of its envar,
|
||||
// or its "default" tag.
|
||||
//
|
||||
// Does not include resolvers.
|
||||
func (v *Value) Reset() error {
|
||||
v.Target.Set(reflect.Zero(v.Target.Type()))
|
||||
if v.Tag.Env != "" {
|
||||
envar := os.Getenv(v.Tag.Env)
|
||||
if envar != "" {
|
||||
err := v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: envar}), v.Target)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s (from envar %s=%q)", err, v.Tag.Env, envar)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if v.Default != "" {
|
||||
return v.Parse(ScanFromTokens(Token{Type: FlagValueToken, Value: v.Default}), v.Target)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*Value) node() {}
|
||||
|
||||
// A Positional represents a non-branching command-line positional argument.
|
||||
type Positional = Value
|
||||
|
||||
// A Flag represents a command-line flag.
|
||||
type Flag struct {
|
||||
*Value
|
||||
Group string // Logical grouping when displaying. May also be used by configuration loaders to group options logically.
|
||||
Xor string
|
||||
PlaceHolder string
|
||||
Env string
|
||||
Short rune
|
||||
Hidden bool
|
||||
}
|
||||
|
||||
func (f *Flag) String() string {
|
||||
out := "--" + f.Name
|
||||
if f.Short != 0 {
|
||||
out = fmt.Sprintf("-%c, %s", f.Short, out)
|
||||
}
|
||||
if !f.IsBool() {
|
||||
out += "=" + f.FormatPlaceHolder()
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// FormatPlaceHolder formats the placeholder string for a Flag.
|
||||
func (f *Flag) FormatPlaceHolder() string {
|
||||
tail := ""
|
||||
if f.Value.IsSlice() && f.Value.Tag.Sep != -1 {
|
||||
tail += string(f.Value.Tag.Sep) + "..."
|
||||
}
|
||||
if f.Default != "" {
|
||||
if f.Value.Target.Kind() == reflect.String {
|
||||
return strconv.Quote(f.Default) + tail
|
||||
}
|
||||
return f.Default + tail
|
||||
}
|
||||
if f.PlaceHolder != "" {
|
||||
return f.PlaceHolder + tail
|
||||
}
|
||||
if f.Value.IsMap() {
|
||||
if f.Value.Tag.MapSep != -1 {
|
||||
tail = string(f.Value.Tag.MapSep) + "..."
|
||||
}
|
||||
return "KEY=VALUE" + tail
|
||||
}
|
||||
return strings.ToUpper(f.Name) + tail
|
||||
}
|
||||
|
||||
// This is directly from the Go 1.13 source code.
|
||||
func reflectValueIsZero(v reflect.Value) bool {
|
||||
switch v.Kind() {
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return math.Float64bits(v.Float()) == 0
|
||||
case reflect.Complex64, reflect.Complex128:
|
||||
c := v.Complex()
|
||||
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
|
||||
case reflect.Array:
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
if !reflectValueIsZero(v.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
|
||||
return v.IsNil()
|
||||
case reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Struct:
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if !reflectValueIsZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
default:
|
||||
// This should never happens, but will act as a safeguard for
|
||||
// later, as a default value doesn't makes sense here.
|
||||
panic(&reflect.ValueError{"reflect.Value.IsZero", v.Kind()})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// An Option applies optional changes to the Kong application.
|
||||
type Option interface {
|
||||
Apply(k *Kong) error
|
||||
}
|
||||
|
||||
// OptionFunc is function that adheres to the Option interface.
|
||||
type OptionFunc func(k *Kong) error
|
||||
|
||||
func (o OptionFunc) Apply(k *Kong) error { return o(k) } // nolint: golint
|
||||
|
||||
// Vars sets the variables to use for interpolation into help strings and default values.
|
||||
//
|
||||
// See README for details.
|
||||
type Vars map[string]string
|
||||
|
||||
// Apply lets Vars act as an Option.
|
||||
func (v Vars) Apply(k *Kong) error {
|
||||
for key, value := range v {
|
||||
k.vars[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CloneWith clones the current Vars and merges "vars" onto the clone.
|
||||
func (v Vars) CloneWith(vars Vars) Vars {
|
||||
out := make(Vars, len(v)+len(vars))
|
||||
for key, value := range v {
|
||||
out[key] = value
|
||||
}
|
||||
for key, value := range vars {
|
||||
out[key] = value
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Exit overrides the function used to terminate. This is useful for testing or interactive use.
|
||||
func Exit(exit func(int)) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.Exit = exit
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NoDefaultHelp disables the default help flags.
|
||||
func NoDefaultHelp() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.noDefaultHelp = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// PostBuild provides read/write access to kong.Kong after initial construction of the model is complete but before
|
||||
// parsing occurs.
|
||||
//
|
||||
// This is useful for, e.g., adding short options to flags, updating help, etc.
|
||||
func (k *Kong) PostBuild(fn func(*Kong) error) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(fn))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Name overrides the application name.
|
||||
func Name(name string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error {
|
||||
k.Model.Name = name
|
||||
return nil
|
||||
}))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Description sets the application description.
|
||||
func Description(description string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.postBuildOptions = append(k.postBuildOptions, OptionFunc(func(k *Kong) error {
|
||||
k.Model.Help = description
|
||||
return nil
|
||||
}))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// TypeMapper registers a mapper to a type.
|
||||
func TypeMapper(typ reflect.Type, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterType(typ, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// KindMapper registers a mapper to a kind.
|
||||
func KindMapper(kind reflect.Kind, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterKind(kind, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ValueMapper registers a mapper to a field value.
|
||||
func ValueMapper(ptr interface{}, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterValue(ptr, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// NamedMapper registers a mapper to a name.
|
||||
func NamedMapper(name string, mapper Mapper) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.registry.RegisterName(name, mapper)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Writers overrides the default writers. Useful for testing or interactive use.
|
||||
func Writers(stdout, stderr io.Writer) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.Stdout = stdout
|
||||
k.Stderr = stderr
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Bind binds values for hooks and Run() function arguments.
|
||||
//
|
||||
// Any arguments passed will be available to the receiving hook functions, but may be omitted. Additionally, *Kong and
|
||||
// the current *Context will also be made available.
|
||||
//
|
||||
// There are two hook points:
|
||||
//
|
||||
// BeforeApply(...) error
|
||||
// AfterApply(...) error
|
||||
//
|
||||
// Called before validation/assignment, and immediately after validation/assignment, respectively.
|
||||
func Bind(args ...interface{}) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.bindings.add(args...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BindTo allows binding of implementations to interfaces.
|
||||
//
|
||||
// BindTo(impl, (*iface)(nil))
|
||||
func BindTo(impl, iface interface{}) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
valueOf := reflect.ValueOf(impl)
|
||||
k.bindings[reflect.TypeOf(iface).Elem()] = func() (reflect.Value, error) { return valueOf, nil }
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// BindToProvider allows binding of provider functions.
|
||||
//
|
||||
// This is useful when the Run() function of different commands require different values that may
|
||||
// not all be initialisable from the main() function.
|
||||
func BindToProvider(provider interface{}) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
pv := reflect.ValueOf(provider)
|
||||
t := pv.Type()
|
||||
if t.Kind() != reflect.Func || t.NumIn() != 0 || t.NumOut() != 2 || t.Out(1) != reflect.TypeOf((*error)(nil)).Elem() {
|
||||
return errors.Errorf("%T must be a function with the signature func()(T, error)", provider)
|
||||
}
|
||||
rt := pv.Type().Out(0)
|
||||
k.bindings[rt] = func() (reflect.Value, error) {
|
||||
out := pv.Call(nil)
|
||||
errv := out[1]
|
||||
var err error
|
||||
if !errv.IsNil() {
|
||||
err = errv.Interface().(error)
|
||||
}
|
||||
return out[0], err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Help printer to use.
|
||||
func Help(help HelpPrinter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.help = help
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// HelpFormatter configures how the help text is formatted.
|
||||
func HelpFormatter(helpFormatter HelpValueFormatter) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.helpFormatter = helpFormatter
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ConfigureHelp sets the HelpOptions to use for printing help.
|
||||
func ConfigureHelp(options HelpOptions) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.helpOptions = options
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// UsageOnError configures Kong to display context-sensitive usage if FatalIfErrorf is called with an error.
|
||||
func UsageOnError() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.usageOnError = true
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ClearResolvers clears all existing resolvers.
|
||||
func ClearResolvers() Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.resolvers = nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Resolvers registers flag resolvers.
|
||||
func Resolvers(resolvers ...Resolver) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.resolvers = append(k.resolvers, resolvers...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ConfigurationLoader is a function that builds a resolver from a file.
|
||||
type ConfigurationLoader func(r io.Reader) (Resolver, error)
|
||||
|
||||
// Configuration provides Kong with support for loading defaults from a set of configuration files.
|
||||
//
|
||||
// Paths will be opened in order, and "loader" will be used to provide a Resolver which is registered with Kong.
|
||||
//
|
||||
// Note: The JSON function is a ConfigurationLoader.
|
||||
//
|
||||
// ~ and variable expansion will occur on the provided paths.
|
||||
func Configuration(loader ConfigurationLoader, paths ...string) Option {
|
||||
return OptionFunc(func(k *Kong) error {
|
||||
k.loader = loader
|
||||
for _, path := range paths {
|
||||
if _, err := os.Stat(ExpandPath(path)); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
resolver, err := k.LoadConfig(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, path)
|
||||
}
|
||||
if resolver != nil {
|
||||
k.resolvers = append(k.resolvers, resolver)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// ExpandPath is a helper function to expand a relative or home-relative path to an absolute path.
|
||||
//
|
||||
// eg. ~/.someconf -> /home/alec/.someconf
|
||||
func ExpandPath(path string) string {
|
||||
if filepath.IsAbs(path) {
|
||||
return path
|
||||
}
|
||||
if strings.HasPrefix(path, "~/") {
|
||||
user, err := user.Current()
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return filepath.Join(user.HomeDir, path[2:])
|
||||
}
|
||||
abspath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return path
|
||||
}
|
||||
return abspath
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// A Resolver resolves a Flag value from an external source.
|
||||
type Resolver interface {
|
||||
// Validate configuration against Application.
|
||||
//
|
||||
// This can be used to validate that all provided configuration is valid within this application.
|
||||
Validate(app *Application) error
|
||||
|
||||
// Resolve the value for a Flag.
|
||||
Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error)
|
||||
}
|
||||
|
||||
// ResolverFunc is a convenience type for non-validating Resolvers.
|
||||
type ResolverFunc func(context *Context, parent *Path, flag *Flag) (interface{}, error)
|
||||
|
||||
var _ Resolver = ResolverFunc(nil)
|
||||
|
||||
func (r ResolverFunc) Resolve(context *Context, parent *Path, flag *Flag) (interface{}, error) { // nolint: golint
|
||||
return r(context, parent, flag)
|
||||
}
|
||||
func (r ResolverFunc) Validate(app *Application) error { return nil } // nolint: golint
|
||||
|
||||
// JSON returns a Resolver that retrieves values from a JSON source.
|
||||
//
|
||||
// Hyphens in flag names are replaced with underscores.
|
||||
func JSON(r io.Reader) (Resolver, error) {
|
||||
values := map[string]interface{}{}
|
||||
err := json.NewDecoder(r).Decode(&values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var f ResolverFunc = func(context *Context, parent *Path, flag *Flag) (interface{}, error) {
|
||||
name := strings.Replace(flag.Name, "-", "_", -1)
|
||||
raw, ok := values[name]
|
||||
if !ok {
|
||||
return nil, nil
|
||||
}
|
||||
return raw, nil
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
|
@ -0,0 +1,217 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TokenType is the type of a token.
|
||||
type TokenType int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
UntypedToken TokenType = iota
|
||||
EOLToken
|
||||
FlagToken // --<flag>
|
||||
FlagValueToken // =<value>
|
||||
ShortFlagToken // -<short>[<tail]
|
||||
ShortFlagTailToken // <tail>
|
||||
PositionalArgumentToken // <arg>
|
||||
)
|
||||
|
||||
func (t TokenType) String() string {
|
||||
switch t {
|
||||
case UntypedToken:
|
||||
return "untyped"
|
||||
case EOLToken:
|
||||
return "<EOL>"
|
||||
case FlagToken: // --<flag>
|
||||
return "long flag"
|
||||
case FlagValueToken: // =<value>
|
||||
return "flag value"
|
||||
case ShortFlagToken: // -<short>[<tail]
|
||||
return "short flag"
|
||||
case ShortFlagTailToken: // <tail>
|
||||
return "short flag remainder"
|
||||
case PositionalArgumentToken: // <arg>
|
||||
return "positional argument"
|
||||
}
|
||||
panic("unsupported type")
|
||||
}
|
||||
|
||||
// Token created by Scanner.
|
||||
type Token struct {
|
||||
Value interface{}
|
||||
Type TokenType
|
||||
}
|
||||
|
||||
func (t Token) String() string {
|
||||
switch t.Type {
|
||||
case FlagToken:
|
||||
return fmt.Sprintf("--%v", t.Value)
|
||||
|
||||
case ShortFlagToken:
|
||||
return fmt.Sprintf("-%v", t.Value)
|
||||
|
||||
case EOLToken:
|
||||
return "EOL"
|
||||
|
||||
default:
|
||||
return fmt.Sprintf("%v", t.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// IsEOL returns true if this Token is past the end of the line.
|
||||
func (t Token) IsEOL() bool {
|
||||
return t.Type == EOLToken
|
||||
}
|
||||
|
||||
// IsAny returns true if the token's type is any of those provided.
|
||||
func (t TokenType) IsAny(types ...TokenType) bool {
|
||||
for _, typ := range types {
|
||||
if t == typ {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// InferredType tries to infer the type of a token.
|
||||
func (t Token) InferredType() TokenType {
|
||||
if t.Type != UntypedToken {
|
||||
return t.Type
|
||||
}
|
||||
if v, ok := t.Value.(string); ok {
|
||||
if strings.HasPrefix(v, "--") { // nolint: gocritic
|
||||
return FlagToken
|
||||
} else if v == "-" {
|
||||
return PositionalArgumentToken
|
||||
} else if strings.HasPrefix(v, "-") {
|
||||
return ShortFlagToken
|
||||
}
|
||||
}
|
||||
return t.Type
|
||||
}
|
||||
|
||||
// IsValue returns true if token is usable as a parseable value.
|
||||
//
|
||||
// A parseable value is either a value typed token, or an untyped token NOT starting with a hyphen.
|
||||
func (t Token) IsValue() bool {
|
||||
tt := t.InferredType()
|
||||
return tt.IsAny(FlagValueToken, ShortFlagTailToken, PositionalArgumentToken) ||
|
||||
(tt == UntypedToken && !strings.HasPrefix(t.String(), "-"))
|
||||
}
|
||||
|
||||
// Scanner is a stack-based scanner over command-line tokens.
|
||||
//
|
||||
// Initially all tokens are untyped. As the parser consumes tokens it assigns types, splits tokens, and pushes them back
|
||||
// onto the stream.
|
||||
//
|
||||
// For example, the token "--foo=bar" will be split into the following by the parser:
|
||||
//
|
||||
// [{FlagToken, "foo"}, {FlagValueToken, "bar"}]
|
||||
type Scanner struct {
|
||||
args []Token
|
||||
}
|
||||
|
||||
// Scan creates a new Scanner from args with untyped tokens.
|
||||
func Scan(args ...string) *Scanner {
|
||||
s := &Scanner{}
|
||||
for _, arg := range args {
|
||||
s.args = append(s.args, Token{Value: arg})
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ScanFromTokens creates a new Scanner from a slice of tokens.
|
||||
func ScanFromTokens(tokens ...Token) *Scanner {
|
||||
return &Scanner{args: tokens}
|
||||
}
|
||||
|
||||
// Len returns the number of input arguments.
|
||||
func (s *Scanner) Len() int {
|
||||
return len(s.args)
|
||||
}
|
||||
|
||||
// Pop the front token off the Scanner.
|
||||
func (s *Scanner) Pop() Token {
|
||||
if len(s.args) == 0 {
|
||||
return Token{Type: EOLToken}
|
||||
}
|
||||
arg := s.args[0]
|
||||
s.args = s.args[1:]
|
||||
return arg
|
||||
}
|
||||
|
||||
type expectedError struct {
|
||||
context string
|
||||
token Token
|
||||
}
|
||||
|
||||
func (e *expectedError) Error() string {
|
||||
return fmt.Sprintf("expected %s value but got %q (%s)", e.context, e.token, e.token.InferredType())
|
||||
}
|
||||
|
||||
// PopValue pops a value token, or returns an error.
|
||||
//
|
||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||
func (s *Scanner) PopValue(context string) (Token, error) {
|
||||
t := s.Pop()
|
||||
if !t.IsValue() {
|
||||
return t, &expectedError{context, t}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// PopValueInto pops a value token into target or returns an error.
|
||||
//
|
||||
// "context" is used to assist the user if the value can not be popped, eg. "expected <context> value but got <type>"
|
||||
func (s *Scanner) PopValueInto(context string, target interface{}) error {
|
||||
t, err := s.PopValue(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return jsonTranscode(t.Value, target)
|
||||
}
|
||||
|
||||
// PopWhile predicate returns true.
|
||||
func (s *Scanner) PopWhile(predicate func(Token) bool) (values []Token) {
|
||||
for predicate(s.Peek()) {
|
||||
values = append(values, s.Pop())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// PopUntil predicate returns true.
|
||||
func (s *Scanner) PopUntil(predicate func(Token) bool) (values []Token) {
|
||||
for !predicate(s.Peek()) {
|
||||
values = append(values, s.Pop())
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Peek at the next Token or return an EOLToken.
|
||||
func (s *Scanner) Peek() Token {
|
||||
if len(s.args) == 0 {
|
||||
return Token{Type: EOLToken}
|
||||
}
|
||||
return s.args[0]
|
||||
}
|
||||
|
||||
// Push an untyped Token onto the front of the Scanner.
|
||||
func (s *Scanner) Push(arg interface{}) *Scanner {
|
||||
s.PushToken(Token{Value: arg})
|
||||
return s
|
||||
}
|
||||
|
||||
// PushTyped pushes a typed token onto the front of the Scanner.
|
||||
func (s *Scanner) PushTyped(arg interface{}, typ TokenType) *Scanner {
|
||||
s.PushToken(Token{Value: arg, Type: typ})
|
||||
return s
|
||||
}
|
||||
|
||||
// PushToken pushes a preconstructed Token onto the front of the Scanner.
|
||||
func (s *Scanner) PushToken(token Token) *Scanner {
|
||||
s.args = append([]Token{token}, s.args...)
|
||||
return s
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// Tag represents the parsed state of Kong tags in a struct field tag.
|
||||
type Tag struct {
|
||||
Ignored bool // Field is ignored by Kong. ie. kong:"-"
|
||||
Cmd bool
|
||||
Arg bool
|
||||
Required bool
|
||||
Optional bool
|
||||
Name string
|
||||
Help string
|
||||
Type string
|
||||
Default string
|
||||
Format string
|
||||
PlaceHolder string
|
||||
Env string
|
||||
Short rune
|
||||
Hidden bool
|
||||
Sep rune
|
||||
MapSep rune
|
||||
Enum string
|
||||
Group string
|
||||
Xor string
|
||||
Vars Vars
|
||||
Prefix string // Optional prefix on anonymous structs. All sub-flags will have this prefix.
|
||||
Embed bool
|
||||
|
||||
// Storage for all tag keys for arbitrary lookups.
|
||||
items map[string][]string
|
||||
}
|
||||
|
||||
type tagChars struct {
|
||||
sep, quote, assign rune
|
||||
}
|
||||
|
||||
var kongChars = tagChars{sep: ',', quote: '\'', assign: '='}
|
||||
var bareChars = tagChars{sep: ' ', quote: '"', assign: ':'}
|
||||
|
||||
func parseTagItems(tagString string, chr tagChars) map[string][]string {
|
||||
d := map[string][]string{}
|
||||
key := []rune{}
|
||||
value := []rune{}
|
||||
quotes := false
|
||||
inKey := true
|
||||
|
||||
add := func() {
|
||||
d[string(key)] = append(d[string(key)], string(value))
|
||||
key = []rune{}
|
||||
value = []rune{}
|
||||
inKey = true
|
||||
}
|
||||
|
||||
runes := []rune(tagString)
|
||||
for idx := 0; idx < len(runes); idx++ {
|
||||
r := runes[idx]
|
||||
next := rune(0)
|
||||
eof := false
|
||||
if idx < len(runes)-1 {
|
||||
next = runes[idx+1]
|
||||
} else {
|
||||
eof = true
|
||||
}
|
||||
if !quotes && r == chr.sep {
|
||||
add()
|
||||
continue
|
||||
}
|
||||
if r == chr.assign && inKey {
|
||||
inKey = false
|
||||
continue
|
||||
}
|
||||
if r == '\\' {
|
||||
if next == chr.quote {
|
||||
idx++
|
||||
r = chr.quote
|
||||
}
|
||||
} else if r == chr.quote {
|
||||
if quotes {
|
||||
quotes = false
|
||||
if next == chr.sep || eof {
|
||||
continue
|
||||
}
|
||||
fail("%v has an unexpected char at pos %v", tagString, idx)
|
||||
} else {
|
||||
quotes = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
if inKey {
|
||||
key = append(key, r)
|
||||
} else {
|
||||
value = append(value, r)
|
||||
}
|
||||
}
|
||||
if quotes {
|
||||
fail("%v is not quoted properly", tagString)
|
||||
}
|
||||
|
||||
add()
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func getTagInfo(ft reflect.StructField) (string, tagChars) {
|
||||
s, ok := ft.Tag.Lookup("kong")
|
||||
if ok {
|
||||
return s, kongChars
|
||||
}
|
||||
|
||||
return string(ft.Tag), bareChars
|
||||
}
|
||||
|
||||
func newEmptyTag() *Tag {
|
||||
return &Tag{items: map[string][]string{}}
|
||||
}
|
||||
|
||||
func parseTag(fv reflect.Value, ft reflect.StructField) *Tag {
|
||||
if ft.Tag.Get("kong") == "-" {
|
||||
t := newEmptyTag()
|
||||
t.Ignored = true
|
||||
return t
|
||||
}
|
||||
t := &Tag{
|
||||
items: parseTagItems(getTagInfo(ft)),
|
||||
}
|
||||
t.Cmd = t.Has("cmd")
|
||||
t.Arg = t.Has("arg")
|
||||
required := t.Has("required")
|
||||
optional := t.Has("optional")
|
||||
if required && optional {
|
||||
fail("can't specify both required and optional")
|
||||
}
|
||||
t.Required = required
|
||||
t.Optional = optional
|
||||
t.Default = t.Get("default")
|
||||
// Arguments with defaults are always optional.
|
||||
if t.Arg && t.Default != "" {
|
||||
t.Optional = true
|
||||
}
|
||||
t.Name = t.Get("name")
|
||||
t.Help = t.Get("help")
|
||||
t.Type = t.Get("type")
|
||||
t.Env = t.Get("env")
|
||||
t.Short, _ = t.GetRune("short")
|
||||
t.Hidden = t.Has("hidden")
|
||||
t.Format = t.Get("format")
|
||||
t.Sep, _ = t.GetSep("sep", ',')
|
||||
t.MapSep, _ = t.GetSep("mapsep", ';')
|
||||
t.Group = t.Get("group")
|
||||
t.Xor = t.Get("xor")
|
||||
t.Prefix = t.Get("prefix")
|
||||
t.Embed = t.Has("embed")
|
||||
t.Vars = Vars{}
|
||||
for _, set := range t.GetAll("set") {
|
||||
parts := strings.SplitN(set, "=", 2)
|
||||
if len(parts) == 0 {
|
||||
fail("set should be in the form key=value but got %q", set)
|
||||
}
|
||||
t.Vars[parts[0]] = parts[1]
|
||||
}
|
||||
t.PlaceHolder = t.Get("placeholder")
|
||||
if t.PlaceHolder == "" {
|
||||
t.PlaceHolder = strings.ToUpper(dashedString(fv.Type().Name()))
|
||||
}
|
||||
t.Enum = t.Get("enum")
|
||||
return t
|
||||
}
|
||||
|
||||
// Has returns true if the tag contained the given key.
|
||||
func (t *Tag) Has(k string) bool {
|
||||
_, ok := t.items[k]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get returns the value of the given tag.
|
||||
//
|
||||
// Note that this will return the empty string if the tag is missing.
|
||||
func (t *Tag) Get(k string) string {
|
||||
values := t.items[k]
|
||||
if len(values) == 0 {
|
||||
return ""
|
||||
}
|
||||
return values[0]
|
||||
}
|
||||
|
||||
// GetAll returns all encountered values for a tag, in the case of multiple occurrences.
|
||||
func (t *Tag) GetAll(k string) []string {
|
||||
return t.items[k]
|
||||
}
|
||||
|
||||
// GetBool returns true if the given tag looks like a boolean truth string.
|
||||
func (t *Tag) GetBool(k string) (bool, error) {
|
||||
return strconv.ParseBool(t.Get(k))
|
||||
}
|
||||
|
||||
// GetFloat parses the given tag as a float64.
|
||||
func (t *Tag) GetFloat(k string) (float64, error) {
|
||||
return strconv.ParseFloat(t.Get(k), 64)
|
||||
}
|
||||
|
||||
// GetInt parses the given tag as an int64.
|
||||
func (t *Tag) GetInt(k string) (int64, error) {
|
||||
return strconv.ParseInt(t.Get(k), 10, 64)
|
||||
}
|
||||
|
||||
// GetRune parses the given tag as a rune.
|
||||
func (t *Tag) GetRune(k string) (rune, error) {
|
||||
r, _ := utf8.DecodeRuneInString(t.Get(k))
|
||||
if r == utf8.RuneError {
|
||||
return 0, fmt.Errorf("%v has a rune error", t.Get(k))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetSep parses the given tag as a rune separator, allowing for a default or none.
|
||||
// The separator is returned, or -1 if "none" is specified. If the tag value is an
|
||||
// invalid utf8 sequence, the default rune is returned as well as an error. If the
|
||||
// tag value is more than one rune, the first rune is returned as well as an error.
|
||||
func (t *Tag) GetSep(k string, dflt rune) (rune, error) {
|
||||
tv := t.Get(k)
|
||||
if tv == "none" {
|
||||
return -1, nil
|
||||
} else if tv == "" {
|
||||
return dflt, nil
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(tv)
|
||||
if r == utf8.RuneError {
|
||||
return dflt, fmt.Errorf(`%v:"%v" has a rune error`, k, tv)
|
||||
} else if size != len(tv) {
|
||||
return r, fmt.Errorf(`%v:"%v" is more than a single rune`, k, tv)
|
||||
}
|
||||
return r, nil
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ConfigFlag uses the configured (via kong.Configuration(loader)) configuration loader to load configuration
|
||||
// from a file specified by a flag.
|
||||
//
|
||||
// Use this as a flag value to support loading of custom configuration via a flag.
|
||||
type ConfigFlag string
|
||||
|
||||
// BeforeResolve adds a resolver.
|
||||
func (c ConfigFlag) BeforeResolve(kong *Kong, ctx *Context, trace *Path) error {
|
||||
if kong.loader == nil {
|
||||
return fmt.Errorf("kong must be configured with kong.Configuration(...)")
|
||||
}
|
||||
path := string(ctx.FlagValue(trace.Flag).(ConfigFlag))
|
||||
resolver, err := kong.LoadConfig(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx.AddResolver(resolver)
|
||||
return nil
|
||||
}
|
||||
|
||||
// VersionFlag is a flag type that can be used to display a version number, stored in the "version" variable.
|
||||
type VersionFlag bool
|
||||
|
||||
// BeforeApply writes the version variable and terminates with a 0 exit status.
|
||||
func (v VersionFlag) BeforeApply(app *Kong, vars Vars) error {
|
||||
fmt.Fprintln(app.Stdout, vars["version"])
|
||||
app.Exit(0)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package kong
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Next should be called by Visitor to proceed with the walk.
|
||||
//
|
||||
// The walk will terminate if "err" is non-nil.
|
||||
type Next func(err error) error
|
||||
|
||||
// Visitor can be used to walk all nodes in the model.
|
||||
type Visitor func(node Visitable, next Next) error
|
||||
|
||||
// Visit all nodes.
|
||||
func Visit(node Visitable, visitor Visitor) error {
|
||||
return visitor(node, func(err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch node := node.(type) {
|
||||
case *Application:
|
||||
return visitNodeChildren(node.Node, visitor)
|
||||
case *Node:
|
||||
return visitNodeChildren(node, visitor)
|
||||
case *Value:
|
||||
case *Flag:
|
||||
return Visit(node.Value, visitor)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported node type %T", node))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func visitNodeChildren(node *Node, visitor Visitor) error {
|
||||
if node.Argument != nil {
|
||||
if err := Visit(node.Argument, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, flag := range node.Flags {
|
||||
if err := Visit(flag, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, pos := range node.Positional {
|
||||
if err := Visit(pos, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, child := range node.Children {
|
||||
if err := Visit(child, visitor); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.idea
|
||||
*.sw?
|
||||
.vscode
|
|
@ -1,18 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
|
||||
script:
|
||||
- go get -d -t ./...
|
||||
- go vet ./...
|
||||
- go test ./...
|
||||
- >
|
||||
go_version=$(go version);
|
||||
if [ ${go_version:13:4} = "1.12" ]; then
|
||||
go get -u golang.org/x/tools/cmd/goimports;
|
||||
goimports -d -e ./ | grep '.*' && { echo; echo "Aborting due to non-empty goimports output."; exit 1; } || :;
|
||||
fi
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
## v4.0.0 (2019-01-10)
|
||||
|
||||
- chi v4 requires Go 1.10.3+ (or Go 1.9.7+) - we have deprecated support for Go 1.7 and 1.8
|
||||
- router: respond with 404 on router with no routes (#362)
|
||||
- router: additional check to ensure wildcard is at the end of a url pattern (#333)
|
||||
- middleware: deprecate use of http.CloseNotifier (#347)
|
||||
- middleware: fix RedirectSlashes to include query params on redirect (#334)
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.4...v4.0.0
|
||||
|
||||
|
||||
## v3.3.4 (2019-01-07)
|
||||
|
||||
- Minor middleware improvements. No changes to core library/router. Moving v3 into its
|
||||
- own branch as a version of chi for Go 1.7, 1.8, 1.9, 1.10, 1.11
|
||||
- History of changes: see https://github.com/go-chi/chi/compare/v3.3.3...v3.3.4
|
||||
|
||||
|
||||
## v3.3.3 (2018-08-27)
|
||||
|
||||
- Minor release
|
||||
- See https://github.com/go-chi/chi/compare/v3.3.2...v3.3.3
|
||||
|
||||
|
||||
## v3.3.2 (2017-12-22)
|
||||
|
||||
- Support to route trailing slashes on mounted sub-routers (#281)
|
||||
- middleware: new `ContentCharset` to check matching charsets. Thank you
|
||||
@csucu for your community contribution!
|
||||
|
||||
|
||||
## v3.3.1 (2017-11-20)
|
||||
|
||||
- middleware: new `AllowContentType` handler for explicit whitelist of accepted request Content-Types
|
||||
- middleware: new `SetHeader` handler for short-hand middleware to set a response header key/value
|
||||
- Minor bug fixes
|
||||
|
||||
|
||||
## v3.3.0 (2017-10-10)
|
||||
|
||||
- New chi.RegisterMethod(method) to add support for custom HTTP methods, see _examples/custom-method for usage
|
||||
- Deprecated LINK and UNLINK methods from the default list, please use `chi.RegisterMethod("LINK")` and `chi.RegisterMethod("UNLINK")` in an `init()` function
|
||||
|
||||
|
||||
## v3.2.1 (2017-08-31)
|
||||
|
||||
- Add new `Match(rctx *Context, method, path string) bool` method to `Routes` interface
|
||||
and `Mux`. Match searches the mux's routing tree for a handler that matches the method/path
|
||||
- Add new `RouteMethod` to `*Context`
|
||||
- Add new `Routes` pointer to `*Context`
|
||||
- Add new `middleware.GetHead` to route missing HEAD requests to GET handler
|
||||
- Updated benchmarks (see README)
|
||||
|
||||
|
||||
## v3.1.5 (2017-08-02)
|
||||
|
||||
- Setup golint and go vet for the project
|
||||
- As per golint, we've redefined `func ServerBaseContext(h http.Handler, baseCtx context.Context) http.Handler`
|
||||
to `func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler`
|
||||
|
||||
|
||||
## v3.1.0 (2017-07-10)
|
||||
|
||||
- Fix a few minor issues after v3 release
|
||||
- Move `docgen` sub-pkg to https://github.com/go-chi/docgen
|
||||
- Move `render` sub-pkg to https://github.com/go-chi/render
|
||||
- Add new `URLFormat` handler to chi/middleware sub-pkg to make working with url mime
|
||||
suffixes easier, ie. parsing `/articles/1.json` and `/articles/1.xml`. See comments in
|
||||
https://github.com/go-chi/chi/blob/master/middleware/url_format.go for example usage.
|
||||
|
||||
|
||||
## v3.0.0 (2017-06-21)
|
||||
|
||||
- Major update to chi library with many exciting updates, but also some *breaking changes*
|
||||
- URL parameter syntax changed from `/:id` to `/{id}` for even more flexible routing, such as
|
||||
`/articles/{month}-{day}-{year}-{slug}`, `/articles/{id}`, and `/articles/{id}.{ext}` on the
|
||||
same router
|
||||
- Support for regexp for routing patterns, in the form of `/{paramKey:regExp}` for example:
|
||||
`r.Get("/articles/{name:[a-z]+}", h)` and `chi.URLParam(r, "name")`
|
||||
- Add `Method` and `MethodFunc` to `chi.Router` to allow routing definitions such as
|
||||
`r.Method("GET", "/", h)` which provides a cleaner interface for custom handlers like
|
||||
in `_examples/custom-handler`
|
||||
- Deprecating `mux#FileServer` helper function. Instead, we encourage users to create their
|
||||
own using file handler with the stdlib, see `_examples/fileserver` for an example
|
||||
- Add support for LINK/UNLINK http methods via `r.Method()` and `r.MethodFunc()`
|
||||
- Moved the chi project to its own organization, to allow chi-related community packages to
|
||||
be easily discovered and supported, at: https://github.com/go-chi
|
||||
- *NOTE:* please update your import paths to `"github.com/go-chi/chi"`
|
||||
- *NOTE:* chi v2 is still available at https://github.com/go-chi/chi/tree/v2
|
||||
|
||||
|
||||
## v2.1.0 (2017-03-30)
|
||||
|
||||
- Minor improvements and update to the chi core library
|
||||
- Introduced a brand new `chi/render` sub-package to complete the story of building
|
||||
APIs to offer a pattern for managing well-defined request / response payloads. Please
|
||||
check out the updated `_examples/rest` example for how it works.
|
||||
- Added `MethodNotAllowed(h http.HandlerFunc)` to chi.Router interface
|
||||
|
||||
|
||||
## v2.0.0 (2017-01-06)
|
||||
|
||||
- After many months of v2 being in an RC state with many companies and users running it in
|
||||
production, the inclusion of some improvements to the middlewares, we are very pleased to
|
||||
announce v2.0.0 of chi.
|
||||
|
||||
|
||||
## v2.0.0-rc1 (2016-07-26)
|
||||
|
||||
- Huge update! chi v2 is a large refactor targetting Go 1.7+. As of Go 1.7, the popular
|
||||
community `"net/context"` package has been included in the standard library as `"context"` and
|
||||
utilized by `"net/http"` and `http.Request` to managing deadlines, cancelation signals and other
|
||||
request-scoped values. We're very excited about the new context addition and are proud to
|
||||
introduce chi v2, a minimal and powerful routing package for building large HTTP services,
|
||||
with zero external dependencies. Chi focuses on idiomatic design and encourages the use of
|
||||
stdlib HTTP handlers and middlwares.
|
||||
- chi v2 deprecates its `chi.Handler` interface and requires `http.Handler` or `http.HandlerFunc`
|
||||
- chi v2 stores URL routing parameters and patterns in the standard request context: `r.Context()`
|
||||
- chi v2 lower-level routing context is accessible by `chi.RouteContext(r.Context()) *chi.Context`,
|
||||
which provides direct access to URL routing parameters, the routing path and the matching
|
||||
routing patterns.
|
||||
- Users upgrading from chi v1 to v2, need to:
|
||||
1. Update the old chi.Handler signature, `func(ctx context.Context, w http.ResponseWriter, r *http.Request)` to
|
||||
the standard http.Handler: `func(w http.ResponseWriter, r *http.Request)`
|
||||
2. Use `chi.URLParam(r *http.Request, paramKey string) string`
|
||||
or `URLParamFromCtx(ctx context.Context, paramKey string) string` to access a url parameter value
|
||||
|
||||
|
||||
## v1.0.0 (2016-07-01)
|
||||
|
||||
- Released chi v1 stable https://github.com/go-chi/chi/tree/v1.0.0 for Go 1.6 and older.
|
||||
|
||||
|
||||
## v0.9.0 (2016-03-31)
|
||||
|
||||
- Reuse context objects via sync.Pool for zero-allocation routing [#33](https://github.com/go-chi/chi/pull/33)
|
||||
- BREAKING NOTE: due to subtle API changes, previously `chi.URLParams(ctx)["id"]` used to access url parameters
|
||||
has changed to: `chi.URLParam(ctx, "id")`
|
|
@ -1,31 +0,0 @@
|
|||
# Contributing
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. [Install Go][go-install].
|
||||
2. Download the sources and switch the working directory:
|
||||
|
||||
```bash
|
||||
go get -u -d github.com/go-chi/chi
|
||||
cd $GOPATH/src/github.com/go-chi/chi
|
||||
```
|
||||
|
||||
## Submitting a Pull Request
|
||||
|
||||
A typical workflow is:
|
||||
|
||||
1. [Fork the repository.][fork] [This tip maybe also helpful.][go-fork-tip]
|
||||
2. [Create a topic branch.][branch]
|
||||
3. Add tests for your change.
|
||||
4. Run `go test`. If your tests pass, return to the step 3.
|
||||
5. Implement the change and ensure the steps from the previous step pass.
|
||||
6. Run `goimports -w .`, to ensure the new code conforms to Go formatting guideline.
|
||||
7. [Add, commit and push your changes.][git-help]
|
||||
8. [Submit a pull request.][pull-req]
|
||||
|
||||
[go-install]: https://golang.org/doc/install
|
||||
[go-fork-tip]: http://blog.campoy.cat/2014/03/github-and-go-forking-pull-requests-and.html
|
||||
[fork]: https://help.github.com/articles/fork-a-repo
|
||||
[branch]: http://learn.github.com/p/branching.html
|
||||
[git-help]: https://guides.github.com
|
||||
[pull-req]: https://help.github.com/articles/using-pull-requests
|
|
@ -1,20 +0,0 @@
|
|||
Copyright (c) 2015-present Peter Kieltyka (https://github.com/pkieltyka), Google Inc.
|
||||
|
||||
MIT License
|
||||
|
||||
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.
|
|
@ -1,438 +0,0 @@
|
|||
# <img alt="chi" src="https://cdn.rawgit.com/go-chi/chi/master/_examples/chi.svg" width="220" />
|
||||
|
||||
|
||||
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
|
||||
|
||||
`chi` is a lightweight, idiomatic and composable router for building Go HTTP services. It's
|
||||
especially good at helping you write large REST API services that are kept maintainable as your
|
||||
project grows and changes. `chi` is built on the new `context` package introduced in Go 1.7 to
|
||||
handle signaling, cancelation and request-scoped values across a handler chain.
|
||||
|
||||
The focus of the project has been to seek out an elegant and comfortable design for writing
|
||||
REST API servers, written during the development of the Pressly API service that powers our
|
||||
public API service, which in turn powers all of our client-side applications.
|
||||
|
||||
The key considerations of chi's design are: project structure, maintainability, standard http
|
||||
handlers (stdlib-only), developer productivity, and deconstructing a large system into many small
|
||||
parts. The core router `github.com/go-chi/chi` is quite small (less than 1000 LOC), but we've also
|
||||
included some useful/optional subpackages: [middleware](/middleware), [render](https://github.com/go-chi/render) and [docgen](https://github.com/go-chi/docgen). We hope you enjoy it too!
|
||||
|
||||
## Install
|
||||
|
||||
`go get -u github.com/go-chi/chi`
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
* **Lightweight** - cloc'd in ~1000 LOC for the chi router
|
||||
* **Fast** - yes, see [benchmarks](#benchmarks)
|
||||
* **100% compatible with net/http** - use any http or middleware pkg in the ecosystem that is also compatible with `net/http`
|
||||
* **Designed for modular/composable APIs** - middlewares, inline middlewares, route groups and subrouter mounting
|
||||
* **Context control** - built on new `context` package, providing value chaining, cancelations and timeouts
|
||||
* **Robust** - in production at Pressly, CloudFlare, Heroku, 99Designs, and many others (see [discussion](https://github.com/go-chi/chi/issues/91))
|
||||
* **Doc generation** - `docgen` auto-generates routing documentation from your source to JSON or Markdown
|
||||
* **No external dependencies** - plain ol' Go stdlib + net/http
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
See [_examples/](https://github.com/go-chi/chi/blob/master/_examples/) for a variety of examples.
|
||||
|
||||
|
||||
**As easy as:**
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/go-chi/chi"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("welcome"))
|
||||
})
|
||||
http.ListenAndServe(":3000", r)
|
||||
}
|
||||
```
|
||||
|
||||
**REST Preview:**
|
||||
|
||||
Here is a little preview of how routing looks like with chi. Also take a look at the generated routing docs
|
||||
in JSON ([routes.json](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.json)) and in
|
||||
Markdown ([routes.md](https://github.com/go-chi/chi/blob/master/_examples/rest/routes.md)).
|
||||
|
||||
I highly recommend reading the source of the [examples](https://github.com/go-chi/chi/blob/master/_examples/) listed
|
||||
above, they will show you all the features of chi and serve as a good form of documentation.
|
||||
|
||||
```go
|
||||
import (
|
||||
//...
|
||||
"context"
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/go-chi/chi/middleware"
|
||||
)
|
||||
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
|
||||
// A good base middleware stack
|
||||
r.Use(middleware.RequestID)
|
||||
r.Use(middleware.RealIP)
|
||||
r.Use(middleware.Logger)
|
||||
r.Use(middleware.Recoverer)
|
||||
|
||||
// Set a timeout value on the request context (ctx), that will signal
|
||||
// through ctx.Done() that the request has timed out and further
|
||||
// processing should be stopped.
|
||||
r.Use(middleware.Timeout(60 * time.Second))
|
||||
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
})
|
||||
|
||||
// RESTy routes for "articles" resource
|
||||
r.Route("/articles", func(r chi.Router) {
|
||||
r.With(paginate).Get("/", listArticles) // GET /articles
|
||||
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017
|
||||
|
||||
r.Post("/", createArticle) // POST /articles
|
||||
r.Get("/search", searchArticles) // GET /articles/search
|
||||
|
||||
// Regexp url parameters:
|
||||
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto
|
||||
|
||||
// Subrouters:
|
||||
r.Route("/{articleID}", func(r chi.Router) {
|
||||
r.Use(ArticleCtx)
|
||||
r.Get("/", getArticle) // GET /articles/123
|
||||
r.Put("/", updateArticle) // PUT /articles/123
|
||||
r.Delete("/", deleteArticle) // DELETE /articles/123
|
||||
})
|
||||
})
|
||||
|
||||
// Mount the admin sub-router
|
||||
r.Mount("/admin", adminRouter())
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func ArticleCtx(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
articleID := chi.URLParam(r, "articleID")
|
||||
article, err := dbGetArticle(articleID)
|
||||
if err != nil {
|
||||
http.Error(w, http.StatusText(404), 404)
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), "article", article)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func getArticle(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
article, ok := ctx.Value("article").(*Article)
|
||||
if !ok {
|
||||
http.Error(w, http.StatusText(422), 422)
|
||||
return
|
||||
}
|
||||
w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
|
||||
}
|
||||
|
||||
// A completely separate router for administrator routes
|
||||
func adminRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
r.Use(AdminOnly)
|
||||
r.Get("/", adminIndex)
|
||||
r.Get("/accounts", adminListAccounts)
|
||||
return r
|
||||
}
|
||||
|
||||
func AdminOnly(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
perm, ok := ctx.Value("acl.permission").(YourPermissionType)
|
||||
if !ok || !perm.IsAdmin() {
|
||||
http.Error(w, http.StatusText(403), 403)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Router design
|
||||
|
||||
chi's router is based on a kind of [Patricia Radix trie](https://en.wikipedia.org/wiki/Radix_tree).
|
||||
The router is fully compatible with `net/http`.
|
||||
|
||||
Built on top of the tree is the `Router` interface:
|
||||
|
||||
```go
|
||||
// Router consisting of the core routing methods used by chi's Mux,
|
||||
// using only the standard net/http.
|
||||
type Router interface {
|
||||
http.Handler
|
||||
Routes
|
||||
|
||||
// Use appends one of more middlewares onto the Router stack.
|
||||
Use(middlewares ...func(http.Handler) http.Handler)
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
With(middlewares ...func(http.Handler) http.Handler) Router
|
||||
|
||||
// Group adds a new inline-Router along the current routing
|
||||
// path, with a fresh middleware stack for the inline-Router.
|
||||
Group(fn func(r Router)) Router
|
||||
|
||||
// Route mounts a sub-Router along a `pattern`` string.
|
||||
Route(pattern string, fn func(r Router)) Router
|
||||
|
||||
// Mount attaches another http.Handler along ./pattern/*
|
||||
Mount(pattern string, h http.Handler)
|
||||
|
||||
// Handle and HandleFunc adds routes for `pattern` that matches
|
||||
// all HTTP methods.
|
||||
Handle(pattern string, h http.Handler)
|
||||
HandleFunc(pattern string, h http.HandlerFunc)
|
||||
|
||||
// Method and MethodFunc adds routes for `pattern` that matches
|
||||
// the `method` HTTP method.
|
||||
Method(method, pattern string, h http.Handler)
|
||||
MethodFunc(method, pattern string, h http.HandlerFunc)
|
||||
|
||||
// HTTP-method routing along `pattern`
|
||||
Connect(pattern string, h http.HandlerFunc)
|
||||
Delete(pattern string, h http.HandlerFunc)
|
||||
Get(pattern string, h http.HandlerFunc)
|
||||
Head(pattern string, h http.HandlerFunc)
|
||||
Options(pattern string, h http.HandlerFunc)
|
||||
Patch(pattern string, h http.HandlerFunc)
|
||||
Post(pattern string, h http.HandlerFunc)
|
||||
Put(pattern string, h http.HandlerFunc)
|
||||
Trace(pattern string, h http.HandlerFunc)
|
||||
|
||||
// NotFound defines a handler to respond whenever a route could
|
||||
// not be found.
|
||||
NotFound(h http.HandlerFunc)
|
||||
|
||||
// MethodNotAllowed defines a handler to respond whenever a method is
|
||||
// not allowed.
|
||||
MethodNotAllowed(h http.HandlerFunc)
|
||||
}
|
||||
|
||||
// Routes interface adds two methods for router traversal, which is also
|
||||
// used by the github.com/go-chi/docgen package to generate documentation for Routers.
|
||||
type Routes interface {
|
||||
// Routes returns the routing tree in an easily traversable structure.
|
||||
Routes() []Route
|
||||
|
||||
// Middlewares returns the list of middlewares in use by the router.
|
||||
Middlewares() Middlewares
|
||||
|
||||
// Match searches the routing tree for a handler that matches
|
||||
// the method/path - similar to routing a http request, but without
|
||||
// executing the handler thereafter.
|
||||
Match(rctx *Context, method, path string) bool
|
||||
}
|
||||
```
|
||||
|
||||
Each routing method accepts a URL `pattern` and chain of `handlers`. The URL pattern
|
||||
supports named params (ie. `/users/{userID}`) and wildcards (ie. `/admin/*`). URL parameters
|
||||
can be fetched at runtime by calling `chi.URLParam(r, "userID")` for named parameters
|
||||
and `chi.URLParam(r, "*")` for a wildcard parameter.
|
||||
|
||||
|
||||
### Middleware handlers
|
||||
|
||||
chi's middlewares are just stdlib net/http middleware handlers. There is nothing special
|
||||
about them, which means the router and all the tooling is designed to be compatible and
|
||||
friendly with any middleware in the community. This offers much better extensibility and reuse
|
||||
of packages and is at the heart of chi's purpose.
|
||||
|
||||
Here is an example of a standard net/http middleware handler using the new request context
|
||||
available in Go. This middleware sets a hypothetical user identifier on the request
|
||||
context and calls the next handler in the chain.
|
||||
|
||||
```go
|
||||
// HTTP middleware setting a value on the request context
|
||||
func MyMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := context.WithValue(r.Context(), "user", "123")
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### Request handlers
|
||||
|
||||
chi uses standard net/http request handlers. This little snippet is an example of a http.Handler
|
||||
func that reads a user identifier from the request context - hypothetically, identifying
|
||||
the user sending an authenticated request, validated+set by a previous middleware handler.
|
||||
|
||||
```go
|
||||
// HTTP handler accessing data from the request context.
|
||||
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Context().Value("user").(string)
|
||||
w.Write([]byte(fmt.Sprintf("hi %s", user)))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### URL parameters
|
||||
|
||||
chi's router parses and stores URL parameters right onto the request context. Here is
|
||||
an example of how to access URL params in your net/http handlers. And of course, middlewares
|
||||
are able to access the same information.
|
||||
|
||||
```go
|
||||
// HTTP handler accessing the url routing parameters.
|
||||
func MyRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
userID := chi.URLParam(r, "userID") // from a route like /users/{userID}
|
||||
|
||||
ctx := r.Context()
|
||||
key := ctx.Value("key").(string)
|
||||
|
||||
w.Write([]byte(fmt.Sprintf("hi %v, %v", userID, key)))
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Middlewares
|
||||
|
||||
chi comes equipped with an optional `middleware` package, providing a suite of standard
|
||||
`net/http` middlewares. Please note, any middleware in the ecosystem that is also compatible
|
||||
with `net/http` can be used with chi's mux.
|
||||
|
||||
### Core middlewares
|
||||
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
| chi/middleware Handler | description |
|
||||
|:----------------------|:---------------------------------------------------------------------------------
|
||||
| AllowContentType | Explicit whitelist of accepted request Content-Types |
|
||||
| Compress | Gzip compression for clients that accept compressed responses |
|
||||
| GetHead | Automatically route undefined HEAD requests to GET handlers |
|
||||
| Heartbeat | Monitoring endpoint to check the servers pulse |
|
||||
| Logger | Logs the start and end of each request with the elapsed processing time |
|
||||
| NoCache | Sets response headers to prevent clients from caching |
|
||||
| Profiler | Easily attach net/http/pprof to your routers |
|
||||
| RealIP | Sets a http.Request's RemoteAddr to either X-Forwarded-For or X-Real-IP |
|
||||
| Recoverer | Gracefully absorb panics and prints the stack trace |
|
||||
| RequestID | Injects a request ID into the context of each request |
|
||||
| RedirectSlashes | Redirect slashes on routing paths |
|
||||
| SetHeader | Short-hand middleware to set a response header key/value |
|
||||
| StripSlashes | Strip slashes on routing paths |
|
||||
| Throttle | Puts a ceiling on the number of concurrent requests |
|
||||
| Timeout | Signals to the request context when the timeout deadline is reached |
|
||||
| URLFormat | Parse extension from url and put it on request context |
|
||||
| WithValue | Short-hand middleware to set a key/value on the request context |
|
||||
-----------------------------------------------------------------------------------------------------------
|
||||
|
||||
### Auxiliary middlewares & packages
|
||||
|
||||
Please see https://github.com/go-chi for additional packages.
|
||||
|
||||
--------------------------------------------------------------------------------------------------------------------
|
||||
| package | description |
|
||||
|:---------------------------------------------------|:-------------------------------------------------------------
|
||||
| [cors](https://github.com/go-chi/cors) | Cross-origin resource sharing (CORS) |
|
||||
| [docgen](https://github.com/go-chi/docgen) | Print chi.Router routes at runtime |
|
||||
| [jwtauth](https://github.com/go-chi/jwtauth) | JWT authentication |
|
||||
| [hostrouter](https://github.com/go-chi/hostrouter) | Domain/host based request routing |
|
||||
| [httpcoala](https://github.com/go-chi/httpcoala) | HTTP request coalescer |
|
||||
| [chi-authz](https://github.com/casbin/chi-authz) | Request ACL via https://github.com/hsluoyz/casbin |
|
||||
| [phi](https://github.com/fate-lovely/phi) | Port chi to [fasthttp](https://github.com/valyala/fasthttp) |
|
||||
--------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
please [submit a PR](./CONTRIBUTING.md) if you'd like to include a link to a chi-compatible middleware
|
||||
|
||||
|
||||
## context?
|
||||
|
||||
`context` is a tiny pkg that provides simple interface to signal context across call stacks
|
||||
and goroutines. It was originally written by [Sameer Ajmani](https://github.com/Sajmani)
|
||||
and is available in stdlib since go1.7.
|
||||
|
||||
Learn more at https://blog.golang.org/context
|
||||
|
||||
and..
|
||||
* Docs: https://golang.org/pkg/context
|
||||
* Source: https://github.com/golang/go/tree/master/src/context
|
||||
|
||||
|
||||
## Benchmarks
|
||||
|
||||
The benchmark suite: https://github.com/pkieltyka/go-http-routing-benchmark
|
||||
|
||||
Results as of Jan 9, 2019 with Go 1.11.4 on Linux X1 Carbon laptop
|
||||
|
||||
```shell
|
||||
BenchmarkChi_Param 3000000 475 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param5 2000000 696 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Param20 1000000 1275 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParamWrite 3000000 505 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubStatic 3000000 508 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubParam 2000000 669 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GithubAll 10000 134627 ns/op 87699 B/op 609 allocs/op
|
||||
BenchmarkChi_GPlusStatic 3000000 402 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusParam 3000000 500 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlus2Params 3000000 586 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_GPlusAll 200000 7237 ns/op 5616 B/op 39 allocs/op
|
||||
BenchmarkChi_ParseStatic 3000000 408 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseParam 3000000 488 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_Parse2Params 3000000 551 ns/op 432 B/op 3 allocs/op
|
||||
BenchmarkChi_ParseAll 100000 13508 ns/op 11232 B/op 78 allocs/op
|
||||
BenchmarkChi_StaticAll 20000 81933 ns/op 67826 B/op 471 allocs/op
|
||||
```
|
||||
|
||||
Comparison with other routers: https://gist.github.com/pkieltyka/123032f12052520aaccab752bd3e78cc
|
||||
|
||||
NOTE: the allocs in the benchmark above are from the calls to http.Request's
|
||||
`WithContext(context.Context)` method that clones the http.Request, sets the `Context()`
|
||||
on the duplicated (alloc'd) request and returns it the new request object. This is just
|
||||
how setting context on a request in Go works.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
* Carl Jackson for https://github.com/zenazn/goji
|
||||
* Parts of chi's thinking comes from goji, and chi's middleware package
|
||||
sources from goji.
|
||||
* Armon Dadgar for https://github.com/armon/go-radix
|
||||
* Contributions: [@VojtechVitek](https://github.com/VojtechVitek)
|
||||
|
||||
We'll be more than happy to see [your contributions](./CONTRIBUTING.md)!
|
||||
|
||||
|
||||
## Beyond REST
|
||||
|
||||
chi is just a http router that lets you decompose request handling into many smaller layers.
|
||||
Many companies including Pressly.com (of course) use chi to write REST services for their public
|
||||
APIs. But, REST is just a convention for managing state via HTTP, and there's a lot of other pieces
|
||||
required to write a complete client-server system or network of microservices.
|
||||
|
||||
Looking ahead beyond REST, I also recommend some newer works in the field coming from
|
||||
[gRPC](https://github.com/grpc/grpc-go), [NATS](https://nats.io), [go-kit](https://github.com/go-kit/kit)
|
||||
and even [graphql](https://github.com/graphql-go/graphql). They're all pretty cool with their
|
||||
own unique approaches and benefits. Specifically, I'd look at gRPC since it makes client-server
|
||||
communication feel like a single program on a single computer, no need to hand-write a client library
|
||||
and the request/response payloads are typed contracts. NATS is pretty amazing too as a super
|
||||
fast and lightweight pub-sub transport that can speak protobufs, with nice service discovery -
|
||||
an excellent combination with gRPC.
|
||||
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2015-present [Peter Kieltyka](https://github.com/pkieltyka)
|
||||
|
||||
Licensed under [MIT License](./LICENSE)
|
||||
|
||||
[GoDoc]: https://godoc.org/github.com/go-chi/chi
|
||||
[GoDoc Widget]: https://godoc.org/github.com/go-chi/chi?status.svg
|
||||
[Travis]: https://travis-ci.org/go-chi/chi
|
||||
[Travis Widget]: https://travis-ci.org/go-chi/chi.svg?branch=master
|
|
@ -1,49 +0,0 @@
|
|||
package chi
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Chain returns a Middlewares type from a slice of middleware handlers.
|
||||
func Chain(middlewares ...func(http.Handler) http.Handler) Middlewares {
|
||||
return Middlewares(middlewares)
|
||||
}
|
||||
|
||||
// Handler builds and returns a http.Handler from the chain of middlewares,
|
||||
// with `h http.Handler` as the final handler.
|
||||
func (mws Middlewares) Handler(h http.Handler) http.Handler {
|
||||
return &ChainHandler{mws, h, chain(mws, h)}
|
||||
}
|
||||
|
||||
// HandlerFunc builds and returns a http.Handler from the chain of middlewares,
|
||||
// with `h http.Handler` as the final handler.
|
||||
func (mws Middlewares) HandlerFunc(h http.HandlerFunc) http.Handler {
|
||||
return &ChainHandler{mws, h, chain(mws, h)}
|
||||
}
|
||||
|
||||
// ChainHandler is a http.Handler with support for handler composition and
|
||||
// execution.
|
||||
type ChainHandler struct {
|
||||
Middlewares Middlewares
|
||||
Endpoint http.Handler
|
||||
chain http.Handler
|
||||
}
|
||||
|
||||
func (c *ChainHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c.chain.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// chain builds a http.Handler composed of an inline middleware stack and endpoint
|
||||
// handler in the order they are passed.
|
||||
func chain(middlewares []func(http.Handler) http.Handler, endpoint http.Handler) http.Handler {
|
||||
// Return ahead of time if there aren't any middlewares for the chain
|
||||
if len(middlewares) == 0 {
|
||||
return endpoint
|
||||
}
|
||||
|
||||
// Wrap the end handler with the middleware chain
|
||||
h := middlewares[len(middlewares)-1](endpoint)
|
||||
for i := len(middlewares) - 2; i >= 0; i-- {
|
||||
h = middlewares[i](h)
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
|
@ -1,134 +0,0 @@
|
|||
//
|
||||
// Package chi is a small, idiomatic and composable router for building HTTP services.
|
||||
//
|
||||
// chi requires Go 1.7 or newer.
|
||||
//
|
||||
// Example:
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "net/http"
|
||||
//
|
||||
// "github.com/go-chi/chi"
|
||||
// "github.com/go-chi/chi/middleware"
|
||||
// )
|
||||
//
|
||||
// func main() {
|
||||
// r := chi.NewRouter()
|
||||
// r.Use(middleware.Logger)
|
||||
// r.Use(middleware.Recoverer)
|
||||
//
|
||||
// r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// w.Write([]byte("root."))
|
||||
// })
|
||||
//
|
||||
// http.ListenAndServe(":3333", r)
|
||||
// }
|
||||
//
|
||||
// See github.com/go-chi/chi/_examples/ for more in-depth examples.
|
||||
//
|
||||
// URL patterns allow for easy matching of path components in HTTP
|
||||
// requests. The matching components can then be accessed using
|
||||
// chi.URLParam(). All patterns must begin with a slash.
|
||||
//
|
||||
// A simple named placeholder {name} matches any sequence of characters
|
||||
// up to the next / or the end of the URL. Trailing slashes on paths must
|
||||
// be handled explicitly.
|
||||
//
|
||||
// A placeholder with a name followed by a colon allows a regular
|
||||
// expression match, for example {number:\\d+}. The regular expression
|
||||
// syntax is Go's normal regexp RE2 syntax, except that regular expressions
|
||||
// including { or } are not supported, and / will never be
|
||||
// matched. An anonymous regexp pattern is allowed, using an empty string
|
||||
// before the colon in the placeholder, such as {:\\d+}
|
||||
//
|
||||
// The special placeholder of asterisk matches the rest of the requested
|
||||
// URL. Any trailing characters in the pattern are ignored. This is the only
|
||||
// placeholder which will match / characters.
|
||||
//
|
||||
// Examples:
|
||||
// "/user/{name}" matches "/user/jsmith" but not "/user/jsmith/info" or "/user/jsmith/"
|
||||
// "/user/{name}/info" matches "/user/jsmith/info"
|
||||
// "/page/*" matches "/page/intro/latest"
|
||||
// "/page/*/index" also matches "/page/intro/latest"
|
||||
// "/date/{yyyy:\\d\\d\\d\\d}/{mm:\\d\\d}/{dd:\\d\\d}" matches "/date/2017/04/01"
|
||||
//
|
||||
package chi
|
||||
|
||||
import "net/http"
|
||||
|
||||
// NewRouter returns a new Mux object that implements the Router interface.
|
||||
func NewRouter() *Mux {
|
||||
return NewMux()
|
||||
}
|
||||
|
||||
// Router consisting of the core routing methods used by chi's Mux,
|
||||
// using only the standard net/http.
|
||||
type Router interface {
|
||||
http.Handler
|
||||
Routes
|
||||
|
||||
// Use appends one of more middlewares onto the Router stack.
|
||||
Use(middlewares ...func(http.Handler) http.Handler)
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
With(middlewares ...func(http.Handler) http.Handler) Router
|
||||
|
||||
// Group adds a new inline-Router along the current routing
|
||||
// path, with a fresh middleware stack for the inline-Router.
|
||||
Group(fn func(r Router)) Router
|
||||
|
||||
// Route mounts a sub-Router along a `pattern`` string.
|
||||
Route(pattern string, fn func(r Router)) Router
|
||||
|
||||
// Mount attaches another http.Handler along ./pattern/*
|
||||
Mount(pattern string, h http.Handler)
|
||||
|
||||
// Handle and HandleFunc adds routes for `pattern` that matches
|
||||
// all HTTP methods.
|
||||
Handle(pattern string, h http.Handler)
|
||||
HandleFunc(pattern string, h http.HandlerFunc)
|
||||
|
||||
// Method and MethodFunc adds routes for `pattern` that matches
|
||||
// the `method` HTTP method.
|
||||
Method(method, pattern string, h http.Handler)
|
||||
MethodFunc(method, pattern string, h http.HandlerFunc)
|
||||
|
||||
// HTTP-method routing along `pattern`
|
||||
Connect(pattern string, h http.HandlerFunc)
|
||||
Delete(pattern string, h http.HandlerFunc)
|
||||
Get(pattern string, h http.HandlerFunc)
|
||||
Head(pattern string, h http.HandlerFunc)
|
||||
Options(pattern string, h http.HandlerFunc)
|
||||
Patch(pattern string, h http.HandlerFunc)
|
||||
Post(pattern string, h http.HandlerFunc)
|
||||
Put(pattern string, h http.HandlerFunc)
|
||||
Trace(pattern string, h http.HandlerFunc)
|
||||
|
||||
// NotFound defines a handler to respond whenever a route could
|
||||
// not be found.
|
||||
NotFound(h http.HandlerFunc)
|
||||
|
||||
// MethodNotAllowed defines a handler to respond whenever a method is
|
||||
// not allowed.
|
||||
MethodNotAllowed(h http.HandlerFunc)
|
||||
}
|
||||
|
||||
// Routes interface adds two methods for router traversal, which is also
|
||||
// used by the `docgen` subpackage to generation documentation for Routers.
|
||||
type Routes interface {
|
||||
// Routes returns the routing tree in an easily traversable structure.
|
||||
Routes() []Route
|
||||
|
||||
// Middlewares returns the list of middlewares in use by the router.
|
||||
Middlewares() Middlewares
|
||||
|
||||
// Match searches the routing tree for a handler that matches
|
||||
// the method/path - similar to routing a http request, but without
|
||||
// executing the handler thereafter.
|
||||
Match(rctx *Context, method, path string) bool
|
||||
}
|
||||
|
||||
// Middlewares type is a slice of standard middleware handlers with methods
|
||||
// to compose middleware chains and http.Handler's.
|
||||
type Middlewares []func(http.Handler) http.Handler
|
|
@ -1,161 +0,0 @@
|
|||
package chi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// RouteCtxKey is the context.Context key to store the request context.
|
||||
RouteCtxKey = &contextKey{"RouteContext"}
|
||||
)
|
||||
|
||||
// Context is the default routing context set on the root node of a
|
||||
// request context to track route patterns, URL parameters and
|
||||
// an optional routing path.
|
||||
type Context struct {
|
||||
Routes Routes
|
||||
|
||||
// Routing path/method override used during the route search.
|
||||
// See Mux#routeHTTP method.
|
||||
RoutePath string
|
||||
RouteMethod string
|
||||
|
||||
// Routing pattern stack throughout the lifecycle of the request,
|
||||
// across all connected routers. It is a record of all matching
|
||||
// patterns across a stack of sub-routers.
|
||||
RoutePatterns []string
|
||||
|
||||
// URLParams are the stack of routeParams captured during the
|
||||
// routing lifecycle across a stack of sub-routers.
|
||||
URLParams RouteParams
|
||||
|
||||
// The endpoint routing pattern that matched the request URI path
|
||||
// or `RoutePath` of the current sub-router. This value will update
|
||||
// during the lifecycle of a request passing through a stack of
|
||||
// sub-routers.
|
||||
routePattern string
|
||||
|
||||
// Route parameters matched for the current sub-router. It is
|
||||
// intentionally unexported so it cant be tampered.
|
||||
routeParams RouteParams
|
||||
|
||||
// methodNotAllowed hint
|
||||
methodNotAllowed bool
|
||||
}
|
||||
|
||||
// NewRouteContext returns a new routing Context object.
|
||||
func NewRouteContext() *Context {
|
||||
return &Context{}
|
||||
}
|
||||
|
||||
// Reset a routing context to its initial state.
|
||||
func (x *Context) Reset() {
|
||||
x.Routes = nil
|
||||
x.RoutePath = ""
|
||||
x.RouteMethod = ""
|
||||
x.RoutePatterns = x.RoutePatterns[:0]
|
||||
x.URLParams.Keys = x.URLParams.Keys[:0]
|
||||
x.URLParams.Values = x.URLParams.Values[:0]
|
||||
|
||||
x.routePattern = ""
|
||||
x.routeParams.Keys = x.routeParams.Keys[:0]
|
||||
x.routeParams.Values = x.routeParams.Values[:0]
|
||||
x.methodNotAllowed = false
|
||||
}
|
||||
|
||||
// URLParam returns the corresponding URL parameter value from the request
|
||||
// routing context.
|
||||
func (x *Context) URLParam(key string) string {
|
||||
for k := len(x.URLParams.Keys) - 1; k >= 0; k-- {
|
||||
if x.URLParams.Keys[k] == key {
|
||||
return x.URLParams.Values[k]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RoutePattern builds the routing pattern string for the particular
|
||||
// request, at the particular point during routing. This means, the value
|
||||
// will change throughout the execution of a request in a router. That is
|
||||
// why its advised to only use this value after calling the next handler.
|
||||
//
|
||||
// For example,
|
||||
//
|
||||
// func Instrument(next http.Handler) http.Handler {
|
||||
// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// next.ServeHTTP(w, r)
|
||||
// routePattern := chi.RouteContext(r.Context()).RoutePattern()
|
||||
// measure(w, r, routePattern)
|
||||
// })
|
||||
// }
|
||||
func (x *Context) RoutePattern() string {
|
||||
routePattern := strings.Join(x.RoutePatterns, "")
|
||||
return strings.Replace(routePattern, "/*/", "/", -1)
|
||||
}
|
||||
|
||||
// RouteContext returns chi's routing Context object from a
|
||||
// http.Request Context.
|
||||
func RouteContext(ctx context.Context) *Context {
|
||||
return ctx.Value(RouteCtxKey).(*Context)
|
||||
}
|
||||
|
||||
// URLParam returns the url parameter from a http.Request object.
|
||||
func URLParam(r *http.Request, key string) string {
|
||||
if rctx := RouteContext(r.Context()); rctx != nil {
|
||||
return rctx.URLParam(key)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// URLParamFromCtx returns the url parameter from a http.Request Context.
|
||||
func URLParamFromCtx(ctx context.Context, key string) string {
|
||||
if rctx := RouteContext(ctx); rctx != nil {
|
||||
return rctx.URLParam(key)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// RouteParams is a structure to track URL routing parameters efficiently.
|
||||
type RouteParams struct {
|
||||
Keys, Values []string
|
||||
}
|
||||
|
||||
// Add will append a URL parameter to the end of the route param
|
||||
func (s *RouteParams) Add(key, value string) {
|
||||
(*s).Keys = append((*s).Keys, key)
|
||||
(*s).Values = append((*s).Values, value)
|
||||
}
|
||||
|
||||
// ServerBaseContext wraps an http.Handler to set the request context to the
|
||||
// `baseCtx`.
|
||||
func ServerBaseContext(baseCtx context.Context, h http.Handler) http.Handler {
|
||||
fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
baseCtx := baseCtx
|
||||
|
||||
// Copy over default net/http server context keys
|
||||
if v, ok := ctx.Value(http.ServerContextKey).(*http.Server); ok {
|
||||
baseCtx = context.WithValue(baseCtx, http.ServerContextKey, v)
|
||||
}
|
||||
if v, ok := ctx.Value(http.LocalAddrContextKey).(net.Addr); ok {
|
||||
baseCtx = context.WithValue(baseCtx, http.LocalAddrContextKey, v)
|
||||
}
|
||||
|
||||
h.ServeHTTP(w, r.WithContext(baseCtx))
|
||||
})
|
||||
return fn
|
||||
}
|
||||
|
||||
// contextKey is a value for use with context.WithValue. It's used as
|
||||
// a pointer so it fits in an interface{} without allocation. This technique
|
||||
// for defining context keys was copied from Go 1.7's new use of context in net/http.
|
||||
type contextKey struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (k *contextKey) String() string {
|
||||
return "chi context value " + k.name
|
||||
}
|
|
@ -1,460 +0,0 @@
|
|||
package chi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var _ Router = &Mux{}
|
||||
|
||||
// Mux is a simple HTTP route multiplexer that parses a request path,
|
||||
// records any URL params, and executes an end handler. It implements
|
||||
// the http.Handler interface and is friendly with the standard library.
|
||||
//
|
||||
// Mux is designed to be fast, minimal and offer a powerful API for building
|
||||
// modular and composable HTTP services with a large set of handlers. It's
|
||||
// particularly useful for writing large REST API services that break a handler
|
||||
// into many smaller parts composed of middlewares and end handlers.
|
||||
type Mux struct {
|
||||
// The radix trie router
|
||||
tree *node
|
||||
|
||||
// The middleware stack
|
||||
middlewares []func(http.Handler) http.Handler
|
||||
|
||||
// Controls the behaviour of middleware chain generation when a mux
|
||||
// is registered as an inline group inside another mux.
|
||||
inline bool
|
||||
parent *Mux
|
||||
|
||||
// The computed mux handler made of the chained middleware stack and
|
||||
// the tree router
|
||||
handler http.Handler
|
||||
|
||||
// Routing context pool
|
||||
pool *sync.Pool
|
||||
|
||||
// Custom route not found handler
|
||||
notFoundHandler http.HandlerFunc
|
||||
|
||||
// Custom method not allowed handler
|
||||
methodNotAllowedHandler http.HandlerFunc
|
||||
}
|
||||
|
||||
// NewMux returns a newly initialized Mux object that implements the Router
|
||||
// interface.
|
||||
func NewMux() *Mux {
|
||||
mux := &Mux{tree: &node{}, pool: &sync.Pool{}}
|
||||
mux.pool.New = func() interface{} {
|
||||
return NewRouteContext()
|
||||
}
|
||||
return mux
|
||||
}
|
||||
|
||||
// ServeHTTP is the single method of the http.Handler interface that makes
|
||||
// Mux interoperable with the standard library. It uses a sync.Pool to get and
|
||||
// reuse routing contexts for each request.
|
||||
func (mx *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Ensure the mux has some routes defined on the mux
|
||||
if mx.handler == nil {
|
||||
mx.NotFoundHandler().ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if a routing context already exists from a parent router.
|
||||
rctx, _ := r.Context().Value(RouteCtxKey).(*Context)
|
||||
if rctx != nil {
|
||||
mx.handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch a RouteContext object from the sync pool, and call the computed
|
||||
// mx.handler that is comprised of mx.middlewares + mx.routeHTTP.
|
||||
// Once the request is finished, reset the routing context and put it back
|
||||
// into the pool for reuse from another request.
|
||||
rctx = mx.pool.Get().(*Context)
|
||||
rctx.Reset()
|
||||
rctx.Routes = mx
|
||||
r = r.WithContext(context.WithValue(r.Context(), RouteCtxKey, rctx))
|
||||
mx.handler.ServeHTTP(w, r)
|
||||
mx.pool.Put(rctx)
|
||||
}
|
||||
|
||||
// Use appends a middleware handler to the Mux middleware stack.
|
||||
//
|
||||
// The middleware stack for any Mux will execute before searching for a matching
|
||||
// route to a specific handler, which provides opportunity to respond early,
|
||||
// change the course of the request execution, or set request-scoped values for
|
||||
// the next http.Handler.
|
||||
func (mx *Mux) Use(middlewares ...func(http.Handler) http.Handler) {
|
||||
if mx.handler != nil {
|
||||
panic("chi: all middlewares must be defined before routes on a mux")
|
||||
}
|
||||
mx.middlewares = append(mx.middlewares, middlewares...)
|
||||
}
|
||||
|
||||
// Handle adds the route `pattern` that matches any http method to
|
||||
// execute the `handler` http.Handler.
|
||||
func (mx *Mux) Handle(pattern string, handler http.Handler) {
|
||||
mx.handle(mALL, pattern, handler)
|
||||
}
|
||||
|
||||
// HandleFunc adds the route `pattern` that matches any http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) HandleFunc(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mALL, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Method adds the route `pattern` that matches `method` http method to
|
||||
// execute the `handler` http.Handler.
|
||||
func (mx *Mux) Method(method, pattern string, handler http.Handler) {
|
||||
m, ok := methodMap[strings.ToUpper(method)]
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("chi: '%s' http method is not supported.", method))
|
||||
}
|
||||
mx.handle(m, pattern, handler)
|
||||
}
|
||||
|
||||
// MethodFunc adds the route `pattern` that matches `method` http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) MethodFunc(method, pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.Method(method, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Connect adds the route `pattern` that matches a CONNECT http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Connect(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mCONNECT, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Delete adds the route `pattern` that matches a DELETE http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Delete(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mDELETE, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Get adds the route `pattern` that matches a GET http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Get(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mGET, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Head adds the route `pattern` that matches a HEAD http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Head(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mHEAD, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Options adds the route `pattern` that matches a OPTIONS http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Options(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mOPTIONS, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Patch adds the route `pattern` that matches a PATCH http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Patch(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mPATCH, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Post adds the route `pattern` that matches a POST http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Post(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mPOST, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Put adds the route `pattern` that matches a PUT http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Put(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mPUT, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// Trace adds the route `pattern` that matches a TRACE http method to
|
||||
// execute the `handlerFn` http.HandlerFunc.
|
||||
func (mx *Mux) Trace(pattern string, handlerFn http.HandlerFunc) {
|
||||
mx.handle(mTRACE, pattern, handlerFn)
|
||||
}
|
||||
|
||||
// NotFound sets a custom http.HandlerFunc for routing paths that could
|
||||
// not be found. The default 404 handler is `http.NotFound`.
|
||||
func (mx *Mux) NotFound(handlerFn http.HandlerFunc) {
|
||||
// Build NotFound handler chain
|
||||
m := mx
|
||||
hFn := handlerFn
|
||||
if mx.inline && mx.parent != nil {
|
||||
m = mx.parent
|
||||
hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
|
||||
}
|
||||
|
||||
// Update the notFoundHandler from this point forward
|
||||
m.notFoundHandler = hFn
|
||||
m.updateSubRoutes(func(subMux *Mux) {
|
||||
if subMux.notFoundHandler == nil {
|
||||
subMux.NotFound(hFn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// MethodNotAllowed sets a custom http.HandlerFunc for routing paths where the
|
||||
// method is unresolved. The default handler returns a 405 with an empty body.
|
||||
func (mx *Mux) MethodNotAllowed(handlerFn http.HandlerFunc) {
|
||||
// Build MethodNotAllowed handler chain
|
||||
m := mx
|
||||
hFn := handlerFn
|
||||
if mx.inline && mx.parent != nil {
|
||||
m = mx.parent
|
||||
hFn = Chain(mx.middlewares...).HandlerFunc(hFn).ServeHTTP
|
||||
}
|
||||
|
||||
// Update the methodNotAllowedHandler from this point forward
|
||||
m.methodNotAllowedHandler = hFn
|
||||
m.updateSubRoutes(func(subMux *Mux) {
|
||||
if subMux.methodNotAllowedHandler == nil {
|
||||
subMux.MethodNotAllowed(hFn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// With adds inline middlewares for an endpoint handler.
|
||||
func (mx *Mux) With(middlewares ...func(http.Handler) http.Handler) Router {
|
||||
// Similarly as in handle(), we must build the mux handler once further
|
||||
// middleware registration isn't allowed for this stack, like now.
|
||||
if !mx.inline && mx.handler == nil {
|
||||
mx.buildRouteHandler()
|
||||
}
|
||||
|
||||
// Copy middlewares from parent inline muxs
|
||||
var mws Middlewares
|
||||
if mx.inline {
|
||||
mws = make(Middlewares, len(mx.middlewares))
|
||||
copy(mws, mx.middlewares)
|
||||
}
|
||||
mws = append(mws, middlewares...)
|
||||
|
||||
im := &Mux{pool: mx.pool, inline: true, parent: mx, tree: mx.tree, middlewares: mws}
|
||||
|
||||
return im
|
||||
}
|
||||
|
||||
// Group creates a new inline-Mux with a fresh middleware stack. It's useful
|
||||
// for a group of handlers along the same routing path that use an additional
|
||||
// set of middlewares. See _examples/.
|
||||
func (mx *Mux) Group(fn func(r Router)) Router {
|
||||
im := mx.With().(*Mux)
|
||||
if fn != nil {
|
||||
fn(im)
|
||||
}
|
||||
return im
|
||||
}
|
||||
|
||||
// Route creates a new Mux with a fresh middleware stack and mounts it
|
||||
// along the `pattern` as a subrouter. Effectively, this is a short-hand
|
||||
// call to Mount. See _examples/.
|
||||
func (mx *Mux) Route(pattern string, fn func(r Router)) Router {
|
||||
subRouter := NewRouter()
|
||||
if fn != nil {
|
||||
fn(subRouter)
|
||||
}
|
||||
mx.Mount(pattern, subRouter)
|
||||
return subRouter
|
||||
}
|
||||
|
||||
// Mount attaches another http.Handler or chi Router as a subrouter along a routing
|
||||
// path. It's very useful to split up a large API as many independent routers and
|
||||
// compose them as a single service using Mount. See _examples/.
|
||||
//
|
||||
// Note that Mount() simply sets a wildcard along the `pattern` that will continue
|
||||
// routing at the `handler`, which in most cases is another chi.Router. As a result,
|
||||
// if you define two Mount() routes on the exact same pattern the mount will panic.
|
||||
func (mx *Mux) Mount(pattern string, handler http.Handler) {
|
||||
// Provide runtime safety for ensuring a pattern isn't mounted on an existing
|
||||
// routing pattern.
|
||||
if mx.tree.findPattern(pattern+"*") || mx.tree.findPattern(pattern+"/*") {
|
||||
panic(fmt.Sprintf("chi: attempting to Mount() a handler on an existing path, '%s'", pattern))
|
||||
}
|
||||
|
||||
// Assign sub-Router's with the parent not found & method not allowed handler if not specified.
|
||||
subr, ok := handler.(*Mux)
|
||||
if ok && subr.notFoundHandler == nil && mx.notFoundHandler != nil {
|
||||
subr.NotFound(mx.notFoundHandler)
|
||||
}
|
||||
if ok && subr.methodNotAllowedHandler == nil && mx.methodNotAllowedHandler != nil {
|
||||
subr.MethodNotAllowed(mx.methodNotAllowedHandler)
|
||||
}
|
||||
|
||||
// Wrap the sub-router in a handlerFunc to scope the request path for routing.
|
||||
mountHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rctx := RouteContext(r.Context())
|
||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
|
||||
if pattern == "" || pattern[len(pattern)-1] != '/' {
|
||||
mx.handle(mALL|mSTUB, pattern, mountHandler)
|
||||
mx.handle(mALL|mSTUB, pattern+"/", mountHandler)
|
||||
pattern += "/"
|
||||
}
|
||||
|
||||
method := mALL
|
||||
subroutes, _ := handler.(Routes)
|
||||
if subroutes != nil {
|
||||
method |= mSTUB
|
||||
}
|
||||
n := mx.handle(method, pattern+"*", mountHandler)
|
||||
|
||||
if subroutes != nil {
|
||||
n.subroutes = subroutes
|
||||
}
|
||||
}
|
||||
|
||||
// Routes returns a slice of routing information from the tree,
|
||||
// useful for traversing available routes of a router.
|
||||
func (mx *Mux) Routes() []Route {
|
||||
return mx.tree.routes()
|
||||
}
|
||||
|
||||
// Middlewares returns a slice of middleware handler functions.
|
||||
func (mx *Mux) Middlewares() Middlewares {
|
||||
return mx.middlewares
|
||||
}
|
||||
|
||||
// Match searches the routing tree for a handler that matches the method/path.
|
||||
// It's similar to routing a http request, but without executing the handler
|
||||
// thereafter.
|
||||
//
|
||||
// Note: the *Context state is updated during execution, so manage
|
||||
// the state carefully or make a NewRouteContext().
|
||||
func (mx *Mux) Match(rctx *Context, method, path string) bool {
|
||||
m, ok := methodMap[method]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
node, _, h := mx.tree.FindRoute(rctx, m, path)
|
||||
|
||||
if node != nil && node.subroutes != nil {
|
||||
rctx.RoutePath = mx.nextRoutePath(rctx)
|
||||
return node.subroutes.Match(rctx, method, rctx.RoutePath)
|
||||
}
|
||||
|
||||
return h != nil
|
||||
}
|
||||
|
||||
// NotFoundHandler returns the default Mux 404 responder whenever a route
|
||||
// cannot be found.
|
||||
func (mx *Mux) NotFoundHandler() http.HandlerFunc {
|
||||
if mx.notFoundHandler != nil {
|
||||
return mx.notFoundHandler
|
||||
}
|
||||
return http.NotFound
|
||||
}
|
||||
|
||||
// MethodNotAllowedHandler returns the default Mux 405 responder whenever
|
||||
// a method cannot be resolved for a route.
|
||||
func (mx *Mux) MethodNotAllowedHandler() http.HandlerFunc {
|
||||
if mx.methodNotAllowedHandler != nil {
|
||||
return mx.methodNotAllowedHandler
|
||||
}
|
||||
return methodNotAllowedHandler
|
||||
}
|
||||
|
||||
// buildRouteHandler builds the single mux handler that is a chain of the middleware
|
||||
// stack, as defined by calls to Use(), and the tree router (Mux) itself. After this
|
||||
// point, no other middlewares can be registered on this Mux's stack. But you can still
|
||||
// compose additional middlewares via Group()'s or using a chained middleware handler.
|
||||
func (mx *Mux) buildRouteHandler() {
|
||||
mx.handler = chain(mx.middlewares, http.HandlerFunc(mx.routeHTTP))
|
||||
}
|
||||
|
||||
// handle registers a http.Handler in the routing tree for a particular http method
|
||||
// and routing pattern.
|
||||
func (mx *Mux) handle(method methodTyp, pattern string, handler http.Handler) *node {
|
||||
if len(pattern) == 0 || pattern[0] != '/' {
|
||||
panic(fmt.Sprintf("chi: routing pattern must begin with '/' in '%s'", pattern))
|
||||
}
|
||||
|
||||
// Build the final routing handler for this Mux.
|
||||
if !mx.inline && mx.handler == nil {
|
||||
mx.buildRouteHandler()
|
||||
}
|
||||
|
||||
// Build endpoint handler with inline middlewares for the route
|
||||
var h http.Handler
|
||||
if mx.inline {
|
||||
mx.handler = http.HandlerFunc(mx.routeHTTP)
|
||||
h = Chain(mx.middlewares...).Handler(handler)
|
||||
} else {
|
||||
h = handler
|
||||
}
|
||||
|
||||
// Add the endpoint to the tree and return the node
|
||||
return mx.tree.InsertRoute(method, pattern, h)
|
||||
}
|
||||
|
||||
// routeHTTP routes a http.Request through the Mux routing tree to serve
|
||||
// the matching handler for a particular http method.
|
||||
func (mx *Mux) routeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Grab the route context object
|
||||
rctx := r.Context().Value(RouteCtxKey).(*Context)
|
||||
|
||||
// The request routing path
|
||||
routePath := rctx.RoutePath
|
||||
if routePath == "" {
|
||||
if r.URL.RawPath != "" {
|
||||
routePath = r.URL.RawPath
|
||||
} else {
|
||||
routePath = r.URL.Path
|
||||
}
|
||||
}
|
||||
|
||||
// Check if method is supported by chi
|
||||
if rctx.RouteMethod == "" {
|
||||
rctx.RouteMethod = r.Method
|
||||
}
|
||||
method, ok := methodMap[rctx.RouteMethod]
|
||||
if !ok {
|
||||
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Find the route
|
||||
if _, _, h := mx.tree.FindRoute(rctx, method, routePath); h != nil {
|
||||
h.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if rctx.methodNotAllowed {
|
||||
mx.MethodNotAllowedHandler().ServeHTTP(w, r)
|
||||
} else {
|
||||
mx.NotFoundHandler().ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (mx *Mux) nextRoutePath(rctx *Context) string {
|
||||
routePath := "/"
|
||||
nx := len(rctx.routeParams.Keys) - 1 // index of last param in list
|
||||
if nx >= 0 && rctx.routeParams.Keys[nx] == "*" && len(rctx.routeParams.Values) > nx {
|
||||
routePath += rctx.routeParams.Values[nx]
|
||||
}
|
||||
return routePath
|
||||
}
|
||||
|
||||
// Recursively update data on child routers.
|
||||
func (mx *Mux) updateSubRoutes(fn func(subMux *Mux)) {
|
||||
for _, r := range mx.tree.routes() {
|
||||
subMux, ok := r.SubRoutes.(*Mux)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
fn(subMux)
|
||||
}
|
||||
}
|
||||
|
||||
// methodNotAllowedHandler is a helper function to respond with a 405,
|
||||
// method not allowed.
|
||||
func methodNotAllowedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(405)
|
||||
w.Write(nil)
|
||||
}
|
|
@ -1,846 +0,0 @@
|
|||
package chi
|
||||
|
||||
// Radix tree implementation below is a based on the original work by
|
||||
// Armon Dadgar in https://github.com/armon/go-radix/blob/master/radix.go
|
||||
// (MIT licensed). It's been heavily modified for use as a HTTP routing tree.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type methodTyp int
|
||||
|
||||
const (
|
||||
mSTUB methodTyp = 1 << iota
|
||||
mCONNECT
|
||||
mDELETE
|
||||
mGET
|
||||
mHEAD
|
||||
mOPTIONS
|
||||
mPATCH
|
||||
mPOST
|
||||
mPUT
|
||||
mTRACE
|
||||
)
|
||||
|
||||
var mALL = mCONNECT | mDELETE | mGET | mHEAD |
|
||||
mOPTIONS | mPATCH | mPOST | mPUT | mTRACE
|
||||
|
||||
var methodMap = map[string]methodTyp{
|
||||
http.MethodConnect: mCONNECT,
|
||||
http.MethodDelete: mDELETE,
|
||||
http.MethodGet: mGET,
|
||||
http.MethodHead: mHEAD,
|
||||
http.MethodOptions: mOPTIONS,
|
||||
http.MethodPatch: mPATCH,
|
||||
http.MethodPost: mPOST,
|
||||
http.MethodPut: mPUT,
|
||||
http.MethodTrace: mTRACE,
|
||||
}
|
||||
|
||||
// RegisterMethod adds support for custom HTTP method handlers, available
|
||||
// via Router#Method and Router#MethodFunc
|
||||
func RegisterMethod(method string) {
|
||||
if method == "" {
|
||||
return
|
||||
}
|
||||
method = strings.ToUpper(method)
|
||||
if _, ok := methodMap[method]; ok {
|
||||
return
|
||||
}
|
||||
n := len(methodMap)
|
||||
if n > strconv.IntSize {
|
||||
panic(fmt.Sprintf("chi: max number of methods reached (%d)", strconv.IntSize))
|
||||
}
|
||||
mt := methodTyp(math.Exp2(float64(n)))
|
||||
methodMap[method] = mt
|
||||
mALL |= mt
|
||||
}
|
||||
|
||||
type nodeTyp uint8
|
||||
|
||||
const (
|
||||
ntStatic nodeTyp = iota // /home
|
||||
ntRegexp // /{id:[0-9]+}
|
||||
ntParam // /{user}
|
||||
ntCatchAll // /api/v1/*
|
||||
)
|
||||
|
||||
type node struct {
|
||||
// node type: static, regexp, param, catchAll
|
||||
typ nodeTyp
|
||||
|
||||
// first byte of the prefix
|
||||
label byte
|
||||
|
||||
// first byte of the child prefix
|
||||
tail byte
|
||||
|
||||
// prefix is the common prefix we ignore
|
||||
prefix string
|
||||
|
||||
// regexp matcher for regexp nodes
|
||||
rex *regexp.Regexp
|
||||
|
||||
// HTTP handler endpoints on the leaf node
|
||||
endpoints endpoints
|
||||
|
||||
// subroutes on the leaf node
|
||||
subroutes Routes
|
||||
|
||||
// child nodes should be stored in-order for iteration,
|
||||
// in groups of the node type.
|
||||
children [ntCatchAll + 1]nodes
|
||||
}
|
||||
|
||||
// endpoints is a mapping of http method constants to handlers
|
||||
// for a given route.
|
||||
type endpoints map[methodTyp]*endpoint
|
||||
|
||||
type endpoint struct {
|
||||
// endpoint handler
|
||||
handler http.Handler
|
||||
|
||||
// pattern is the routing pattern for handler nodes
|
||||
pattern string
|
||||
|
||||
// parameter keys recorded on handler nodes
|
||||
paramKeys []string
|
||||
}
|
||||
|
||||
func (s endpoints) Value(method methodTyp) *endpoint {
|
||||
mh, ok := s[method]
|
||||
if !ok {
|
||||
mh = &endpoint{}
|
||||
s[method] = mh
|
||||
}
|
||||
return mh
|
||||
}
|
||||
|
||||
func (n *node) InsertRoute(method methodTyp, pattern string, handler http.Handler) *node {
|
||||
var parent *node
|
||||
search := pattern
|
||||
|
||||
for {
|
||||
// Handle key exhaustion
|
||||
if len(search) == 0 {
|
||||
// Insert or update the node's leaf handler
|
||||
n.setEndpoint(method, handler, pattern)
|
||||
return n
|
||||
}
|
||||
|
||||
// We're going to be searching for a wild node next,
|
||||
// in this case, we need to get the tail
|
||||
var label = search[0]
|
||||
var segTail byte
|
||||
var segEndIdx int
|
||||
var segTyp nodeTyp
|
||||
var segRexpat string
|
||||
if label == '{' || label == '*' {
|
||||
segTyp, _, segRexpat, segTail, _, segEndIdx = patNextSegment(search)
|
||||
}
|
||||
|
||||
var prefix string
|
||||
if segTyp == ntRegexp {
|
||||
prefix = segRexpat
|
||||
}
|
||||
|
||||
// Look for the edge to attach to
|
||||
parent = n
|
||||
n = n.getEdge(segTyp, label, segTail, prefix)
|
||||
|
||||
// No edge, create one
|
||||
if n == nil {
|
||||
child := &node{label: label, tail: segTail, prefix: search}
|
||||
hn := parent.addChild(child, search)
|
||||
hn.setEndpoint(method, handler, pattern)
|
||||
|
||||
return hn
|
||||
}
|
||||
|
||||
// Found an edge to match the pattern
|
||||
|
||||
if n.typ > ntStatic {
|
||||
// We found a param node, trim the param from the search path and continue.
|
||||
// This param/wild pattern segment would already be on the tree from a previous
|
||||
// call to addChild when creating a new node.
|
||||
search = search[segEndIdx:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Static nodes fall below here.
|
||||
// Determine longest prefix of the search key on match.
|
||||
commonPrefix := longestPrefix(search, n.prefix)
|
||||
if commonPrefix == len(n.prefix) {
|
||||
// the common prefix is as long as the current node's prefix we're attempting to insert.
|
||||
// keep the search going.
|
||||
search = search[commonPrefix:]
|
||||
continue
|
||||
}
|
||||
|
||||
// Split the node
|
||||
child := &node{
|
||||
typ: ntStatic,
|
||||
prefix: search[:commonPrefix],
|
||||
}
|
||||
parent.replaceChild(search[0], segTail, child)
|
||||
|
||||
// Restore the existing node
|
||||
n.label = n.prefix[commonPrefix]
|
||||
n.prefix = n.prefix[commonPrefix:]
|
||||
child.addChild(n, n.prefix)
|
||||
|
||||
// If the new key is a subset, set the method/handler on this node and finish.
|
||||
search = search[commonPrefix:]
|
||||
if len(search) == 0 {
|
||||
child.setEndpoint(method, handler, pattern)
|
||||
return child
|
||||
}
|
||||
|
||||
// Create a new edge for the node
|
||||
subchild := &node{
|
||||
typ: ntStatic,
|
||||
label: search[0],
|
||||
prefix: search,
|
||||
}
|
||||
hn := child.addChild(subchild, search)
|
||||
hn.setEndpoint(method, handler, pattern)
|
||||
return hn
|
||||
}
|
||||
}
|
||||
|
||||
// addChild appends the new `child` node to the tree using the `pattern` as the trie key.
|
||||
// For a URL router like chi's, we split the static, param, regexp and wildcard segments
|
||||
// into different nodes. In addition, addChild will recursively call itself until every
|
||||
// pattern segment is added to the url pattern tree as individual nodes, depending on type.
|
||||
func (n *node) addChild(child *node, prefix string) *node {
|
||||
search := prefix
|
||||
|
||||
// handler leaf node added to the tree is the child.
|
||||
// this may be overridden later down the flow
|
||||
hn := child
|
||||
|
||||
// Parse next segment
|
||||
segTyp, _, segRexpat, segTail, segStartIdx, segEndIdx := patNextSegment(search)
|
||||
|
||||
// Add child depending on next up segment
|
||||
switch segTyp {
|
||||
|
||||
case ntStatic:
|
||||
// Search prefix is all static (that is, has no params in path)
|
||||
// noop
|
||||
|
||||
default:
|
||||
// Search prefix contains a param, regexp or wildcard
|
||||
|
||||
if segTyp == ntRegexp {
|
||||
rex, err := regexp.Compile(segRexpat)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("chi: invalid regexp pattern '%s' in route param", segRexpat))
|
||||
}
|
||||
child.prefix = segRexpat
|
||||
child.rex = rex
|
||||
}
|
||||
|
||||
if segStartIdx == 0 {
|
||||
// Route starts with a param
|
||||
child.typ = segTyp
|
||||
|
||||
if segTyp == ntCatchAll {
|
||||
segStartIdx = -1
|
||||
} else {
|
||||
segStartIdx = segEndIdx
|
||||
}
|
||||
if segStartIdx < 0 {
|
||||
segStartIdx = len(search)
|
||||
}
|
||||
child.tail = segTail // for params, we set the tail
|
||||
|
||||
if segStartIdx != len(search) {
|
||||
// add static edge for the remaining part, split the end.
|
||||
// its not possible to have adjacent param nodes, so its certainly
|
||||
// going to be a static node next.
|
||||
|
||||
search = search[segStartIdx:] // advance search position
|
||||
|
||||
nn := &node{
|
||||
typ: ntStatic,
|
||||
label: search[0],
|
||||
prefix: search,
|
||||
}
|
||||
hn = child.addChild(nn, search)
|
||||
}
|
||||
|
||||
} else if segStartIdx > 0 {
|
||||
// Route has some param
|
||||
|
||||
// starts with a static segment
|
||||
child.typ = ntStatic
|
||||
child.prefix = search[:segStartIdx]
|
||||
child.rex = nil
|
||||
|
||||
// add the param edge node
|
||||
search = search[segStartIdx:]
|
||||
|
||||
nn := &node{
|
||||
typ: segTyp,
|
||||
label: search[0],
|
||||
tail: segTail,
|
||||
}
|
||||
hn = child.addChild(nn, search)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
n.children[child.typ] = append(n.children[child.typ], child)
|
||||
n.children[child.typ].Sort()
|
||||
return hn
|
||||
}
|
||||
|
||||
func (n *node) replaceChild(label, tail byte, child *node) {
|
||||
for i := 0; i < len(n.children[child.typ]); i++ {
|
||||
if n.children[child.typ][i].label == label && n.children[child.typ][i].tail == tail {
|
||||
n.children[child.typ][i] = child
|
||||
n.children[child.typ][i].label = label
|
||||
n.children[child.typ][i].tail = tail
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("chi: replacing missing child")
|
||||
}
|
||||
|
||||
func (n *node) getEdge(ntyp nodeTyp, label, tail byte, prefix string) *node {
|
||||
nds := n.children[ntyp]
|
||||
for i := 0; i < len(nds); i++ {
|
||||
if nds[i].label == label && nds[i].tail == tail {
|
||||
if ntyp == ntRegexp && nds[i].prefix != prefix {
|
||||
continue
|
||||
}
|
||||
return nds[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) setEndpoint(method methodTyp, handler http.Handler, pattern string) {
|
||||
// Set the handler for the method type on the node
|
||||
if n.endpoints == nil {
|
||||
n.endpoints = make(endpoints, 0)
|
||||
}
|
||||
|
||||
paramKeys := patParamKeys(pattern)
|
||||
|
||||
if method&mSTUB == mSTUB {
|
||||
n.endpoints.Value(mSTUB).handler = handler
|
||||
}
|
||||
if method&mALL == mALL {
|
||||
h := n.endpoints.Value(mALL)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
for _, m := range methodMap {
|
||||
h := n.endpoints.Value(m)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
}
|
||||
} else {
|
||||
h := n.endpoints.Value(method)
|
||||
h.handler = handler
|
||||
h.pattern = pattern
|
||||
h.paramKeys = paramKeys
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) FindRoute(rctx *Context, method methodTyp, path string) (*node, endpoints, http.Handler) {
|
||||
// Reset the context routing pattern and params
|
||||
rctx.routePattern = ""
|
||||
rctx.routeParams.Keys = rctx.routeParams.Keys[:0]
|
||||
rctx.routeParams.Values = rctx.routeParams.Values[:0]
|
||||
|
||||
// Find the routing handlers for the path
|
||||
rn := n.findRoute(rctx, method, path)
|
||||
if rn == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Record the routing params in the request lifecycle
|
||||
rctx.URLParams.Keys = append(rctx.URLParams.Keys, rctx.routeParams.Keys...)
|
||||
rctx.URLParams.Values = append(rctx.URLParams.Values, rctx.routeParams.Values...)
|
||||
|
||||
// Record the routing pattern in the request lifecycle
|
||||
if rn.endpoints[method].pattern != "" {
|
||||
rctx.routePattern = rn.endpoints[method].pattern
|
||||
rctx.RoutePatterns = append(rctx.RoutePatterns, rctx.routePattern)
|
||||
}
|
||||
|
||||
return rn, rn.endpoints, rn.endpoints[method].handler
|
||||
}
|
||||
|
||||
// Recursive edge traversal by checking all nodeTyp groups along the way.
|
||||
// It's like searching through a multi-dimensional radix trie.
|
||||
func (n *node) findRoute(rctx *Context, method methodTyp, path string) *node {
|
||||
nn := n
|
||||
search := path
|
||||
|
||||
for t, nds := range nn.children {
|
||||
ntyp := nodeTyp(t)
|
||||
if len(nds) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
var xn *node
|
||||
xsearch := search
|
||||
|
||||
var label byte
|
||||
if search != "" {
|
||||
label = search[0]
|
||||
}
|
||||
|
||||
switch ntyp {
|
||||
case ntStatic:
|
||||
xn = nds.findEdge(label)
|
||||
if xn == nil || !strings.HasPrefix(xsearch, xn.prefix) {
|
||||
continue
|
||||
}
|
||||
xsearch = xsearch[len(xn.prefix):]
|
||||
|
||||
case ntParam, ntRegexp:
|
||||
// short-circuit and return no matching route for empty param values
|
||||
if xsearch == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// serially loop through each node grouped by the tail delimiter
|
||||
for idx := 0; idx < len(nds); idx++ {
|
||||
xn = nds[idx]
|
||||
|
||||
// label for param nodes is the delimiter byte
|
||||
p := strings.IndexByte(xsearch, xn.tail)
|
||||
|
||||
if p < 0 {
|
||||
if xn.tail == '/' {
|
||||
p = len(xsearch)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if ntyp == ntRegexp && xn.rex != nil {
|
||||
if xn.rex.Match([]byte(xsearch[:p])) == false {
|
||||
continue
|
||||
}
|
||||
} else if strings.IndexByte(xsearch[:p], '/') != -1 {
|
||||
// avoid a match across path segments
|
||||
continue
|
||||
}
|
||||
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, xsearch[:p])
|
||||
xsearch = xsearch[p:]
|
||||
break
|
||||
}
|
||||
|
||||
default:
|
||||
// catch-all nodes
|
||||
rctx.routeParams.Values = append(rctx.routeParams.Values, search)
|
||||
xn = nds[0]
|
||||
xsearch = ""
|
||||
}
|
||||
|
||||
if xn == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// did we find it yet?
|
||||
if len(xsearch) == 0 {
|
||||
if xn.isLeaf() {
|
||||
h, _ := xn.endpoints[method]
|
||||
if h != nil && h.handler != nil {
|
||||
rctx.routeParams.Keys = append(rctx.routeParams.Keys, h.paramKeys...)
|
||||
return xn
|
||||
}
|
||||
|
||||
// flag that the routing context found a route, but not a corresponding
|
||||
// supported method
|
||||
rctx.methodNotAllowed = true
|
||||
}
|
||||
}
|
||||
|
||||
// recursively find the next node..
|
||||
fin := xn.findRoute(rctx, method, xsearch)
|
||||
if fin != nil {
|
||||
return fin
|
||||
}
|
||||
|
||||
// Did not find final handler, let's remove the param here if it was set
|
||||
if xn.typ > ntStatic {
|
||||
if len(rctx.routeParams.Values) > 0 {
|
||||
rctx.routeParams.Values = rctx.routeParams.Values[:len(rctx.routeParams.Values)-1]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) findEdge(ntyp nodeTyp, label byte) *node {
|
||||
nds := n.children[ntyp]
|
||||
num := len(nds)
|
||||
idx := 0
|
||||
|
||||
switch ntyp {
|
||||
case ntStatic, ntParam, ntRegexp:
|
||||
i, j := 0, num-1
|
||||
for i <= j {
|
||||
idx = i + (j-i)/2
|
||||
if label > nds[idx].label {
|
||||
i = idx + 1
|
||||
} else if label < nds[idx].label {
|
||||
j = idx - 1
|
||||
} else {
|
||||
i = num // breaks cond
|
||||
}
|
||||
}
|
||||
if nds[idx].label != label {
|
||||
return nil
|
||||
}
|
||||
return nds[idx]
|
||||
|
||||
default: // catch all
|
||||
return nds[idx]
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) isEmpty() bool {
|
||||
for _, nds := range n.children {
|
||||
if len(nds) > 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (n *node) isLeaf() bool {
|
||||
return n.endpoints != nil
|
||||
}
|
||||
|
||||
func (n *node) findPattern(pattern string) bool {
|
||||
nn := n
|
||||
for _, nds := range nn.children {
|
||||
if len(nds) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
n = nn.findEdge(nds[0].typ, pattern[0])
|
||||
if n == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var idx int
|
||||
var xpattern string
|
||||
|
||||
switch n.typ {
|
||||
case ntStatic:
|
||||
idx = longestPrefix(pattern, n.prefix)
|
||||
if idx < len(n.prefix) {
|
||||
continue
|
||||
}
|
||||
|
||||
case ntParam, ntRegexp:
|
||||
idx = strings.IndexByte(pattern, '}') + 1
|
||||
|
||||
case ntCatchAll:
|
||||
idx = longestPrefix(pattern, "*")
|
||||
|
||||
default:
|
||||
panic("chi: unknown node type")
|
||||
}
|
||||
|
||||
xpattern = pattern[idx:]
|
||||
if len(xpattern) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return n.findPattern(xpattern)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (n *node) routes() []Route {
|
||||
rts := []Route{}
|
||||
|
||||
n.walk(func(eps endpoints, subroutes Routes) bool {
|
||||
if eps[mSTUB] != nil && eps[mSTUB].handler != nil && subroutes == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Group methodHandlers by unique patterns
|
||||
pats := make(map[string]endpoints, 0)
|
||||
|
||||
for mt, h := range eps {
|
||||
if h.pattern == "" {
|
||||
continue
|
||||
}
|
||||
p, ok := pats[h.pattern]
|
||||
if !ok {
|
||||
p = endpoints{}
|
||||
pats[h.pattern] = p
|
||||
}
|
||||
p[mt] = h
|
||||
}
|
||||
|
||||
for p, mh := range pats {
|
||||
hs := make(map[string]http.Handler, 0)
|
||||
if mh[mALL] != nil && mh[mALL].handler != nil {
|
||||
hs["*"] = mh[mALL].handler
|
||||
}
|
||||
|
||||
for mt, h := range mh {
|
||||
if h.handler == nil {
|
||||
continue
|
||||
}
|
||||
m := methodTypString(mt)
|
||||
if m == "" {
|
||||
continue
|
||||
}
|
||||
hs[m] = h.handler
|
||||
}
|
||||
|
||||
rt := Route{p, hs, subroutes}
|
||||
rts = append(rts, rt)
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
return rts
|
||||
}
|
||||
|
||||
func (n *node) walk(fn func(eps endpoints, subroutes Routes) bool) bool {
|
||||
// Visit the leaf values if any
|
||||
if (n.endpoints != nil || n.subroutes != nil) && fn(n.endpoints, n.subroutes) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Recurse on the children
|
||||
for _, ns := range n.children {
|
||||
for _, cn := range ns {
|
||||
if cn.walk(fn) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// patNextSegment returns the next segment details from a pattern:
|
||||
// node type, param key, regexp string, param tail byte, param starting index, param ending index
|
||||
func patNextSegment(pattern string) (nodeTyp, string, string, byte, int, int) {
|
||||
ps := strings.Index(pattern, "{")
|
||||
ws := strings.Index(pattern, "*")
|
||||
|
||||
if ps < 0 && ws < 0 {
|
||||
return ntStatic, "", "", 0, 0, len(pattern) // we return the entire thing
|
||||
}
|
||||
|
||||
// Sanity check
|
||||
if ps >= 0 && ws >= 0 && ws < ps {
|
||||
panic("chi: wildcard '*' must be the last pattern in a route, otherwise use a '{param}'")
|
||||
}
|
||||
|
||||
var tail byte = '/' // Default endpoint tail to / byte
|
||||
|
||||
if ps >= 0 {
|
||||
// Param/Regexp pattern is next
|
||||
nt := ntParam
|
||||
|
||||
// Read to closing } taking into account opens and closes in curl count (cc)
|
||||
cc := 0
|
||||
pe := ps
|
||||
for i, c := range pattern[ps:] {
|
||||
if c == '{' {
|
||||
cc++
|
||||
} else if c == '}' {
|
||||
cc--
|
||||
if cc == 0 {
|
||||
pe = ps + i
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if pe == ps {
|
||||
panic("chi: route param closing delimiter '}' is missing")
|
||||
}
|
||||
|
||||
key := pattern[ps+1 : pe]
|
||||
pe++ // set end to next position
|
||||
|
||||
if pe < len(pattern) {
|
||||
tail = pattern[pe]
|
||||
}
|
||||
|
||||
var rexpat string
|
||||
if idx := strings.Index(key, ":"); idx >= 0 {
|
||||
nt = ntRegexp
|
||||
rexpat = key[idx+1:]
|
||||
key = key[:idx]
|
||||
}
|
||||
|
||||
if len(rexpat) > 0 {
|
||||
if rexpat[0] != '^' {
|
||||
rexpat = "^" + rexpat
|
||||
}
|
||||
if rexpat[len(rexpat)-1] != '$' {
|
||||
rexpat = rexpat + "$"
|
||||
}
|
||||
}
|
||||
|
||||
return nt, key, rexpat, tail, ps, pe
|
||||
}
|
||||
|
||||
// Wildcard pattern as finale
|
||||
if ws < len(pattern)-1 {
|
||||
panic("chi: wildcard '*' must be the last value in a route. trim trailing text or use a '{param}' instead")
|
||||
}
|
||||
return ntCatchAll, "*", "", 0, ws, len(pattern)
|
||||
}
|
||||
|
||||
func patParamKeys(pattern string) []string {
|
||||
pat := pattern
|
||||
paramKeys := []string{}
|
||||
for {
|
||||
ptyp, paramKey, _, _, _, e := patNextSegment(pat)
|
||||
if ptyp == ntStatic {
|
||||
return paramKeys
|
||||
}
|
||||
for i := 0; i < len(paramKeys); i++ {
|
||||
if paramKeys[i] == paramKey {
|
||||
panic(fmt.Sprintf("chi: routing pattern '%s' contains duplicate param key, '%s'", pattern, paramKey))
|
||||
}
|
||||
}
|
||||
paramKeys = append(paramKeys, paramKey)
|
||||
pat = pat[e:]
|
||||
}
|
||||
}
|
||||
|
||||
// longestPrefix finds the length of the shared prefix
|
||||
// of two strings
|
||||
func longestPrefix(k1, k2 string) int {
|
||||
max := len(k1)
|
||||
if l := len(k2); l < max {
|
||||
max = l
|
||||
}
|
||||
var i int
|
||||
for i = 0; i < max; i++ {
|
||||
if k1[i] != k2[i] {
|
||||
break
|
||||
}
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func methodTypString(method methodTyp) string {
|
||||
for s, t := range methodMap {
|
||||
if method == t {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type nodes []*node
|
||||
|
||||
// Sort the list of nodes by label
|
||||
func (ns nodes) Sort() { sort.Sort(ns); ns.tailSort() }
|
||||
func (ns nodes) Len() int { return len(ns) }
|
||||
func (ns nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
func (ns nodes) Less(i, j int) bool { return ns[i].label < ns[j].label }
|
||||
|
||||
// tailSort pushes nodes with '/' as the tail to the end of the list for param nodes.
|
||||
// The list order determines the traversal order.
|
||||
func (ns nodes) tailSort() {
|
||||
for i := len(ns) - 1; i >= 0; i-- {
|
||||
if ns[i].typ > ntStatic && ns[i].tail == '/' {
|
||||
ns.Swap(i, len(ns)-1)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ns nodes) findEdge(label byte) *node {
|
||||
num := len(ns)
|
||||
idx := 0
|
||||
i, j := 0, num-1
|
||||
for i <= j {
|
||||
idx = i + (j-i)/2
|
||||
if label > ns[idx].label {
|
||||
i = idx + 1
|
||||
} else if label < ns[idx].label {
|
||||
j = idx - 1
|
||||
} else {
|
||||
i = num // breaks cond
|
||||
}
|
||||
}
|
||||
if ns[idx].label != label {
|
||||
return nil
|
||||
}
|
||||
return ns[idx]
|
||||
}
|
||||
|
||||
// Route describes the details of a routing handler.
|
||||
type Route struct {
|
||||
Pattern string
|
||||
Handlers map[string]http.Handler
|
||||
SubRoutes Routes
|
||||
}
|
||||
|
||||
// WalkFunc is the type of the function called for each method and route visited by Walk.
|
||||
type WalkFunc func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error
|
||||
|
||||
// Walk walks any router tree that implements Routes interface.
|
||||
func Walk(r Routes, walkFn WalkFunc) error {
|
||||
return walk(r, walkFn, "")
|
||||
}
|
||||
|
||||
func walk(r Routes, walkFn WalkFunc, parentRoute string, parentMw ...func(http.Handler) http.Handler) error {
|
||||
for _, route := range r.Routes() {
|
||||
mws := make([]func(http.Handler) http.Handler, len(parentMw))
|
||||
copy(mws, parentMw)
|
||||
mws = append(mws, r.Middlewares()...)
|
||||
|
||||
if route.SubRoutes != nil {
|
||||
if err := walk(route.SubRoutes, walkFn, parentRoute+route.Pattern, mws...); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for method, handler := range route.Handlers {
|
||||
if method == "*" {
|
||||
// Ignore a "catchAll" method, since we pass down all the specific methods for each route.
|
||||
continue
|
||||
}
|
||||
|
||||
fullRoute := parentRoute + route.Pattern
|
||||
|
||||
if chain, ok := handler.(*ChainHandler); ok {
|
||||
if err := walkFn(method, fullRoute, chain.Endpoint, append(mws, chain.Middlewares...)...); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := walkFn(method, fullRoute, handler, mws...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
|
||||
go_import_path: gopkg.in/yaml.v2
|
|
@ -1,201 +0,0 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright {yyyy} {name of copyright owner}
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,13 +0,0 @@
|
|||
Copyright 2011-2016 Canonical Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -1,135 +0,0 @@
|
|||
# YAML support for the Go language
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The yaml package enables Go programs to comfortably encode and decode YAML
|
||||
values. It was developed within [Canonical](https://www.canonical.com) as
|
||||
part of the [juju](https://juju.ubuntu.com) project, and is based on a
|
||||
pure Go port of the well-known [libyaml](http://pyyaml.org/wiki/LibYAML)
|
||||
C library to parse and generate YAML data quickly and reliably.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
The yaml package supports most of YAML 1.1 and 1.2, including support for
|
||||
anchors, tags, map merging, etc. Multi-document unmarshalling is not yet
|
||||
implemented, and base-60 floats from YAML 1.1 are purposefully not
|
||||
supported since they're a poor design and are gone in YAML 1.2.
|
||||
|
||||
Installation and usage
|
||||
----------------------
|
||||
|
||||
The import path for the package is *gopkg.in/yaml.v2*.
|
||||
|
||||
To install it, run:
|
||||
|
||||
go get gopkg.in/yaml.v2
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
If opened in a browser, the import path itself leads to the API documentation:
|
||||
|
||||
* [https://gopkg.in/yaml.v2](https://gopkg.in/yaml.v2)
|
||||
|
||||
API stability
|
||||
-------------
|
||||
|
||||
The package API for yaml v2 will remain stable as described in [gopkg.in](https://gopkg.in).
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details.
|
||||
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Some more examples can be found in the "examples" folder.
|
||||
|
||||
```Go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var data = `
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
`
|
||||
|
||||
// Note: struct fields must be public in order for unmarshal to
|
||||
// correctly populate the data.
|
||||
type T struct {
|
||||
A string
|
||||
B struct {
|
||||
RenamedC int `yaml:"c"`
|
||||
D []int `yaml:",flow"`
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
t := T{}
|
||||
|
||||
err := yaml.Unmarshal([]byte(data), &t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t:\n%v\n\n", t)
|
||||
|
||||
d, err := yaml.Marshal(&t)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- t dump:\n%s\n\n", string(d))
|
||||
|
||||
m := make(map[interface{}]interface{})
|
||||
|
||||
err = yaml.Unmarshal([]byte(data), &m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m:\n%v\n\n", m)
|
||||
|
||||
d, err = yaml.Marshal(&m)
|
||||
if err != nil {
|
||||
log.Fatalf("error: %v", err)
|
||||
}
|
||||
fmt.Printf("--- m dump:\n%s\n\n", string(d))
|
||||
}
|
||||
```
|
||||
|
||||
This example will generate the following output:
|
||||
|
||||
```
|
||||
--- t:
|
||||
{Easy! {2 [3 4]}}
|
||||
|
||||
--- t dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d: [3, 4]
|
||||
|
||||
|
||||
--- m:
|
||||
map[a:Easy! b:map[c:2 d:[3 4]]]
|
||||
|
||||
--- m dump:
|
||||
a: Easy!
|
||||
b:
|
||||
c: 2
|
||||
d:
|
||||
- 3
|
||||
- 4
|
||||
```
|
||||
|
|
@ -1,739 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
|
||||
//fmt.Println("yaml_insert_token", "pos:", pos, "typ:", token.typ, "head:", parser.tokens_head, "len:", len(parser.tokens))
|
||||
|
||||
// Check if we can move the queue at the beginning of the buffer.
|
||||
if parser.tokens_head > 0 && len(parser.tokens) == cap(parser.tokens) {
|
||||
if parser.tokens_head != len(parser.tokens) {
|
||||
copy(parser.tokens, parser.tokens[parser.tokens_head:])
|
||||
}
|
||||
parser.tokens = parser.tokens[:len(parser.tokens)-parser.tokens_head]
|
||||
parser.tokens_head = 0
|
||||
}
|
||||
parser.tokens = append(parser.tokens, *token)
|
||||
if pos < 0 {
|
||||
return
|
||||
}
|
||||
copy(parser.tokens[parser.tokens_head+pos+1:], parser.tokens[parser.tokens_head+pos:])
|
||||
parser.tokens[parser.tokens_head+pos] = *token
|
||||
}
|
||||
|
||||
// Create a new parser object.
|
||||
func yaml_parser_initialize(parser *yaml_parser_t) bool {
|
||||
*parser = yaml_parser_t{
|
||||
raw_buffer: make([]byte, 0, input_raw_buffer_size),
|
||||
buffer: make([]byte, 0, input_buffer_size),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Destroy a parser object.
|
||||
func yaml_parser_delete(parser *yaml_parser_t) {
|
||||
*parser = yaml_parser_t{}
|
||||
}
|
||||
|
||||
// String read handler.
|
||||
func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
if parser.input_pos == len(parser.input) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
n = copy(buffer, parser.input[parser.input_pos:])
|
||||
parser.input_pos += n
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Reader read handler.
|
||||
func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
|
||||
return parser.input_reader.Read(buffer)
|
||||
}
|
||||
|
||||
// Set a string input.
|
||||
func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_string_read_handler
|
||||
parser.input = input
|
||||
parser.input_pos = 0
|
||||
}
|
||||
|
||||
// Set a file input.
|
||||
func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
|
||||
if parser.read_handler != nil {
|
||||
panic("must set the input source only once")
|
||||
}
|
||||
parser.read_handler = yaml_reader_read_handler
|
||||
parser.input_reader = r
|
||||
}
|
||||
|
||||
// Set the source encoding.
|
||||
func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
|
||||
if parser.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the encoding only once")
|
||||
}
|
||||
parser.encoding = encoding
|
||||
}
|
||||
|
||||
// Create a new emitter object.
|
||||
func yaml_emitter_initialize(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{
|
||||
buffer: make([]byte, output_buffer_size),
|
||||
raw_buffer: make([]byte, 0, output_raw_buffer_size),
|
||||
states: make([]yaml_emitter_state_t, 0, initial_stack_size),
|
||||
events: make([]yaml_event_t, 0, initial_queue_size),
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an emitter object.
|
||||
func yaml_emitter_delete(emitter *yaml_emitter_t) {
|
||||
*emitter = yaml_emitter_t{}
|
||||
}
|
||||
|
||||
// String write handler.
|
||||
func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
*emitter.output_buffer = append(*emitter.output_buffer, buffer...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// yaml_writer_write_handler uses emitter.output_writer to write the
|
||||
// emitted text.
|
||||
func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
|
||||
_, err := emitter.output_writer.Write(buffer)
|
||||
return err
|
||||
}
|
||||
|
||||
// Set a string output.
|
||||
func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]byte) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_string_write_handler
|
||||
emitter.output_buffer = output_buffer
|
||||
}
|
||||
|
||||
// Set a file output.
|
||||
func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
|
||||
if emitter.write_handler != nil {
|
||||
panic("must set the output target only once")
|
||||
}
|
||||
emitter.write_handler = yaml_writer_write_handler
|
||||
emitter.output_writer = w
|
||||
}
|
||||
|
||||
// Set the output encoding.
|
||||
func yaml_emitter_set_encoding(emitter *yaml_emitter_t, encoding yaml_encoding_t) {
|
||||
if emitter.encoding != yaml_ANY_ENCODING {
|
||||
panic("must set the output encoding only once")
|
||||
}
|
||||
emitter.encoding = encoding
|
||||
}
|
||||
|
||||
// Set the canonical output style.
|
||||
func yaml_emitter_set_canonical(emitter *yaml_emitter_t, canonical bool) {
|
||||
emitter.canonical = canonical
|
||||
}
|
||||
|
||||
//// Set the indentation increment.
|
||||
func yaml_emitter_set_indent(emitter *yaml_emitter_t, indent int) {
|
||||
if indent < 2 || indent > 9 {
|
||||
indent = 2
|
||||
}
|
||||
emitter.best_indent = indent
|
||||
}
|
||||
|
||||
// Set the preferred line width.
|
||||
func yaml_emitter_set_width(emitter *yaml_emitter_t, width int) {
|
||||
if width < 0 {
|
||||
width = -1
|
||||
}
|
||||
emitter.best_width = width
|
||||
}
|
||||
|
||||
// Set if unescaped non-ASCII characters are allowed.
|
||||
func yaml_emitter_set_unicode(emitter *yaml_emitter_t, unicode bool) {
|
||||
emitter.unicode = unicode
|
||||
}
|
||||
|
||||
// Set the preferred line break character.
|
||||
func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
|
||||
emitter.line_break = line_break
|
||||
}
|
||||
|
||||
///*
|
||||
// * Destroy a token object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_token_delete(yaml_token_t *token)
|
||||
//{
|
||||
// assert(token); // Non-NULL token object expected.
|
||||
//
|
||||
// switch (token.type)
|
||||
// {
|
||||
// case YAML_TAG_DIRECTIVE_TOKEN:
|
||||
// yaml_free(token.data.tag_directive.handle);
|
||||
// yaml_free(token.data.tag_directive.prefix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ALIAS_TOKEN:
|
||||
// yaml_free(token.data.alias.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_ANCHOR_TOKEN:
|
||||
// yaml_free(token.data.anchor.value);
|
||||
// break;
|
||||
//
|
||||
// case YAML_TAG_TOKEN:
|
||||
// yaml_free(token.data.tag.handle);
|
||||
// yaml_free(token.data.tag.suffix);
|
||||
// break;
|
||||
//
|
||||
// case YAML_SCALAR_TOKEN:
|
||||
// yaml_free(token.data.scalar.value);
|
||||
// break;
|
||||
//
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
//
|
||||
// memset(token, 0, sizeof(yaml_token_t));
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Check if a string is a valid UTF-8 sequence.
|
||||
// *
|
||||
// * Check 'reader.c' for more details on UTF-8 encoding.
|
||||
// */
|
||||
//
|
||||
//static int
|
||||
//yaml_check_utf8(yaml_char_t *start, size_t length)
|
||||
//{
|
||||
// yaml_char_t *end = start+length;
|
||||
// yaml_char_t *pointer = start;
|
||||
//
|
||||
// while (pointer < end) {
|
||||
// unsigned char octet;
|
||||
// unsigned int width;
|
||||
// unsigned int value;
|
||||
// size_t k;
|
||||
//
|
||||
// octet = pointer[0];
|
||||
// width = (octet & 0x80) == 0x00 ? 1 :
|
||||
// (octet & 0xE0) == 0xC0 ? 2 :
|
||||
// (octet & 0xF0) == 0xE0 ? 3 :
|
||||
// (octet & 0xF8) == 0xF0 ? 4 : 0;
|
||||
// value = (octet & 0x80) == 0x00 ? octet & 0x7F :
|
||||
// (octet & 0xE0) == 0xC0 ? octet & 0x1F :
|
||||
// (octet & 0xF0) == 0xE0 ? octet & 0x0F :
|
||||
// (octet & 0xF8) == 0xF0 ? octet & 0x07 : 0;
|
||||
// if (!width) return 0;
|
||||
// if (pointer+width > end) return 0;
|
||||
// for (k = 1; k < width; k ++) {
|
||||
// octet = pointer[k];
|
||||
// if ((octet & 0xC0) != 0x80) return 0;
|
||||
// value = (value << 6) + (octet & 0x3F);
|
||||
// }
|
||||
// if (!((width == 1) ||
|
||||
// (width == 2 && value >= 0x80) ||
|
||||
// (width == 3 && value >= 0x800) ||
|
||||
// (width == 4 && value >= 0x10000))) return 0;
|
||||
//
|
||||
// pointer += width;
|
||||
// }
|
||||
//
|
||||
// return 1;
|
||||
//}
|
||||
//
|
||||
|
||||
// Create STREAM-START.
|
||||
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_START_EVENT,
|
||||
encoding: encoding,
|
||||
}
|
||||
}
|
||||
|
||||
// Create STREAM-END.
|
||||
func yaml_stream_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_STREAM_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-START.
|
||||
func yaml_document_start_event_initialize(
|
||||
event *yaml_event_t,
|
||||
version_directive *yaml_version_directive_t,
|
||||
tag_directives []yaml_tag_directive_t,
|
||||
implicit bool,
|
||||
) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_START_EVENT,
|
||||
version_directive: version_directive,
|
||||
tag_directives: tag_directives,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
// Create DOCUMENT-END.
|
||||
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_DOCUMENT_END_EVENT,
|
||||
implicit: implicit,
|
||||
}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create ALIAS.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_alias_event_initialize(event *yaml_event_t, anchor *yaml_char_t)
|
||||
//{
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// anchor_copy *yaml_char_t = NULL
|
||||
//
|
||||
// assert(event) // Non-NULL event object is expected.
|
||||
// assert(anchor) // Non-NULL anchor is expected.
|
||||
//
|
||||
// if (!yaml_check_utf8(anchor, strlen((char *)anchor))) return 0
|
||||
//
|
||||
// anchor_copy = yaml_strdup(anchor)
|
||||
// if (!anchor_copy)
|
||||
// return 0
|
||||
//
|
||||
// ALIAS_EVENT_INIT(*event, anchor_copy, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
|
||||
// Create SCALAR.
|
||||
func yaml_scalar_event_initialize(event *yaml_event_t, anchor, tag, value []byte, plain_implicit, quoted_implicit bool, style yaml_scalar_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SCALAR_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
value: value,
|
||||
implicit: plain_implicit,
|
||||
quoted_implicit: quoted_implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-START.
|
||||
func yaml_sequence_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_sequence_style_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create SEQUENCE-END.
|
||||
func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_SEQUENCE_END_EVENT,
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Create MAPPING-START.
|
||||
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_START_EVENT,
|
||||
anchor: anchor,
|
||||
tag: tag,
|
||||
implicit: implicit,
|
||||
style: yaml_style_t(style),
|
||||
}
|
||||
}
|
||||
|
||||
// Create MAPPING-END.
|
||||
func yaml_mapping_end_event_initialize(event *yaml_event_t) {
|
||||
*event = yaml_event_t{
|
||||
typ: yaml_MAPPING_END_EVENT,
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy an event object.
|
||||
func yaml_event_delete(event *yaml_event_t) {
|
||||
*event = yaml_event_t{}
|
||||
}
|
||||
|
||||
///*
|
||||
// * Create a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_initialize(document *yaml_document_t,
|
||||
// version_directive *yaml_version_directive_t,
|
||||
// tag_directives_start *yaml_tag_directive_t,
|
||||
// tag_directives_end *yaml_tag_directive_t,
|
||||
// start_implicit int, end_implicit int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// struct {
|
||||
// start *yaml_node_t
|
||||
// end *yaml_node_t
|
||||
// top *yaml_node_t
|
||||
// } nodes = { NULL, NULL, NULL }
|
||||
// version_directive_copy *yaml_version_directive_t = NULL
|
||||
// struct {
|
||||
// start *yaml_tag_directive_t
|
||||
// end *yaml_tag_directive_t
|
||||
// top *yaml_tag_directive_t
|
||||
// } tag_directives_copy = { NULL, NULL, NULL }
|
||||
// value yaml_tag_directive_t = { NULL, NULL }
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert((tag_directives_start && tag_directives_end) ||
|
||||
// (tag_directives_start == tag_directives_end))
|
||||
// // Valid tag directives are expected.
|
||||
//
|
||||
// if (!STACK_INIT(&context, nodes, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// if (version_directive) {
|
||||
// version_directive_copy = yaml_malloc(sizeof(yaml_version_directive_t))
|
||||
// if (!version_directive_copy) goto error
|
||||
// version_directive_copy.major = version_directive.major
|
||||
// version_directive_copy.minor = version_directive.minor
|
||||
// }
|
||||
//
|
||||
// if (tag_directives_start != tag_directives_end) {
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
// if (!STACK_INIT(&context, tag_directives_copy, INITIAL_STACK_SIZE))
|
||||
// goto error
|
||||
// for (tag_directive = tag_directives_start
|
||||
// tag_directive != tag_directives_end; tag_directive ++) {
|
||||
// assert(tag_directive.handle)
|
||||
// assert(tag_directive.prefix)
|
||||
// if (!yaml_check_utf8(tag_directive.handle,
|
||||
// strlen((char *)tag_directive.handle)))
|
||||
// goto error
|
||||
// if (!yaml_check_utf8(tag_directive.prefix,
|
||||
// strlen((char *)tag_directive.prefix)))
|
||||
// goto error
|
||||
// value.handle = yaml_strdup(tag_directive.handle)
|
||||
// value.prefix = yaml_strdup(tag_directive.prefix)
|
||||
// if (!value.handle || !value.prefix) goto error
|
||||
// if (!PUSH(&context, tag_directives_copy, value))
|
||||
// goto error
|
||||
// value.handle = NULL
|
||||
// value.prefix = NULL
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// DOCUMENT_INIT(*document, nodes.start, nodes.end, version_directive_copy,
|
||||
// tag_directives_copy.start, tag_directives_copy.top,
|
||||
// start_implicit, end_implicit, mark, mark)
|
||||
//
|
||||
// return 1
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, nodes)
|
||||
// yaml_free(version_directive_copy)
|
||||
// while (!STACK_EMPTY(&context, tag_directives_copy)) {
|
||||
// value yaml_tag_directive_t = POP(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
// }
|
||||
// STACK_DEL(&context, tag_directives_copy)
|
||||
// yaml_free(value.handle)
|
||||
// yaml_free(value.prefix)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Destroy a document object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(void)
|
||||
//yaml_document_delete(document *yaml_document_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// tag_directive *yaml_tag_directive_t
|
||||
//
|
||||
// context.error = YAML_NO_ERROR // Eliminate a compliler warning.
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// while (!STACK_EMPTY(&context, document.nodes)) {
|
||||
// node yaml_node_t = POP(&context, document.nodes)
|
||||
// yaml_free(node.tag)
|
||||
// switch (node.type) {
|
||||
// case YAML_SCALAR_NODE:
|
||||
// yaml_free(node.data.scalar.value)
|
||||
// break
|
||||
// case YAML_SEQUENCE_NODE:
|
||||
// STACK_DEL(&context, node.data.sequence.items)
|
||||
// break
|
||||
// case YAML_MAPPING_NODE:
|
||||
// STACK_DEL(&context, node.data.mapping.pairs)
|
||||
// break
|
||||
// default:
|
||||
// assert(0) // Should not happen.
|
||||
// }
|
||||
// }
|
||||
// STACK_DEL(&context, document.nodes)
|
||||
//
|
||||
// yaml_free(document.version_directive)
|
||||
// for (tag_directive = document.tag_directives.start
|
||||
// tag_directive != document.tag_directives.end
|
||||
// tag_directive++) {
|
||||
// yaml_free(tag_directive.handle)
|
||||
// yaml_free(tag_directive.prefix)
|
||||
// }
|
||||
// yaml_free(document.tag_directives.start)
|
||||
//
|
||||
// memset(document, 0, sizeof(yaml_document_t))
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get a document node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_node(document *yaml_document_t, index int)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (index > 0 && document.nodes.start + index <= document.nodes.top) {
|
||||
// return document.nodes.start + index - 1
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Get the root object.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(yaml_node_t *)
|
||||
//yaml_document_get_root_node(document *yaml_document_t)
|
||||
//{
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (document.nodes.top != document.nodes.start) {
|
||||
// return document.nodes.start
|
||||
// }
|
||||
// return NULL
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a scalar node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_scalar(document *yaml_document_t,
|
||||
// tag *yaml_char_t, value *yaml_char_t, length int,
|
||||
// style yaml_scalar_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// value_copy *yaml_char_t = NULL
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
// assert(value) // Non-NULL value is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SCALAR_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (length < 0) {
|
||||
// length = strlen((char *)value)
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(value, length)) goto error
|
||||
// value_copy = yaml_malloc(length+1)
|
||||
// if (!value_copy) goto error
|
||||
// memcpy(value_copy, value, length)
|
||||
// value_copy[length] = '\0'
|
||||
//
|
||||
// SCALAR_NODE_INIT(node, tag_copy, value_copy, length, style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// yaml_free(tag_copy)
|
||||
// yaml_free(value_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a sequence node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_sequence(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_sequence_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_item_t
|
||||
// end *yaml_node_item_t
|
||||
// top *yaml_node_item_t
|
||||
// } items = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_SEQUENCE_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, items, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// SEQUENCE_NODE_INIT(node, tag_copy, items.start, items.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, items)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Add a mapping node to a document.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_add_mapping(document *yaml_document_t,
|
||||
// tag *yaml_char_t, style yaml_mapping_style_t)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
// mark yaml_mark_t = { 0, 0, 0 }
|
||||
// tag_copy *yaml_char_t = NULL
|
||||
// struct {
|
||||
// start *yaml_node_pair_t
|
||||
// end *yaml_node_pair_t
|
||||
// top *yaml_node_pair_t
|
||||
// } pairs = { NULL, NULL, NULL }
|
||||
// node yaml_node_t
|
||||
//
|
||||
// assert(document) // Non-NULL document object is expected.
|
||||
//
|
||||
// if (!tag) {
|
||||
// tag = (yaml_char_t *)YAML_DEFAULT_MAPPING_TAG
|
||||
// }
|
||||
//
|
||||
// if (!yaml_check_utf8(tag, strlen((char *)tag))) goto error
|
||||
// tag_copy = yaml_strdup(tag)
|
||||
// if (!tag_copy) goto error
|
||||
//
|
||||
// if (!STACK_INIT(&context, pairs, INITIAL_STACK_SIZE)) goto error
|
||||
//
|
||||
// MAPPING_NODE_INIT(node, tag_copy, pairs.start, pairs.end,
|
||||
// style, mark, mark)
|
||||
// if (!PUSH(&context, document.nodes, node)) goto error
|
||||
//
|
||||
// return document.nodes.top - document.nodes.start
|
||||
//
|
||||
//error:
|
||||
// STACK_DEL(&context, pairs)
|
||||
// yaml_free(tag_copy)
|
||||
//
|
||||
// return 0
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append an item to a sequence node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_sequence_item(document *yaml_document_t,
|
||||
// sequence int, item int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(sequence > 0
|
||||
// && document.nodes.start + sequence <= document.nodes.top)
|
||||
// // Valid sequence id is required.
|
||||
// assert(document.nodes.start[sequence-1].type == YAML_SEQUENCE_NODE)
|
||||
// // A sequence node is required.
|
||||
// assert(item > 0 && document.nodes.start + item <= document.nodes.top)
|
||||
// // Valid item id is required.
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[sequence-1].data.sequence.items, item))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
///*
|
||||
// * Append a pair of a key and a value to a mapping node.
|
||||
// */
|
||||
//
|
||||
//YAML_DECLARE(int)
|
||||
//yaml_document_append_mapping_pair(document *yaml_document_t,
|
||||
// mapping int, key int, value int)
|
||||
//{
|
||||
// struct {
|
||||
// error yaml_error_type_t
|
||||
// } context
|
||||
//
|
||||
// pair yaml_node_pair_t
|
||||
//
|
||||
// assert(document) // Non-NULL document is required.
|
||||
// assert(mapping > 0
|
||||
// && document.nodes.start + mapping <= document.nodes.top)
|
||||
// // Valid mapping id is required.
|
||||
// assert(document.nodes.start[mapping-1].type == YAML_MAPPING_NODE)
|
||||
// // A mapping node is required.
|
||||
// assert(key > 0 && document.nodes.start + key <= document.nodes.top)
|
||||
// // Valid key id is required.
|
||||
// assert(value > 0 && document.nodes.start + value <= document.nodes.top)
|
||||
// // Valid value id is required.
|
||||
//
|
||||
// pair.key = key
|
||||
// pair.value = value
|
||||
//
|
||||
// if (!PUSH(&context,
|
||||
// document.nodes.start[mapping-1].data.mapping.pairs, pair))
|
||||
// return 0
|
||||
//
|
||||
// return 1
|
||||
//}
|
||||
//
|
||||
//
|
|
@ -1,764 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
documentNode = 1 << iota
|
||||
mappingNode
|
||||
sequenceNode
|
||||
scalarNode
|
||||
aliasNode
|
||||
)
|
||||
|
||||
type node struct {
|
||||
kind int
|
||||
line, column int
|
||||
tag string
|
||||
// For an alias node, alias holds the resolved alias.
|
||||
alias *node
|
||||
value string
|
||||
implicit bool
|
||||
children []*node
|
||||
anchors map[string]*node
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Parser, produces a node tree out of a libyaml event stream.
|
||||
|
||||
type parser struct {
|
||||
parser yaml_parser_t
|
||||
event yaml_event_t
|
||||
doc *node
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newParser(b []byte) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
b = []byte{'\n'}
|
||||
}
|
||||
yaml_parser_set_input_string(&p.parser, b)
|
||||
return &p
|
||||
}
|
||||
|
||||
func newParserFromReader(r io.Reader) *parser {
|
||||
p := parser{}
|
||||
if !yaml_parser_initialize(&p.parser) {
|
||||
panic("failed to initialize YAML emitter")
|
||||
}
|
||||
yaml_parser_set_input_reader(&p.parser, r)
|
||||
return &p
|
||||
}
|
||||
|
||||
func (p *parser) init() {
|
||||
if p.doneInit {
|
||||
return
|
||||
}
|
||||
p.expect(yaml_STREAM_START_EVENT)
|
||||
p.doneInit = true
|
||||
}
|
||||
|
||||
func (p *parser) destroy() {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
yaml_event_delete(&p.event)
|
||||
}
|
||||
yaml_parser_delete(&p.parser)
|
||||
}
|
||||
|
||||
// expect consumes an event from the event stream and
|
||||
// checks that it's of the expected type.
|
||||
func (p *parser) expect(e yaml_event_type_t) {
|
||||
if p.event.typ == yaml_NO_EVENT {
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
}
|
||||
if p.event.typ == yaml_STREAM_END_EVENT {
|
||||
failf("attempted to go past the end of stream; corrupted value?")
|
||||
}
|
||||
if p.event.typ != e {
|
||||
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
|
||||
p.fail()
|
||||
}
|
||||
yaml_event_delete(&p.event)
|
||||
p.event.typ = yaml_NO_EVENT
|
||||
}
|
||||
|
||||
// peek peeks at the next event in the event stream,
|
||||
// puts the results into p.event and returns the event type.
|
||||
func (p *parser) peek() yaml_event_type_t {
|
||||
if p.event.typ != yaml_NO_EVENT {
|
||||
return p.event.typ
|
||||
}
|
||||
if !yaml_parser_parse(&p.parser, &p.event) {
|
||||
p.fail()
|
||||
}
|
||||
return p.event.typ
|
||||
}
|
||||
|
||||
func (p *parser) fail() {
|
||||
var where string
|
||||
var line int
|
||||
if p.parser.problem_mark.line != 0 {
|
||||
line = p.parser.problem_mark.line
|
||||
} else if p.parser.context_mark.line != 0 {
|
||||
line = p.parser.context_mark.line
|
||||
}
|
||||
if line != 0 {
|
||||
where = "line " + strconv.Itoa(line) + ": "
|
||||
}
|
||||
var msg string
|
||||
if len(p.parser.problem) > 0 {
|
||||
msg = p.parser.problem
|
||||
} else {
|
||||
msg = "unknown problem parsing YAML content"
|
||||
}
|
||||
failf("%s%s", where, msg)
|
||||
}
|
||||
|
||||
func (p *parser) anchor(n *node, anchor []byte) {
|
||||
if anchor != nil {
|
||||
p.doc.anchors[string(anchor)] = n
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) parse() *node {
|
||||
p.init()
|
||||
switch p.peek() {
|
||||
case yaml_SCALAR_EVENT:
|
||||
return p.scalar()
|
||||
case yaml_ALIAS_EVENT:
|
||||
return p.alias()
|
||||
case yaml_MAPPING_START_EVENT:
|
||||
return p.mapping()
|
||||
case yaml_SEQUENCE_START_EVENT:
|
||||
return p.sequence()
|
||||
case yaml_DOCUMENT_START_EVENT:
|
||||
return p.document()
|
||||
case yaml_STREAM_END_EVENT:
|
||||
// Happens when attempting to decode an empty buffer.
|
||||
return nil
|
||||
default:
|
||||
panic("attempted to parse unknown event: " + p.event.typ.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) node(kind int) *node {
|
||||
return &node{
|
||||
kind: kind,
|
||||
line: p.event.start_mark.line,
|
||||
column: p.event.start_mark.column,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *parser) document() *node {
|
||||
n := p.node(documentNode)
|
||||
n.anchors = make(map[string]*node)
|
||||
p.doc = n
|
||||
p.expect(yaml_DOCUMENT_START_EVENT)
|
||||
n.children = append(n.children, p.parse())
|
||||
p.expect(yaml_DOCUMENT_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) alias() *node {
|
||||
n := p.node(aliasNode)
|
||||
n.value = string(p.event.anchor)
|
||||
n.alias = p.doc.anchors[n.value]
|
||||
if n.alias == nil {
|
||||
failf("unknown anchor '%s' referenced", n.value)
|
||||
}
|
||||
p.expect(yaml_ALIAS_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) scalar() *node {
|
||||
n := p.node(scalarNode)
|
||||
n.value = string(p.event.value)
|
||||
n.tag = string(p.event.tag)
|
||||
n.implicit = p.event.implicit
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SCALAR_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) sequence() *node {
|
||||
n := p.node(sequenceNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_SEQUENCE_START_EVENT)
|
||||
for p.peek() != yaml_SEQUENCE_END_EVENT {
|
||||
n.children = append(n.children, p.parse())
|
||||
}
|
||||
p.expect(yaml_SEQUENCE_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
func (p *parser) mapping() *node {
|
||||
n := p.node(mappingNode)
|
||||
p.anchor(n, p.event.anchor)
|
||||
p.expect(yaml_MAPPING_START_EVENT)
|
||||
for p.peek() != yaml_MAPPING_END_EVENT {
|
||||
n.children = append(n.children, p.parse(), p.parse())
|
||||
}
|
||||
p.expect(yaml_MAPPING_END_EVENT)
|
||||
return n
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Decoder, unmarshals a node into a provided value.
|
||||
|
||||
type decoder struct {
|
||||
doc *node
|
||||
aliases map[*node]bool
|
||||
mapType reflect.Type
|
||||
terrors []string
|
||||
strict bool
|
||||
}
|
||||
|
||||
var (
|
||||
mapItemType = reflect.TypeOf(MapItem{})
|
||||
durationType = reflect.TypeOf(time.Duration(0))
|
||||
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
|
||||
ifaceType = defaultMapType.Elem()
|
||||
timeType = reflect.TypeOf(time.Time{})
|
||||
ptrTimeType = reflect.TypeOf(&time.Time{})
|
||||
)
|
||||
|
||||
func newDecoder(strict bool) *decoder {
|
||||
d := &decoder{mapType: defaultMapType, strict: strict}
|
||||
d.aliases = make(map[*node]bool)
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *decoder) terror(n *node, tag string, out reflect.Value) {
|
||||
if n.tag != "" {
|
||||
tag = n.tag
|
||||
}
|
||||
value := n.value
|
||||
if tag != yaml_SEQ_TAG && tag != yaml_MAP_TAG {
|
||||
if len(value) > 10 {
|
||||
value = " `" + value[:7] + "...`"
|
||||
} else {
|
||||
value = " `" + value + "`"
|
||||
}
|
||||
}
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: cannot unmarshal %s%s into %s", n.line+1, shortTag(tag), value, out.Type()))
|
||||
}
|
||||
|
||||
func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
|
||||
terrlen := len(d.terrors)
|
||||
err := u.UnmarshalYAML(func(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
d.unmarshal(n, reflect.ValueOf(v))
|
||||
if len(d.terrors) > terrlen {
|
||||
issues := d.terrors[terrlen:]
|
||||
d.terrors = d.terrors[:terrlen]
|
||||
return &TypeError{issues}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if e, ok := err.(*TypeError); ok {
|
||||
d.terrors = append(d.terrors, e.Errors...)
|
||||
return false
|
||||
}
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// d.prepare initializes and dereferences pointers and calls UnmarshalYAML
|
||||
// if a value is found to implement it.
|
||||
// It returns the initialized and dereferenced out value, whether
|
||||
// unmarshalling was already done by UnmarshalYAML, and if so whether
|
||||
// its types unmarshalled appropriately.
|
||||
//
|
||||
// If n holds a null value, prepare returns before doing anything.
|
||||
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
|
||||
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
|
||||
return out, false, false
|
||||
}
|
||||
again := true
|
||||
for again {
|
||||
again = false
|
||||
if out.Kind() == reflect.Ptr {
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.New(out.Type().Elem()))
|
||||
}
|
||||
out = out.Elem()
|
||||
again = true
|
||||
}
|
||||
if out.CanAddr() {
|
||||
if u, ok := out.Addr().Interface().(Unmarshaler); ok {
|
||||
good = d.callUnmarshaler(n, u)
|
||||
return out, true, good
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, false, false
|
||||
}
|
||||
|
||||
func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) {
|
||||
switch n.kind {
|
||||
case documentNode:
|
||||
return d.document(n, out)
|
||||
case aliasNode:
|
||||
return d.alias(n, out)
|
||||
}
|
||||
out, unmarshaled, good := d.prepare(n, out)
|
||||
if unmarshaled {
|
||||
return good
|
||||
}
|
||||
switch n.kind {
|
||||
case scalarNode:
|
||||
good = d.scalar(n, out)
|
||||
case mappingNode:
|
||||
good = d.mapping(n, out)
|
||||
case sequenceNode:
|
||||
good = d.sequence(n, out)
|
||||
default:
|
||||
panic("internal error: unknown node kind: " + strconv.Itoa(n.kind))
|
||||
}
|
||||
return good
|
||||
}
|
||||
|
||||
func (d *decoder) document(n *node, out reflect.Value) (good bool) {
|
||||
if len(n.children) == 1 {
|
||||
d.doc = n
|
||||
d.unmarshal(n.children[0], out)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
|
||||
if d.aliases[n] {
|
||||
// TODO this could actually be allowed in some circumstances.
|
||||
failf("anchor '%s' value contains itself", n.value)
|
||||
}
|
||||
d.aliases[n] = true
|
||||
good = d.unmarshal(n.alias, out)
|
||||
delete(d.aliases, n)
|
||||
return good
|
||||
}
|
||||
|
||||
var zeroValue reflect.Value
|
||||
|
||||
func resetMap(out reflect.Value) {
|
||||
for _, k := range out.MapKeys() {
|
||||
out.SetMapIndex(k, zeroValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *decoder) scalar(n *node, out reflect.Value) bool {
|
||||
var tag string
|
||||
var resolved interface{}
|
||||
if n.tag == "" && !n.implicit {
|
||||
tag = yaml_STR_TAG
|
||||
resolved = n.value
|
||||
} else {
|
||||
tag, resolved = resolve(n.tag, n.value)
|
||||
if tag == yaml_BINARY_TAG {
|
||||
data, err := base64.StdEncoding.DecodeString(resolved.(string))
|
||||
if err != nil {
|
||||
failf("!!binary value contains invalid base64 data")
|
||||
}
|
||||
resolved = string(data)
|
||||
}
|
||||
}
|
||||
if resolved == nil {
|
||||
if out.Kind() == reflect.Map && !out.CanAddr() {
|
||||
resetMap(out)
|
||||
} else {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
}
|
||||
return true
|
||||
}
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
// We've resolved to exactly the type we want, so use that.
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
// Perhaps we can use the value as a TextUnmarshaler to
|
||||
// set its value.
|
||||
if out.CanAddr() {
|
||||
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
|
||||
if ok {
|
||||
var text []byte
|
||||
if tag == yaml_BINARY_TAG {
|
||||
text = []byte(resolved.(string))
|
||||
} else {
|
||||
// We let any value be unmarshaled into TextUnmarshaler.
|
||||
// That might be more lax than we'd like, but the
|
||||
// TextUnmarshaler itself should bowl out any dubious values.
|
||||
text = []byte(n.value)
|
||||
}
|
||||
err := u.UnmarshalText(text)
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
switch out.Kind() {
|
||||
case reflect.String:
|
||||
if tag == yaml_BINARY_TAG {
|
||||
out.SetString(resolved.(string))
|
||||
return true
|
||||
}
|
||||
if resolved != nil {
|
||||
out.SetString(n.value)
|
||||
return true
|
||||
}
|
||||
case reflect.Interface:
|
||||
if resolved == nil {
|
||||
out.Set(reflect.Zero(out.Type()))
|
||||
} else if tag == yaml_TIMESTAMP_TAG {
|
||||
// It looks like a timestamp but for backward compatibility
|
||||
// reasons we set it as a string, so that code that unmarshals
|
||||
// timestamp-like values into interface{} will continue to
|
||||
// see a string and not a time.Time.
|
||||
out.Set(reflect.ValueOf(n.value))
|
||||
} else {
|
||||
out.Set(reflect.ValueOf(resolved))
|
||||
}
|
||||
return true
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if !out.OverflowInt(resolved) {
|
||||
out.SetInt(resolved)
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
|
||||
out.SetInt(int64(resolved))
|
||||
return true
|
||||
}
|
||||
case string:
|
||||
if out.Type() == durationType {
|
||||
d, err := time.ParseDuration(resolved)
|
||||
if err == nil {
|
||||
out.SetInt(int64(d))
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case int64:
|
||||
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case uint64:
|
||||
if !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
case float64:
|
||||
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
|
||||
out.SetUint(uint64(resolved))
|
||||
return true
|
||||
}
|
||||
}
|
||||
case reflect.Bool:
|
||||
switch resolved := resolved.(type) {
|
||||
case bool:
|
||||
out.SetBool(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Float32, reflect.Float64:
|
||||
switch resolved := resolved.(type) {
|
||||
case int:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case int64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case uint64:
|
||||
out.SetFloat(float64(resolved))
|
||||
return true
|
||||
case float64:
|
||||
out.SetFloat(resolved)
|
||||
return true
|
||||
}
|
||||
case reflect.Struct:
|
||||
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
|
||||
out.Set(resolvedv)
|
||||
return true
|
||||
}
|
||||
case reflect.Ptr:
|
||||
if out.Type().Elem() == reflect.TypeOf(resolved) {
|
||||
// TODO DOes this make sense? When is out a Ptr except when decoding a nil value?
|
||||
elem := reflect.New(out.Type().Elem())
|
||||
elem.Elem().Set(reflect.ValueOf(resolved))
|
||||
out.Set(elem)
|
||||
return true
|
||||
}
|
||||
}
|
||||
d.terror(n, tag, out)
|
||||
return false
|
||||
}
|
||||
|
||||
func settableValueOf(i interface{}) reflect.Value {
|
||||
v := reflect.ValueOf(i)
|
||||
sv := reflect.New(v.Type()).Elem()
|
||||
sv.Set(v)
|
||||
return sv
|
||||
}
|
||||
|
||||
func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
|
||||
l := len(n.children)
|
||||
|
||||
var iface reflect.Value
|
||||
switch out.Kind() {
|
||||
case reflect.Slice:
|
||||
out.Set(reflect.MakeSlice(out.Type(), l, l))
|
||||
case reflect.Interface:
|
||||
// No type hints. Will have to use a generic sequence.
|
||||
iface = out
|
||||
out = settableValueOf(make([]interface{}, l))
|
||||
default:
|
||||
d.terror(n, yaml_SEQ_TAG, out)
|
||||
return false
|
||||
}
|
||||
et := out.Type().Elem()
|
||||
|
||||
j := 0
|
||||
for i := 0; i < l; i++ {
|
||||
e := reflect.New(et).Elem()
|
||||
if ok := d.unmarshal(n.children[i], e); ok {
|
||||
out.Index(j).Set(e)
|
||||
j++
|
||||
}
|
||||
}
|
||||
out.Set(out.Slice(0, j))
|
||||
if iface.IsValid() {
|
||||
iface.Set(out)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
|
||||
switch out.Kind() {
|
||||
case reflect.Struct:
|
||||
return d.mappingStruct(n, out)
|
||||
case reflect.Slice:
|
||||
return d.mappingSlice(n, out)
|
||||
case reflect.Map:
|
||||
// okay
|
||||
case reflect.Interface:
|
||||
if d.mapType.Kind() == reflect.Map {
|
||||
iface := out
|
||||
out = reflect.MakeMap(d.mapType)
|
||||
iface.Set(out)
|
||||
} else {
|
||||
slicev := reflect.New(d.mapType).Elem()
|
||||
if !d.mappingSlice(n, slicev) {
|
||||
return false
|
||||
}
|
||||
out.Set(slicev)
|
||||
return true
|
||||
}
|
||||
default:
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
outt := out.Type()
|
||||
kt := outt.Key()
|
||||
et := outt.Elem()
|
||||
|
||||
mapType := d.mapType
|
||||
if outt.Key() == ifaceType && outt.Elem() == ifaceType {
|
||||
d.mapType = outt
|
||||
}
|
||||
|
||||
if out.IsNil() {
|
||||
out.Set(reflect.MakeMap(outt))
|
||||
}
|
||||
l := len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
k := reflect.New(kt).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
kkind := k.Kind()
|
||||
if kkind == reflect.Interface {
|
||||
kkind = k.Elem().Kind()
|
||||
}
|
||||
if kkind == reflect.Map || kkind == reflect.Slice {
|
||||
failf("invalid map key: %#v", k.Interface())
|
||||
}
|
||||
e := reflect.New(et).Elem()
|
||||
if d.unmarshal(n.children[i+1], e) {
|
||||
d.setMapIndex(n.children[i+1], out, k, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
|
||||
if d.strict && out.MapIndex(k) != zeroValue {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
|
||||
return
|
||||
}
|
||||
out.SetMapIndex(k, v)
|
||||
}
|
||||
|
||||
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
|
||||
outt := out.Type()
|
||||
if outt.Elem() != mapItemType {
|
||||
d.terror(n, yaml_MAP_TAG, out)
|
||||
return false
|
||||
}
|
||||
|
||||
mapType := d.mapType
|
||||
d.mapType = outt
|
||||
|
||||
var slice []MapItem
|
||||
var l = len(n.children)
|
||||
for i := 0; i < l; i += 2 {
|
||||
if isMerge(n.children[i]) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
item := MapItem{}
|
||||
k := reflect.ValueOf(&item.Key).Elem()
|
||||
if d.unmarshal(n.children[i], k) {
|
||||
v := reflect.ValueOf(&item.Value).Elem()
|
||||
if d.unmarshal(n.children[i+1], v) {
|
||||
slice = append(slice, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
out.Set(reflect.ValueOf(slice))
|
||||
d.mapType = mapType
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
|
||||
sinfo, err := getStructInfo(out.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
name := settableValueOf("")
|
||||
l := len(n.children)
|
||||
|
||||
var inlineMap reflect.Value
|
||||
var elemType reflect.Type
|
||||
if sinfo.InlineMap != -1 {
|
||||
inlineMap = out.Field(sinfo.InlineMap)
|
||||
inlineMap.Set(reflect.New(inlineMap.Type()).Elem())
|
||||
elemType = inlineMap.Type().Elem()
|
||||
}
|
||||
|
||||
var doneFields []bool
|
||||
if d.strict {
|
||||
doneFields = make([]bool, len(sinfo.FieldsList))
|
||||
}
|
||||
for i := 0; i < l; i += 2 {
|
||||
ni := n.children[i]
|
||||
if isMerge(ni) {
|
||||
d.merge(n.children[i+1], out)
|
||||
continue
|
||||
}
|
||||
if !d.unmarshal(ni, name) {
|
||||
continue
|
||||
}
|
||||
if info, ok := sinfo.FieldsMap[name.String()]; ok {
|
||||
if d.strict {
|
||||
if doneFields[info.Id] {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
|
||||
continue
|
||||
}
|
||||
doneFields[info.Id] = true
|
||||
}
|
||||
var field reflect.Value
|
||||
if info.Inline == nil {
|
||||
field = out.Field(info.Num)
|
||||
} else {
|
||||
field = out.FieldByIndex(info.Inline)
|
||||
}
|
||||
d.unmarshal(n.children[i+1], field)
|
||||
} else if sinfo.InlineMap != -1 {
|
||||
if inlineMap.IsNil() {
|
||||
inlineMap.Set(reflect.MakeMap(inlineMap.Type()))
|
||||
}
|
||||
value := reflect.New(elemType).Elem()
|
||||
d.unmarshal(n.children[i+1], value)
|
||||
d.setMapIndex(n.children[i+1], inlineMap, name, value)
|
||||
} else if d.strict {
|
||||
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func failWantMap() {
|
||||
failf("map merge requires map or sequence of maps as the value")
|
||||
}
|
||||
|
||||
func (d *decoder) merge(n *node, out reflect.Value) {
|
||||
switch n.kind {
|
||||
case mappingNode:
|
||||
d.unmarshal(n, out)
|
||||
case aliasNode:
|
||||
an, ok := d.doc.anchors[n.value]
|
||||
if ok && an.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(n, out)
|
||||
case sequenceNode:
|
||||
// Step backwards as earlier nodes take precedence.
|
||||
for i := len(n.children) - 1; i >= 0; i-- {
|
||||
ni := n.children[i]
|
||||
if ni.kind == aliasNode {
|
||||
an, ok := d.doc.anchors[ni.value]
|
||||
if ok && an.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
} else if ni.kind != mappingNode {
|
||||
failWantMap()
|
||||
}
|
||||
d.unmarshal(ni, out)
|
||||
}
|
||||
default:
|
||||
failWantMap()
|
||||
}
|
||||
}
|
||||
|
||||
func isMerge(n *node) bool {
|
||||
return n.kind == scalarNode && n.value == "<<" && (n.implicit == true || n.tag == yaml_MERGE_TAG)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,358 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type encoder struct {
|
||||
emitter yaml_emitter_t
|
||||
event yaml_event_t
|
||||
out []byte
|
||||
flow bool
|
||||
// doneInit holds whether the initial stream_start_event has been
|
||||
// emitted.
|
||||
doneInit bool
|
||||
}
|
||||
|
||||
func newEncoder() *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_string(&e.emitter, &e.out)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func newEncoderWithWriter(w io.Writer) *encoder {
|
||||
e := &encoder{}
|
||||
yaml_emitter_initialize(&e.emitter)
|
||||
yaml_emitter_set_output_writer(&e.emitter, w)
|
||||
yaml_emitter_set_unicode(&e.emitter, true)
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *encoder) init() {
|
||||
if e.doneInit {
|
||||
return
|
||||
}
|
||||
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
|
||||
e.emit()
|
||||
e.doneInit = true
|
||||
}
|
||||
|
||||
func (e *encoder) finish() {
|
||||
e.emitter.open_ended = false
|
||||
yaml_stream_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) destroy() {
|
||||
yaml_emitter_delete(&e.emitter)
|
||||
}
|
||||
|
||||
func (e *encoder) emit() {
|
||||
// This will internally delete the e.event value.
|
||||
e.must(yaml_emitter_emit(&e.emitter, &e.event))
|
||||
}
|
||||
|
||||
func (e *encoder) must(ok bool) {
|
||||
if !ok {
|
||||
msg := e.emitter.problem
|
||||
if msg == "" {
|
||||
msg = "unknown problem generating YAML content"
|
||||
}
|
||||
failf("%s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
|
||||
e.init()
|
||||
yaml_document_start_event_initialize(&e.event, nil, nil, true)
|
||||
e.emit()
|
||||
e.marshal(tag, in)
|
||||
yaml_document_end_event_initialize(&e.event, true)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) marshal(tag string, in reflect.Value) {
|
||||
if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
iface := in.Interface()
|
||||
switch m := iface.(type) {
|
||||
case time.Time, *time.Time:
|
||||
// Although time.Time implements TextMarshaler,
|
||||
// we don't want to treat it as a string for YAML
|
||||
// purposes because YAML has special support for
|
||||
// timestamps.
|
||||
case Marshaler:
|
||||
v, err := m.MarshalYAML()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
if v == nil {
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
in = reflect.ValueOf(v)
|
||||
case encoding.TextMarshaler:
|
||||
text, err := m.MarshalText()
|
||||
if err != nil {
|
||||
fail(err)
|
||||
}
|
||||
in = reflect.ValueOf(string(text))
|
||||
case nil:
|
||||
e.nilv()
|
||||
return
|
||||
}
|
||||
switch in.Kind() {
|
||||
case reflect.Interface:
|
||||
e.marshal(tag, in.Elem())
|
||||
case reflect.Map:
|
||||
e.mapv(tag, in)
|
||||
case reflect.Ptr:
|
||||
if in.Type() == ptrTimeType {
|
||||
e.timev(tag, in.Elem())
|
||||
} else {
|
||||
e.marshal(tag, in.Elem())
|
||||
}
|
||||
case reflect.Struct:
|
||||
if in.Type() == timeType {
|
||||
e.timev(tag, in)
|
||||
} else {
|
||||
e.structv(tag, in)
|
||||
}
|
||||
case reflect.Slice:
|
||||
if in.Type().Elem() == mapItemType {
|
||||
e.itemsv(tag, in)
|
||||
} else {
|
||||
e.slicev(tag, in)
|
||||
}
|
||||
case reflect.String:
|
||||
e.stringv(tag, in)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if in.Type() == durationType {
|
||||
e.stringv(tag, reflect.ValueOf(iface.(time.Duration).String()))
|
||||
} else {
|
||||
e.intv(tag, in)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
e.uintv(tag, in)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
e.floatv(tag, in)
|
||||
case reflect.Bool:
|
||||
e.boolv(tag, in)
|
||||
default:
|
||||
panic("cannot marshal type: " + in.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
func (e *encoder) mapv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
keys := keyList(in.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
e.marshal("", k)
|
||||
e.marshal("", in.MapIndex(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) itemsv(tag string, in reflect.Value) {
|
||||
e.mappingv(tag, func() {
|
||||
slice := in.Convert(reflect.TypeOf([]MapItem{})).Interface().([]MapItem)
|
||||
for _, item := range slice {
|
||||
e.marshal("", reflect.ValueOf(item.Key))
|
||||
e.marshal("", reflect.ValueOf(item.Value))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) structv(tag string, in reflect.Value) {
|
||||
sinfo, err := getStructInfo(in.Type())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
e.mappingv(tag, func() {
|
||||
for _, info := range sinfo.FieldsList {
|
||||
var value reflect.Value
|
||||
if info.Inline == nil {
|
||||
value = in.Field(info.Num)
|
||||
} else {
|
||||
value = in.FieldByIndex(info.Inline)
|
||||
}
|
||||
if info.OmitEmpty && isZero(value) {
|
||||
continue
|
||||
}
|
||||
e.marshal("", reflect.ValueOf(info.Key))
|
||||
e.flow = info.Flow
|
||||
e.marshal("", value)
|
||||
}
|
||||
if sinfo.InlineMap >= 0 {
|
||||
m := in.Field(sinfo.InlineMap)
|
||||
if m.Len() > 0 {
|
||||
e.flow = false
|
||||
keys := keyList(m.MapKeys())
|
||||
sort.Sort(keys)
|
||||
for _, k := range keys {
|
||||
if _, found := sinfo.FieldsMap[k.String()]; found {
|
||||
panic(fmt.Sprintf("Can't have key %q in inlined map; conflicts with struct field", k.String()))
|
||||
}
|
||||
e.marshal("", k)
|
||||
e.flow = false
|
||||
e.marshal("", m.MapIndex(k))
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (e *encoder) mappingv(tag string, f func()) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_MAPPING_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_MAPPING_STYLE
|
||||
}
|
||||
yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
|
||||
e.emit()
|
||||
f()
|
||||
yaml_mapping_end_event_initialize(&e.event)
|
||||
e.emit()
|
||||
}
|
||||
|
||||
func (e *encoder) slicev(tag string, in reflect.Value) {
|
||||
implicit := tag == ""
|
||||
style := yaml_BLOCK_SEQUENCE_STYLE
|
||||
if e.flow {
|
||||
e.flow = false
|
||||
style = yaml_FLOW_SEQUENCE_STYLE
|
||||
}
|
||||
e.must(yaml_sequence_start_event_initialize(&e.event, nil, []byte(tag), implicit, style))
|
||||
e.emit()
|
||||
n := in.Len()
|
||||
for i := 0; i < n; i++ {
|
||||
e.marshal("", in.Index(i))
|
||||
}
|
||||
e.must(yaml_sequence_end_event_initialize(&e.event))
|
||||
e.emit()
|
||||
}
|
||||
|
||||
// isBase60 returns whether s is in base 60 notation as defined in YAML 1.1.
|
||||
//
|
||||
// The base 60 float notation in YAML 1.1 is a terrible idea and is unsupported
|
||||
// in YAML 1.2 and by this package, but these should be marshalled quoted for
|
||||
// the time being for compatibility with other parsers.
|
||||
func isBase60Float(s string) (result bool) {
|
||||
// Fast path.
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
c := s[0]
|
||||
if !(c == '+' || c == '-' || c >= '0' && c <= '9') || strings.IndexByte(s, ':') < 0 {
|
||||
return false
|
||||
}
|
||||
// Do the full match.
|
||||
return base60float.MatchString(s)
|
||||
}
|
||||
|
||||
// From http://yaml.org/type/float.html, except the regular expression there
|
||||
// is bogus. In practice parsers do not enforce the "\.[0-9_]*" suffix.
|
||||
var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0-9_]*)?$`)
|
||||
|
||||
func (e *encoder) stringv(tag string, in reflect.Value) {
|
||||
var style yaml_scalar_style_t
|
||||
s := in.String()
|
||||
canUsePlain := true
|
||||
switch {
|
||||
case !utf8.ValidString(s):
|
||||
if tag == yaml_BINARY_TAG {
|
||||
failf("explicitly tagged !!binary data must be base64-encoded")
|
||||
}
|
||||
if tag != "" {
|
||||
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
|
||||
}
|
||||
// It can't be encoded directly as YAML so use a binary tag
|
||||
// and encode it as base64.
|
||||
tag = yaml_BINARY_TAG
|
||||
s = encodeBase64(s)
|
||||
case tag == "":
|
||||
// Check to see if it would resolve to a specific
|
||||
// tag when encoded unquoted. If it doesn't,
|
||||
// there's no need to quote it.
|
||||
rtag, _ := resolve("", s)
|
||||
canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
|
||||
}
|
||||
// Note: it's possible for user code to emit invalid YAML
|
||||
// if they explicitly specify a tag and a string containing
|
||||
// text that's incompatible with that tag.
|
||||
switch {
|
||||
case strings.Contains(s, "\n"):
|
||||
style = yaml_LITERAL_SCALAR_STYLE
|
||||
case canUsePlain:
|
||||
style = yaml_PLAIN_SCALAR_STYLE
|
||||
default:
|
||||
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
|
||||
}
|
||||
e.emitScalar(s, "", tag, style)
|
||||
}
|
||||
|
||||
func (e *encoder) boolv(tag string, in reflect.Value) {
|
||||
var s string
|
||||
if in.Bool() {
|
||||
s = "true"
|
||||
} else {
|
||||
s = "false"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) intv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatInt(in.Int(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) uintv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatUint(in.Uint(), 10)
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) timev(tag string, in reflect.Value) {
|
||||
t := in.Interface().(time.Time)
|
||||
if tag == "" {
|
||||
tag = yaml_TIMESTAMP_TAG
|
||||
}
|
||||
e.emitScalar(t.Format(time.RFC3339Nano), "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) floatv(tag string, in reflect.Value) {
|
||||
s := strconv.FormatFloat(in.Float(), 'g', -1, 64)
|
||||
switch s {
|
||||
case "+Inf":
|
||||
s = ".inf"
|
||||
case "-Inf":
|
||||
s = "-.inf"
|
||||
case "NaN":
|
||||
s = ".nan"
|
||||
}
|
||||
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) nilv() {
|
||||
e.emitScalar("null", "", "", yaml_PLAIN_SCALAR_STYLE)
|
||||
}
|
||||
|
||||
func (e *encoder) emitScalar(value, anchor, tag string, style yaml_scalar_style_t) {
|
||||
implicit := tag == ""
|
||||
e.must(yaml_scalar_event_initialize(&e.event, []byte(anchor), []byte(tag), []byte(value), implicit, implicit, style))
|
||||
e.emit()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,394 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Set the reader error and return 0.
|
||||
func yaml_parser_set_reader_error(parser *yaml_parser_t, problem string, offset int, value int) bool {
|
||||
parser.error = yaml_READER_ERROR
|
||||
parser.problem = problem
|
||||
parser.problem_offset = offset
|
||||
parser.problem_value = value
|
||||
return false
|
||||
}
|
||||
|
||||
// Byte order marks.
|
||||
const (
|
||||
bom_UTF8 = "\xef\xbb\xbf"
|
||||
bom_UTF16LE = "\xff\xfe"
|
||||
bom_UTF16BE = "\xfe\xff"
|
||||
)
|
||||
|
||||
// Determine the input stream encoding by checking the BOM symbol. If no BOM is
|
||||
// found, the UTF-8 encoding is assumed. Return 1 on success, 0 on failure.
|
||||
func yaml_parser_determine_encoding(parser *yaml_parser_t) bool {
|
||||
// Ensure that we had enough bytes in the raw buffer.
|
||||
for !parser.eof && len(parser.raw_buffer)-parser.raw_buffer_pos < 3 {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the encoding.
|
||||
buf := parser.raw_buffer
|
||||
pos := parser.raw_buffer_pos
|
||||
avail := len(buf) - pos
|
||||
if avail >= 2 && buf[pos] == bom_UTF16LE[0] && buf[pos+1] == bom_UTF16LE[1] {
|
||||
parser.encoding = yaml_UTF16LE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 2 && buf[pos] == bom_UTF16BE[0] && buf[pos+1] == bom_UTF16BE[1] {
|
||||
parser.encoding = yaml_UTF16BE_ENCODING
|
||||
parser.raw_buffer_pos += 2
|
||||
parser.offset += 2
|
||||
} else if avail >= 3 && buf[pos] == bom_UTF8[0] && buf[pos+1] == bom_UTF8[1] && buf[pos+2] == bom_UTF8[2] {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
parser.raw_buffer_pos += 3
|
||||
parser.offset += 3
|
||||
} else {
|
||||
parser.encoding = yaml_UTF8_ENCODING
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Update the raw buffer.
|
||||
func yaml_parser_update_raw_buffer(parser *yaml_parser_t) bool {
|
||||
size_read := 0
|
||||
|
||||
// Return if the raw buffer is full.
|
||||
if parser.raw_buffer_pos == 0 && len(parser.raw_buffer) == cap(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return on EOF.
|
||||
if parser.eof {
|
||||
return true
|
||||
}
|
||||
|
||||
// Move the remaining bytes in the raw buffer to the beginning.
|
||||
if parser.raw_buffer_pos > 0 && parser.raw_buffer_pos < len(parser.raw_buffer) {
|
||||
copy(parser.raw_buffer, parser.raw_buffer[parser.raw_buffer_pos:])
|
||||
}
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)-parser.raw_buffer_pos]
|
||||
parser.raw_buffer_pos = 0
|
||||
|
||||
// Call the read handler to fill the buffer.
|
||||
size_read, err := parser.read_handler(parser, parser.raw_buffer[len(parser.raw_buffer):cap(parser.raw_buffer)])
|
||||
parser.raw_buffer = parser.raw_buffer[:len(parser.raw_buffer)+size_read]
|
||||
if err == io.EOF {
|
||||
parser.eof = true
|
||||
} else if err != nil {
|
||||
return yaml_parser_set_reader_error(parser, "input error: "+err.Error(), parser.offset, -1)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the buffer contains at least `length` characters.
|
||||
// Return true on success, false on failure.
|
||||
//
|
||||
// The length is supposed to be significantly less that the buffer size.
|
||||
func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
|
||||
if parser.read_handler == nil {
|
||||
panic("read handler must be set")
|
||||
}
|
||||
|
||||
// If the EOF flag is set and the raw buffer is empty, do nothing.
|
||||
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Return if the buffer contains enough characters.
|
||||
if parser.unread >= length {
|
||||
return true
|
||||
}
|
||||
|
||||
// Determine the input encoding if it is not known yet.
|
||||
if parser.encoding == yaml_ANY_ENCODING {
|
||||
if !yaml_parser_determine_encoding(parser) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Move the unread characters to the beginning of the buffer.
|
||||
buffer_len := len(parser.buffer)
|
||||
if parser.buffer_pos > 0 && parser.buffer_pos < buffer_len {
|
||||
copy(parser.buffer, parser.buffer[parser.buffer_pos:])
|
||||
buffer_len -= parser.buffer_pos
|
||||
parser.buffer_pos = 0
|
||||
} else if parser.buffer_pos == buffer_len {
|
||||
buffer_len = 0
|
||||
parser.buffer_pos = 0
|
||||
}
|
||||
|
||||
// Open the whole buffer for writing, and cut it before returning.
|
||||
parser.buffer = parser.buffer[:cap(parser.buffer)]
|
||||
|
||||
// Fill the buffer until it has enough characters.
|
||||
first := true
|
||||
for parser.unread < length {
|
||||
|
||||
// Fill the raw buffer if necessary.
|
||||
if !first || parser.raw_buffer_pos == len(parser.raw_buffer) {
|
||||
if !yaml_parser_update_raw_buffer(parser) {
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return false
|
||||
}
|
||||
}
|
||||
first = false
|
||||
|
||||
// Decode the raw buffer.
|
||||
inner:
|
||||
for parser.raw_buffer_pos != len(parser.raw_buffer) {
|
||||
var value rune
|
||||
var width int
|
||||
|
||||
raw_unread := len(parser.raw_buffer) - parser.raw_buffer_pos
|
||||
|
||||
// Decode the next character.
|
||||
switch parser.encoding {
|
||||
case yaml_UTF8_ENCODING:
|
||||
// Decode a UTF-8 character. Check RFC 3629
|
||||
// (http://www.ietf.org/rfc/rfc3629.txt) for more details.
|
||||
//
|
||||
// The following table (taken from the RFC) is used for
|
||||
// decoding.
|
||||
//
|
||||
// Char. number range | UTF-8 octet sequence
|
||||
// (hexadecimal) | (binary)
|
||||
// --------------------+------------------------------------
|
||||
// 0000 0000-0000 007F | 0xxxxxxx
|
||||
// 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
|
||||
// 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
|
||||
// 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
//
|
||||
// Additionally, the characters in the range 0xD800-0xDFFF
|
||||
// are prohibited as they are reserved for use with UTF-16
|
||||
// surrogate pairs.
|
||||
|
||||
// Determine the length of the UTF-8 sequence.
|
||||
octet := parser.raw_buffer[parser.raw_buffer_pos]
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
width = 1
|
||||
case octet&0xE0 == 0xC0:
|
||||
width = 2
|
||||
case octet&0xF0 == 0xE0:
|
||||
width = 3
|
||||
case octet&0xF8 == 0xF0:
|
||||
width = 4
|
||||
default:
|
||||
// The leading octet is invalid.
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid leading UTF-8 octet",
|
||||
parser.offset, int(octet))
|
||||
}
|
||||
|
||||
// Check if the raw buffer contains an incomplete character.
|
||||
if width > raw_unread {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-8 octet sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Decode the leading octet.
|
||||
switch {
|
||||
case octet&0x80 == 0x00:
|
||||
value = rune(octet & 0x7F)
|
||||
case octet&0xE0 == 0xC0:
|
||||
value = rune(octet & 0x1F)
|
||||
case octet&0xF0 == 0xE0:
|
||||
value = rune(octet & 0x0F)
|
||||
case octet&0xF8 == 0xF0:
|
||||
value = rune(octet & 0x07)
|
||||
default:
|
||||
value = 0
|
||||
}
|
||||
|
||||
// Check and decode the trailing octets.
|
||||
for k := 1; k < width; k++ {
|
||||
octet = parser.raw_buffer[parser.raw_buffer_pos+k]
|
||||
|
||||
// Check if the octet is valid.
|
||||
if (octet & 0xC0) != 0x80 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid trailing UTF-8 octet",
|
||||
parser.offset+k, int(octet))
|
||||
}
|
||||
|
||||
// Decode the octet.
|
||||
value = (value << 6) + rune(octet&0x3F)
|
||||
}
|
||||
|
||||
// Check the length of the sequence against the value.
|
||||
switch {
|
||||
case width == 1:
|
||||
case width == 2 && value >= 0x80:
|
||||
case width == 3 && value >= 0x800:
|
||||
case width == 4 && value >= 0x10000:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid length of a UTF-8 sequence",
|
||||
parser.offset, -1)
|
||||
}
|
||||
|
||||
// Check the range of the value.
|
||||
if value >= 0xD800 && value <= 0xDFFF || value > 0x10FFFF {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"invalid Unicode character",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
case yaml_UTF16LE_ENCODING, yaml_UTF16BE_ENCODING:
|
||||
var low, high int
|
||||
if parser.encoding == yaml_UTF16LE_ENCODING {
|
||||
low, high = 0, 1
|
||||
} else {
|
||||
low, high = 1, 0
|
||||
}
|
||||
|
||||
// The UTF-16 encoding is not as simple as one might
|
||||
// naively think. Check RFC 2781
|
||||
// (http://www.ietf.org/rfc/rfc2781.txt).
|
||||
//
|
||||
// Normally, two subsequent bytes describe a Unicode
|
||||
// character. However a special technique (called a
|
||||
// surrogate pair) is used for specifying character
|
||||
// values larger than 0xFFFF.
|
||||
//
|
||||
// A surrogate pair consists of two pseudo-characters:
|
||||
// high surrogate area (0xD800-0xDBFF)
|
||||
// low surrogate area (0xDC00-0xDFFF)
|
||||
//
|
||||
// The following formulas are used for decoding
|
||||
// and encoding characters using surrogate pairs:
|
||||
//
|
||||
// U = U' + 0x10000 (0x01 00 00 <= U <= 0x10 FF FF)
|
||||
// U' = yyyyyyyyyyxxxxxxxxxx (0 <= U' <= 0x0F FF FF)
|
||||
// W1 = 110110yyyyyyyyyy
|
||||
// W2 = 110111xxxxxxxxxx
|
||||
//
|
||||
// where U is the character value, W1 is the high surrogate
|
||||
// area, W2 is the low surrogate area.
|
||||
|
||||
// Check for incomplete UTF-16 character.
|
||||
if raw_unread < 2 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 character",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the character.
|
||||
value = rune(parser.raw_buffer[parser.raw_buffer_pos+low]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high]) << 8)
|
||||
|
||||
// Check for unexpected low surrogate area.
|
||||
if value&0xFC00 == 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"unexpected low surrogate area",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Check for a high surrogate area.
|
||||
if value&0xFC00 == 0xD800 {
|
||||
width = 4
|
||||
|
||||
// Check for incomplete surrogate pair.
|
||||
if raw_unread < 4 {
|
||||
if parser.eof {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"incomplete UTF-16 surrogate pair",
|
||||
parser.offset, -1)
|
||||
}
|
||||
break inner
|
||||
}
|
||||
|
||||
// Get the next character.
|
||||
value2 := rune(parser.raw_buffer[parser.raw_buffer_pos+low+2]) +
|
||||
(rune(parser.raw_buffer[parser.raw_buffer_pos+high+2]) << 8)
|
||||
|
||||
// Check for a low surrogate area.
|
||||
if value2&0xFC00 != 0xDC00 {
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"expected low surrogate area",
|
||||
parser.offset+2, int(value2))
|
||||
}
|
||||
|
||||
// Generate the value of the surrogate pair.
|
||||
value = 0x10000 + ((value & 0x3FF) << 10) + (value2 & 0x3FF)
|
||||
} else {
|
||||
width = 2
|
||||
}
|
||||
|
||||
default:
|
||||
panic("impossible")
|
||||
}
|
||||
|
||||
// Check if the character is in the allowed range:
|
||||
// #x9 | #xA | #xD | [#x20-#x7E] (8 bit)
|
||||
// | #x85 | [#xA0-#xD7FF] | [#xE000-#xFFFD] (16 bit)
|
||||
// | [#x10000-#x10FFFF] (32 bit)
|
||||
switch {
|
||||
case value == 0x09:
|
||||
case value == 0x0A:
|
||||
case value == 0x0D:
|
||||
case value >= 0x20 && value <= 0x7E:
|
||||
case value == 0x85:
|
||||
case value >= 0xA0 && value <= 0xD7FF:
|
||||
case value >= 0xE000 && value <= 0xFFFD:
|
||||
case value >= 0x10000 && value <= 0x10FFFF:
|
||||
default:
|
||||
return yaml_parser_set_reader_error(parser,
|
||||
"control characters are not allowed",
|
||||
parser.offset, int(value))
|
||||
}
|
||||
|
||||
// Move the raw pointers.
|
||||
parser.raw_buffer_pos += width
|
||||
parser.offset += width
|
||||
|
||||
// Finally put the character into the buffer.
|
||||
if value <= 0x7F {
|
||||
// 0000 0000-0000 007F . 0xxxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(value)
|
||||
buffer_len += 1
|
||||
} else if value <= 0x7FF {
|
||||
// 0000 0080-0000 07FF . 110xxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xC0 + (value >> 6))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 2
|
||||
} else if value <= 0xFFFF {
|
||||
// 0000 0800-0000 FFFF . 1110xxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xE0 + (value >> 12))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 3
|
||||
} else {
|
||||
// 0001 0000-0010 FFFF . 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||||
parser.buffer[buffer_len+0] = byte(0xF0 + (value >> 18))
|
||||
parser.buffer[buffer_len+1] = byte(0x80 + ((value >> 12) & 0x3F))
|
||||
parser.buffer[buffer_len+2] = byte(0x80 + ((value >> 6) & 0x3F))
|
||||
parser.buffer[buffer_len+3] = byte(0x80 + (value & 0x3F))
|
||||
buffer_len += 4
|
||||
}
|
||||
|
||||
parser.unread++
|
||||
}
|
||||
|
||||
// On EOF, put NUL into the buffer and return.
|
||||
if parser.eof {
|
||||
parser.buffer[buffer_len] = 0
|
||||
buffer_len++
|
||||
parser.unread++
|
||||
break
|
||||
}
|
||||
}
|
||||
parser.buffer = parser.buffer[:buffer_len]
|
||||
return true
|
||||
}
|
|
@ -1,245 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type resolveMapItem struct {
|
||||
value interface{}
|
||||
tag string
|
||||
}
|
||||
|
||||
var resolveTable = make([]byte, 256)
|
||||
var resolveMap = make(map[string]resolveMapItem)
|
||||
|
||||
func init() {
|
||||
t := resolveTable
|
||||
t[int('+')] = 'S' // Sign
|
||||
t[int('-')] = 'S'
|
||||
for _, c := range "0123456789" {
|
||||
t[int(c)] = 'D' // Digit
|
||||
}
|
||||
for _, c := range "yYnNtTfFoO~" {
|
||||
t[int(c)] = 'M' // In map
|
||||
}
|
||||
t[int('.')] = '.' // Float (potentially in map)
|
||||
|
||||
var resolveMapList = []struct {
|
||||
v interface{}
|
||||
tag string
|
||||
l []string
|
||||
}{
|
||||
{true, yaml_BOOL_TAG, []string{"y", "Y", "yes", "Yes", "YES"}},
|
||||
{true, yaml_BOOL_TAG, []string{"true", "True", "TRUE"}},
|
||||
{true, yaml_BOOL_TAG, []string{"on", "On", "ON"}},
|
||||
{false, yaml_BOOL_TAG, []string{"n", "N", "no", "No", "NO"}},
|
||||
{false, yaml_BOOL_TAG, []string{"false", "False", "FALSE"}},
|
||||
{false, yaml_BOOL_TAG, []string{"off", "Off", "OFF"}},
|
||||
{nil, yaml_NULL_TAG, []string{"", "~", "null", "Null", "NULL"}},
|
||||
{math.NaN(), yaml_FLOAT_TAG, []string{".nan", ".NaN", ".NAN"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{".inf", ".Inf", ".INF"}},
|
||||
{math.Inf(+1), yaml_FLOAT_TAG, []string{"+.inf", "+.Inf", "+.INF"}},
|
||||
{math.Inf(-1), yaml_FLOAT_TAG, []string{"-.inf", "-.Inf", "-.INF"}},
|
||||
{"<<", yaml_MERGE_TAG, []string{"<<"}},
|
||||
}
|
||||
|
||||
m := resolveMap
|
||||
for _, item := range resolveMapList {
|
||||
for _, s := range item.l {
|
||||
m[s] = resolveMapItem{item.v, item.tag}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const longTagPrefix = "tag:yaml.org,2002:"
|
||||
|
||||
func shortTag(tag string) string {
|
||||
// TODO This can easily be made faster and produce less garbage.
|
||||
if strings.HasPrefix(tag, longTagPrefix) {
|
||||
return "!!" + tag[len(longTagPrefix):]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func longTag(tag string) string {
|
||||
if strings.HasPrefix(tag, "!!") {
|
||||
return longTagPrefix + tag[2:]
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func resolvableTag(tag string) bool {
|
||||
switch tag {
|
||||
case "", yaml_STR_TAG, yaml_BOOL_TAG, yaml_INT_TAG, yaml_FLOAT_TAG, yaml_NULL_TAG, yaml_TIMESTAMP_TAG:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`)
|
||||
|
||||
func resolve(tag string, in string) (rtag string, out interface{}) {
|
||||
if !resolvableTag(tag) {
|
||||
return tag, in
|
||||
}
|
||||
|
||||
defer func() {
|
||||
switch tag {
|
||||
case "", rtag, yaml_STR_TAG, yaml_BINARY_TAG:
|
||||
return
|
||||
}
|
||||
failf("cannot decode %s `%s` as a %s", shortTag(rtag), in, shortTag(tag))
|
||||
}()
|
||||
|
||||
// Any data is accepted as a !!str or !!binary.
|
||||
// Otherwise, the prefix is enough of a hint about what it might be.
|
||||
hint := byte('N')
|
||||
if in != "" {
|
||||
hint = resolveTable[in[0]]
|
||||
}
|
||||
if hint != 0 && tag != yaml_STR_TAG && tag != yaml_BINARY_TAG {
|
||||
// Handle things we can lookup in a map.
|
||||
if item, ok := resolveMap[in]; ok {
|
||||
return item.tag, item.value
|
||||
}
|
||||
|
||||
// Base 60 floats are a bad idea, were dropped in YAML 1.2, and
|
||||
// are purposefully unsupported here. They're still quoted on
|
||||
// the way out for compatibility with other parser, though.
|
||||
|
||||
switch hint {
|
||||
case 'M':
|
||||
// We've already checked the map above.
|
||||
|
||||
case '.':
|
||||
// Not in the map, so maybe a normal float.
|
||||
floatv, err := strconv.ParseFloat(in, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
|
||||
case 'D', 'S':
|
||||
// Int, float, or timestamp.
|
||||
// Only try values as a timestamp if the value is unquoted or there's an explicit
|
||||
// !!timestamp tag.
|
||||
if tag == "" || tag == yaml_TIMESTAMP_TAG {
|
||||
t, ok := parseTimestamp(in)
|
||||
if ok {
|
||||
return yaml_TIMESTAMP_TAG, t
|
||||
}
|
||||
}
|
||||
|
||||
plain := strings.Replace(in, "_", "", -1)
|
||||
intv, err := strconv.ParseInt(plain, 0, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain, 0, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
if yamlStyleFloat.MatchString(plain) {
|
||||
floatv, err := strconv.ParseFloat(plain, 64)
|
||||
if err == nil {
|
||||
return yaml_FLOAT_TAG, floatv
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(plain, "0b") {
|
||||
intv, err := strconv.ParseInt(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, intv
|
||||
}
|
||||
}
|
||||
uintv, err := strconv.ParseUint(plain[2:], 2, 64)
|
||||
if err == nil {
|
||||
return yaml_INT_TAG, uintv
|
||||
}
|
||||
} else if strings.HasPrefix(plain, "-0b") {
|
||||
intv, err := strconv.ParseInt(plain[3:], 2, 64)
|
||||
if err == nil {
|
||||
if intv == int64(int(intv)) {
|
||||
return yaml_INT_TAG, -int(intv)
|
||||
} else {
|
||||
return yaml_INT_TAG, -intv
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
panic("resolveTable item not yet handled: " + string(rune(hint)) + " (with " + in + ")")
|
||||
}
|
||||
}
|
||||
return yaml_STR_TAG, in
|
||||
}
|
||||
|
||||
// encodeBase64 encodes s as base64 that is broken up into multiple lines
|
||||
// as appropriate for the resulting length.
|
||||
func encodeBase64(s string) string {
|
||||
const lineLen = 70
|
||||
encLen := base64.StdEncoding.EncodedLen(len(s))
|
||||
lines := encLen/lineLen + 1
|
||||
buf := make([]byte, encLen*2+lines)
|
||||
in := buf[0:encLen]
|
||||
out := buf[encLen:]
|
||||
base64.StdEncoding.Encode(in, []byte(s))
|
||||
k := 0
|
||||
for i := 0; i < len(in); i += lineLen {
|
||||
j := i + lineLen
|
||||
if j > len(in) {
|
||||
j = len(in)
|
||||
}
|
||||
k += copy(out[k:], in[i:j])
|
||||
if lines > 1 {
|
||||
out[k] = '\n'
|
||||
k++
|
||||
}
|
||||
}
|
||||
return string(out[:k])
|
||||
}
|
||||
|
||||
// This is a subset of the formats allowed by the regular expression
|
||||
// defined at http://yaml.org/type/timestamp.html.
|
||||
var allowedTimestampFormats = []string{
|
||||
"2006-1-2T15:4:5Z07:00",
|
||||
"2006-1-2t15:4:5Z07:00", // RFC3339 with lower-case "t".
|
||||
"2006-1-2 15:4:5", // space separated with no time zone
|
||||
"2006-1-2", // date only
|
||||
// Notable exception: time.Parse cannot handle: "2001-12-14 21:59:43.10 -5"
|
||||
// from the set of examples.
|
||||
}
|
||||
|
||||
// parseTimestamp parses s as a timestamp string and
|
||||
// returns the timestamp and reports whether it succeeded.
|
||||
// Timestamp formats are defined at http://yaml.org/type/timestamp.html
|
||||
func parseTimestamp(s string) (time.Time, bool) {
|
||||
// TODO write code to check all the formats supported by
|
||||
// http://yaml.org/type/timestamp.html instead of using time.Parse.
|
||||
|
||||
// Quick check: all date formats start with YYYY-.
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
if c := s[i]; c < '0' || c > '9' {
|
||||
break
|
||||
}
|
||||
}
|
||||
if i != 4 || i == len(s) || s[i] != '-' {
|
||||
return time.Time{}, false
|
||||
}
|
||||
for _, format := range allowedTimestampFormats {
|
||||
if t, err := time.Parse(format, s); err == nil {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
return time.Time{}, false
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,104 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type keyList []reflect.Value
|
||||
|
||||
func (l keyList) Len() int { return len(l) }
|
||||
func (l keyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l keyList) Less(i, j int) bool {
|
||||
a := l[i]
|
||||
b := l[j]
|
||||
ak := a.Kind()
|
||||
bk := b.Kind()
|
||||
for (ak == reflect.Interface || ak == reflect.Ptr) && !a.IsNil() {
|
||||
a = a.Elem()
|
||||
ak = a.Kind()
|
||||
}
|
||||
for (bk == reflect.Interface || bk == reflect.Ptr) && !b.IsNil() {
|
||||
b = b.Elem()
|
||||
bk = b.Kind()
|
||||
}
|
||||
af, aok := keyFloat(a)
|
||||
bf, bok := keyFloat(b)
|
||||
if aok && bok {
|
||||
if af != bf {
|
||||
return af < bf
|
||||
}
|
||||
if ak != bk {
|
||||
return ak < bk
|
||||
}
|
||||
return numLess(a, b)
|
||||
}
|
||||
if ak != reflect.String || bk != reflect.String {
|
||||
return ak < bk
|
||||
}
|
||||
ar, br := []rune(a.String()), []rune(b.String())
|
||||
for i := 0; i < len(ar) && i < len(br); i++ {
|
||||
if ar[i] == br[i] {
|
||||
continue
|
||||
}
|
||||
al := unicode.IsLetter(ar[i])
|
||||
bl := unicode.IsLetter(br[i])
|
||||
if al && bl {
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
if al || bl {
|
||||
return bl
|
||||
}
|
||||
var ai, bi int
|
||||
var an, bn int64
|
||||
for ai = i; ai < len(ar) && unicode.IsDigit(ar[ai]); ai++ {
|
||||
an = an*10 + int64(ar[ai]-'0')
|
||||
}
|
||||
for bi = i; bi < len(br) && unicode.IsDigit(br[bi]); bi++ {
|
||||
bn = bn*10 + int64(br[bi]-'0')
|
||||
}
|
||||
if an != bn {
|
||||
return an < bn
|
||||
}
|
||||
if ai != bi {
|
||||
return ai < bi
|
||||
}
|
||||
return ar[i] < br[i]
|
||||
}
|
||||
return len(ar) < len(br)
|
||||
}
|
||||
|
||||
// keyFloat returns a float value for v if it is a number/bool
|
||||
// and whether it is a number/bool or not.
|
||||
func keyFloat(v reflect.Value) (f float64, ok bool) {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return float64(v.Int()), true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float(), true
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return float64(v.Uint()), true
|
||||
case reflect.Bool:
|
||||
if v.Bool() {
|
||||
return 1, true
|
||||
}
|
||||
return 0, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// numLess returns whether a < b.
|
||||
// a and b must necessarily have the same kind.
|
||||
func numLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
}
|
||||
panic("not a number")
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
package yaml
|
||||
|
||||
// Set the writer error and return false.
|
||||
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
|
||||
emitter.error = yaml_WRITER_ERROR
|
||||
emitter.problem = problem
|
||||
return false
|
||||
}
|
||||
|
||||
// Flush the output buffer.
|
||||
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
|
||||
if emitter.write_handler == nil {
|
||||
panic("write handler not set")
|
||||
}
|
||||
|
||||
// Check if the buffer is empty.
|
||||
if emitter.buffer_pos == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
|
||||
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
|
||||
}
|
||||
emitter.buffer_pos = 0
|
||||
return true
|
||||
}
|
|
@ -1,466 +0,0 @@
|
|||
// Package yaml implements YAML support for the Go language.
|
||||
//
|
||||
// Source code and other details for the project are available at GitHub:
|
||||
//
|
||||
// https://github.com/go-yaml/yaml
|
||||
//
|
||||
package yaml
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MapSlice encodes and decodes as a YAML map.
|
||||
// The order of keys is preserved when encoding and decoding.
|
||||
type MapSlice []MapItem
|
||||
|
||||
// MapItem is an item in a MapSlice.
|
||||
type MapItem struct {
|
||||
Key, Value interface{}
|
||||
}
|
||||
|
||||
// The Unmarshaler interface may be implemented by types to customize their
|
||||
// behavior when being unmarshaled from a YAML document. The UnmarshalYAML
|
||||
// method receives a function that may be called to unmarshal the original
|
||||
// YAML value into a field or variable. It is safe to call the unmarshal
|
||||
// function parameter more than once if necessary.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalYAML(unmarshal func(interface{}) error) error
|
||||
}
|
||||
|
||||
// The Marshaler interface may be implemented by types to customize their
|
||||
// behavior when being marshaled into a YAML document. The returned value
|
||||
// is marshaled in place of the original value implementing Marshaler.
|
||||
//
|
||||
// If an error is returned by MarshalYAML, the marshaling procedure stops
|
||||
// and returns with the provided error.
|
||||
type Marshaler interface {
|
||||
MarshalYAML() (interface{}, error)
|
||||
}
|
||||
|
||||
// Unmarshal decodes the first document found within the in byte slice
|
||||
// and assigns decoded values into the out value.
|
||||
//
|
||||
// Maps and pointers (to a struct, string, int, etc) are accepted as out
|
||||
// values. If an internal pointer within a struct is not initialized,
|
||||
// the yaml package will initialize it if necessary for unmarshalling
|
||||
// the provided data. The out parameter must not be nil.
|
||||
//
|
||||
// The type of the decoded values should be compatible with the respective
|
||||
// values in out. If one or more values cannot be decoded due to a type
|
||||
// mismatches, decoding continues partially until the end of the YAML
|
||||
// content, and a *yaml.TypeError is returned with details for all
|
||||
// missed values.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an
|
||||
// upper case first letter), and are unmarshalled using the field name
|
||||
// lowercased as the default key. Custom keys may be defined via the
|
||||
// "yaml" name in the field tag: the content preceding the first comma
|
||||
// is used as the key, and the following comma-separated options are
|
||||
// used to tweak the marshalling process (see Marshal).
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// var t T
|
||||
// yaml.Unmarshal([]byte("a: 1\nb: 2"), &t)
|
||||
//
|
||||
// See the documentation of Marshal for the format of tags and a list of
|
||||
// supported tag options.
|
||||
//
|
||||
func Unmarshal(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, false)
|
||||
}
|
||||
|
||||
// UnmarshalStrict is like Unmarshal except that any fields that are found
|
||||
// in the data that do not have corresponding struct members, or mapping
|
||||
// keys that are duplicates, will result in
|
||||
// an error.
|
||||
func UnmarshalStrict(in []byte, out interface{}) (err error) {
|
||||
return unmarshal(in, out, true)
|
||||
}
|
||||
|
||||
// A Decorder reads and decodes YAML values from an input stream.
|
||||
type Decoder struct {
|
||||
strict bool
|
||||
parser *parser
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
//
|
||||
// The decoder introduces its own buffering and may read
|
||||
// data from r beyond the YAML values requested.
|
||||
func NewDecoder(r io.Reader) *Decoder {
|
||||
return &Decoder{
|
||||
parser: newParserFromReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
// SetStrict sets whether strict decoding behaviour is enabled when
|
||||
// decoding items in the data (see UnmarshalStrict). By default, decoding is not strict.
|
||||
func (dec *Decoder) SetStrict(strict bool) {
|
||||
dec.strict = strict
|
||||
}
|
||||
|
||||
// Decode reads the next YAML-encoded value from its input
|
||||
// and stores it in the value pointed to by v.
|
||||
//
|
||||
// See the documentation for Unmarshal for details about the
|
||||
// conversion of YAML into a Go value.
|
||||
func (dec *Decoder) Decode(v interface{}) (err error) {
|
||||
d := newDecoder(dec.strict)
|
||||
defer handleErr(&err)
|
||||
node := dec.parser.parse()
|
||||
if node == nil {
|
||||
return io.EOF
|
||||
}
|
||||
out := reflect.ValueOf(v)
|
||||
if out.Kind() == reflect.Ptr && !out.IsNil() {
|
||||
out = out.Elem()
|
||||
}
|
||||
d.unmarshal(node, out)
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshal(in []byte, out interface{}, strict bool) (err error) {
|
||||
defer handleErr(&err)
|
||||
d := newDecoder(strict)
|
||||
p := newParser(in)
|
||||
defer p.destroy()
|
||||
node := p.parse()
|
||||
if node != nil {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() == reflect.Ptr && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
d.unmarshal(node, v)
|
||||
}
|
||||
if len(d.terrors) > 0 {
|
||||
return &TypeError{d.terrors}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Marshal serializes the value provided into a YAML document. The structure
|
||||
// of the generated document will reflect the structure of the value itself.
|
||||
// Maps and pointers (to struct, string, int, etc) are accepted as the in value.
|
||||
//
|
||||
// Struct fields are only unmarshalled if they are exported (have an upper case
|
||||
// first letter), and are unmarshalled using the field name lowercased as the
|
||||
// default key. Custom keys may be defined via the "yaml" name in the field
|
||||
// tag: the content preceding the first comma is used as the key, and the
|
||||
// following comma-separated options are used to tweak the marshalling process.
|
||||
// Conflicting names result in a runtime error.
|
||||
//
|
||||
// The field tag format accepted is:
|
||||
//
|
||||
// `(...) yaml:"[<key>][,<flag1>[,<flag2>]]" (...)`
|
||||
//
|
||||
// The following flags are currently supported:
|
||||
//
|
||||
// omitempty Only include the field if it's not set to the zero
|
||||
// value for the type or to empty slices or maps.
|
||||
// Zero valued structs will be omitted if all their public
|
||||
// fields are zero, unless they implement an IsZero
|
||||
// method (see the IsZeroer interface type), in which
|
||||
// case the field will be included if that method returns true.
|
||||
//
|
||||
// flow Marshal using a flow style (useful for structs,
|
||||
// sequences and maps).
|
||||
//
|
||||
// inline Inline the field, which must be a struct or a map,
|
||||
// causing all of its fields or keys to be processed as if
|
||||
// they were part of the outer struct. For maps, keys must
|
||||
// not conflict with the yaml keys of other struct fields.
|
||||
//
|
||||
// In addition, if the key is "-", the field is ignored.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// type T struct {
|
||||
// F int `yaml:"a,omitempty"`
|
||||
// B int
|
||||
// }
|
||||
// yaml.Marshal(&T{B: 2}) // Returns "b: 2\n"
|
||||
// yaml.Marshal(&T{F: 1}} // Returns "a: 1\nb: 0\n"
|
||||
//
|
||||
func Marshal(in interface{}) (out []byte, err error) {
|
||||
defer handleErr(&err)
|
||||
e := newEncoder()
|
||||
defer e.destroy()
|
||||
e.marshalDoc("", reflect.ValueOf(in))
|
||||
e.finish()
|
||||
out = e.out
|
||||
return
|
||||
}
|
||||
|
||||
// An Encoder writes YAML values to an output stream.
|
||||
type Encoder struct {
|
||||
encoder *encoder
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
// The Encoder should be closed after use to flush all data
|
||||
// to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
encoder: newEncoderWithWriter(w),
|
||||
}
|
||||
}
|
||||
|
||||
// Encode writes the YAML encoding of v to the stream.
|
||||
// If multiple items are encoded to the stream, the
|
||||
// second and subsequent document will be preceded
|
||||
// with a "---" document separator, but the first will not.
|
||||
//
|
||||
// See the documentation for Marshal for details about the conversion of Go
|
||||
// values to YAML.
|
||||
func (e *Encoder) Encode(v interface{}) (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.marshalDoc("", reflect.ValueOf(v))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the encoder by writing any remaining data.
|
||||
// It does not write a stream terminating string "...".
|
||||
func (e *Encoder) Close() (err error) {
|
||||
defer handleErr(&err)
|
||||
e.encoder.finish()
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleErr(err *error) {
|
||||
if v := recover(); v != nil {
|
||||
if e, ok := v.(yamlError); ok {
|
||||
*err = e.err
|
||||
} else {
|
||||
panic(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type yamlError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func fail(err error) {
|
||||
panic(yamlError{err})
|
||||
}
|
||||
|
||||
func failf(format string, args ...interface{}) {
|
||||
panic(yamlError{fmt.Errorf("yaml: "+format, args...)})
|
||||
}
|
||||
|
||||
// A TypeError is returned by Unmarshal when one or more fields in
|
||||
// the YAML document cannot be properly decoded into the requested
|
||||
// types. When this error is returned, the value is still
|
||||
// unmarshaled partially.
|
||||
type TypeError struct {
|
||||
Errors []string
|
||||
}
|
||||
|
||||
func (e *TypeError) Error() string {
|
||||
return fmt.Sprintf("yaml: unmarshal errors:\n %s", strings.Join(e.Errors, "\n "))
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------
|
||||
// Maintain a mapping of keys to structure field indexes
|
||||
|
||||
// The code in this section was copied from mgo/bson.
|
||||
|
||||
// structInfo holds details for the serialization of fields of
|
||||
// a given struct.
|
||||
type structInfo struct {
|
||||
FieldsMap map[string]fieldInfo
|
||||
FieldsList []fieldInfo
|
||||
|
||||
// InlineMap is the number of the field in the struct that
|
||||
// contains an ,inline map, or -1 if there's none.
|
||||
InlineMap int
|
||||
}
|
||||
|
||||
type fieldInfo struct {
|
||||
Key string
|
||||
Num int
|
||||
OmitEmpty bool
|
||||
Flow bool
|
||||
// Id holds the unique field identifier, so we can cheaply
|
||||
// check for field duplicates without maintaining an extra map.
|
||||
Id int
|
||||
|
||||
// Inline holds the field index if the field is part of an inlined struct.
|
||||
Inline []int
|
||||
}
|
||||
|
||||
var structMap = make(map[reflect.Type]*structInfo)
|
||||
var fieldMapMutex sync.RWMutex
|
||||
|
||||
func getStructInfo(st reflect.Type) (*structInfo, error) {
|
||||
fieldMapMutex.RLock()
|
||||
sinfo, found := structMap[st]
|
||||
fieldMapMutex.RUnlock()
|
||||
if found {
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
n := st.NumField()
|
||||
fieldsMap := make(map[string]fieldInfo)
|
||||
fieldsList := make([]fieldInfo, 0, n)
|
||||
inlineMap := -1
|
||||
for i := 0; i != n; i++ {
|
||||
field := st.Field(i)
|
||||
if field.PkgPath != "" && !field.Anonymous {
|
||||
continue // Private field
|
||||
}
|
||||
|
||||
info := fieldInfo{Num: i}
|
||||
|
||||
tag := field.Tag.Get("yaml")
|
||||
if tag == "" && strings.Index(string(field.Tag), ":") < 0 {
|
||||
tag = string(field.Tag)
|
||||
}
|
||||
if tag == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
inline := false
|
||||
fields := strings.Split(tag, ",")
|
||||
if len(fields) > 1 {
|
||||
for _, flag := range fields[1:] {
|
||||
switch flag {
|
||||
case "omitempty":
|
||||
info.OmitEmpty = true
|
||||
case "flow":
|
||||
info.Flow = true
|
||||
case "inline":
|
||||
inline = true
|
||||
default:
|
||||
return nil, errors.New(fmt.Sprintf("Unsupported flag %q in tag %q of type %s", flag, tag, st))
|
||||
}
|
||||
}
|
||||
tag = fields[0]
|
||||
}
|
||||
|
||||
if inline {
|
||||
switch field.Type.Kind() {
|
||||
case reflect.Map:
|
||||
if inlineMap >= 0 {
|
||||
return nil, errors.New("Multiple ,inline maps in struct " + st.String())
|
||||
}
|
||||
if field.Type.Key() != reflect.TypeOf("") {
|
||||
return nil, errors.New("Option ,inline needs a map with string keys in struct " + st.String())
|
||||
}
|
||||
inlineMap = info.Num
|
||||
case reflect.Struct:
|
||||
sinfo, err := getStructInfo(field.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, finfo := range sinfo.FieldsList {
|
||||
if _, found := fieldsMap[finfo.Key]; found {
|
||||
msg := "Duplicated key '" + finfo.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
if finfo.Inline == nil {
|
||||
finfo.Inline = []int{i, finfo.Num}
|
||||
} else {
|
||||
finfo.Inline = append([]int{i}, finfo.Inline...)
|
||||
}
|
||||
finfo.Id = len(fieldsList)
|
||||
fieldsMap[finfo.Key] = finfo
|
||||
fieldsList = append(fieldsList, finfo)
|
||||
}
|
||||
default:
|
||||
//return nil, errors.New("Option ,inline needs a struct value or map field")
|
||||
return nil, errors.New("Option ,inline needs a struct value field")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if tag != "" {
|
||||
info.Key = tag
|
||||
} else {
|
||||
info.Key = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
if _, found = fieldsMap[info.Key]; found {
|
||||
msg := "Duplicated key '" + info.Key + "' in struct " + st.String()
|
||||
return nil, errors.New(msg)
|
||||
}
|
||||
|
||||
info.Id = len(fieldsList)
|
||||
fieldsList = append(fieldsList, info)
|
||||
fieldsMap[info.Key] = info
|
||||
}
|
||||
|
||||
sinfo = &structInfo{
|
||||
FieldsMap: fieldsMap,
|
||||
FieldsList: fieldsList,
|
||||
InlineMap: inlineMap,
|
||||
}
|
||||
|
||||
fieldMapMutex.Lock()
|
||||
structMap[st] = sinfo
|
||||
fieldMapMutex.Unlock()
|
||||
return sinfo, nil
|
||||
}
|
||||
|
||||
// IsZeroer is used to check whether an object is zero to
|
||||
// determine whether it should be omitted when marshaling
|
||||
// with the omitempty flag. One notable implementation
|
||||
// is time.Time.
|
||||
type IsZeroer interface {
|
||||
IsZero() bool
|
||||
}
|
||||
|
||||
func isZero(v reflect.Value) bool {
|
||||
kind := v.Kind()
|
||||
if z, ok := v.Interface().(IsZeroer); ok {
|
||||
if (kind == reflect.Ptr || kind == reflect.Interface) && v.IsNil() {
|
||||
return true
|
||||
}
|
||||
return z.IsZero()
|
||||
}
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return len(v.String()) == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
case reflect.Slice:
|
||||
return v.Len() == 0
|
||||
case reflect.Map:
|
||||
return v.Len() == 0
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Struct:
|
||||
vt := v.Type()
|
||||
for i := v.NumField() - 1; i >= 0; i-- {
|
||||
if vt.Field(i).PkgPath != "" {
|
||||
continue // Private field
|
||||
}
|
||||
if !isZero(v.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,738 +0,0 @@
|
|||
package yaml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// The version directive data.
|
||||
type yaml_version_directive_t struct {
|
||||
major int8 // The major version number.
|
||||
minor int8 // The minor version number.
|
||||
}
|
||||
|
||||
// The tag directive data.
|
||||
type yaml_tag_directive_t struct {
|
||||
handle []byte // The tag handle.
|
||||
prefix []byte // The tag prefix.
|
||||
}
|
||||
|
||||
type yaml_encoding_t int
|
||||
|
||||
// The stream encoding.
|
||||
const (
|
||||
// Let the parser choose the encoding.
|
||||
yaml_ANY_ENCODING yaml_encoding_t = iota
|
||||
|
||||
yaml_UTF8_ENCODING // The default UTF-8 encoding.
|
||||
yaml_UTF16LE_ENCODING // The UTF-16-LE encoding with BOM.
|
||||
yaml_UTF16BE_ENCODING // The UTF-16-BE encoding with BOM.
|
||||
)
|
||||
|
||||
type yaml_break_t int
|
||||
|
||||
// Line break types.
|
||||
const (
|
||||
// Let the parser choose the break type.
|
||||
yaml_ANY_BREAK yaml_break_t = iota
|
||||
|
||||
yaml_CR_BREAK // Use CR for line breaks (Mac style).
|
||||
yaml_LN_BREAK // Use LN for line breaks (Unix style).
|
||||
yaml_CRLN_BREAK // Use CR LN for line breaks (DOS style).
|
||||
)
|
||||
|
||||
type yaml_error_type_t int
|
||||
|
||||
// Many bad things could happen with the parser and emitter.
|
||||
const (
|
||||
// No error is produced.
|
||||
yaml_NO_ERROR yaml_error_type_t = iota
|
||||
|
||||
yaml_MEMORY_ERROR // Cannot allocate or reallocate a block of memory.
|
||||
yaml_READER_ERROR // Cannot read or decode the input stream.
|
||||
yaml_SCANNER_ERROR // Cannot scan the input stream.
|
||||
yaml_PARSER_ERROR // Cannot parse the input stream.
|
||||
yaml_COMPOSER_ERROR // Cannot compose a YAML document.
|
||||
yaml_WRITER_ERROR // Cannot write to the output stream.
|
||||
yaml_EMITTER_ERROR // Cannot emit a YAML stream.
|
||||
)
|
||||
|
||||
// The pointer position.
|
||||
type yaml_mark_t struct {
|
||||
index int // The position index.
|
||||
line int // The position line.
|
||||
column int // The position column.
|
||||
}
|
||||
|
||||
// Node Styles
|
||||
|
||||
type yaml_style_t int8
|
||||
|
||||
type yaml_scalar_style_t yaml_style_t
|
||||
|
||||
// Scalar styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SCALAR_STYLE yaml_scalar_style_t = iota
|
||||
|
||||
yaml_PLAIN_SCALAR_STYLE // The plain scalar style.
|
||||
yaml_SINGLE_QUOTED_SCALAR_STYLE // The single-quoted scalar style.
|
||||
yaml_DOUBLE_QUOTED_SCALAR_STYLE // The double-quoted scalar style.
|
||||
yaml_LITERAL_SCALAR_STYLE // The literal scalar style.
|
||||
yaml_FOLDED_SCALAR_STYLE // The folded scalar style.
|
||||
)
|
||||
|
||||
type yaml_sequence_style_t yaml_style_t
|
||||
|
||||
// Sequence styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_SEQUENCE_STYLE yaml_sequence_style_t = iota
|
||||
|
||||
yaml_BLOCK_SEQUENCE_STYLE // The block sequence style.
|
||||
yaml_FLOW_SEQUENCE_STYLE // The flow sequence style.
|
||||
)
|
||||
|
||||
type yaml_mapping_style_t yaml_style_t
|
||||
|
||||
// Mapping styles.
|
||||
const (
|
||||
// Let the emitter choose the style.
|
||||
yaml_ANY_MAPPING_STYLE yaml_mapping_style_t = iota
|
||||
|
||||
yaml_BLOCK_MAPPING_STYLE // The block mapping style.
|
||||
yaml_FLOW_MAPPING_STYLE // The flow mapping style.
|
||||
)
|
||||
|
||||
// Tokens
|
||||
|
||||
type yaml_token_type_t int
|
||||
|
||||
// Token types.
|
||||
const (
|
||||
// An empty token.
|
||||
yaml_NO_TOKEN yaml_token_type_t = iota
|
||||
|
||||
yaml_STREAM_START_TOKEN // A STREAM-START token.
|
||||
yaml_STREAM_END_TOKEN // A STREAM-END token.
|
||||
|
||||
yaml_VERSION_DIRECTIVE_TOKEN // A VERSION-DIRECTIVE token.
|
||||
yaml_TAG_DIRECTIVE_TOKEN // A TAG-DIRECTIVE token.
|
||||
yaml_DOCUMENT_START_TOKEN // A DOCUMENT-START token.
|
||||
yaml_DOCUMENT_END_TOKEN // A DOCUMENT-END token.
|
||||
|
||||
yaml_BLOCK_SEQUENCE_START_TOKEN // A BLOCK-SEQUENCE-START token.
|
||||
yaml_BLOCK_MAPPING_START_TOKEN // A BLOCK-SEQUENCE-END token.
|
||||
yaml_BLOCK_END_TOKEN // A BLOCK-END token.
|
||||
|
||||
yaml_FLOW_SEQUENCE_START_TOKEN // A FLOW-SEQUENCE-START token.
|
||||
yaml_FLOW_SEQUENCE_END_TOKEN // A FLOW-SEQUENCE-END token.
|
||||
yaml_FLOW_MAPPING_START_TOKEN // A FLOW-MAPPING-START token.
|
||||
yaml_FLOW_MAPPING_END_TOKEN // A FLOW-MAPPING-END token.
|
||||
|
||||
yaml_BLOCK_ENTRY_TOKEN // A BLOCK-ENTRY token.
|
||||
yaml_FLOW_ENTRY_TOKEN // A FLOW-ENTRY token.
|
||||
yaml_KEY_TOKEN // A KEY token.
|
||||
yaml_VALUE_TOKEN // A VALUE token.
|
||||
|
||||
yaml_ALIAS_TOKEN // An ALIAS token.
|
||||
yaml_ANCHOR_TOKEN // An ANCHOR token.
|
||||
yaml_TAG_TOKEN // A TAG token.
|
||||
yaml_SCALAR_TOKEN // A SCALAR token.
|
||||
)
|
||||
|
||||
func (tt yaml_token_type_t) String() string {
|
||||
switch tt {
|
||||
case yaml_NO_TOKEN:
|
||||
return "yaml_NO_TOKEN"
|
||||
case yaml_STREAM_START_TOKEN:
|
||||
return "yaml_STREAM_START_TOKEN"
|
||||
case yaml_STREAM_END_TOKEN:
|
||||
return "yaml_STREAM_END_TOKEN"
|
||||
case yaml_VERSION_DIRECTIVE_TOKEN:
|
||||
return "yaml_VERSION_DIRECTIVE_TOKEN"
|
||||
case yaml_TAG_DIRECTIVE_TOKEN:
|
||||
return "yaml_TAG_DIRECTIVE_TOKEN"
|
||||
case yaml_DOCUMENT_START_TOKEN:
|
||||
return "yaml_DOCUMENT_START_TOKEN"
|
||||
case yaml_DOCUMENT_END_TOKEN:
|
||||
return "yaml_DOCUMENT_END_TOKEN"
|
||||
case yaml_BLOCK_SEQUENCE_START_TOKEN:
|
||||
return "yaml_BLOCK_SEQUENCE_START_TOKEN"
|
||||
case yaml_BLOCK_MAPPING_START_TOKEN:
|
||||
return "yaml_BLOCK_MAPPING_START_TOKEN"
|
||||
case yaml_BLOCK_END_TOKEN:
|
||||
return "yaml_BLOCK_END_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_START_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_START_TOKEN"
|
||||
case yaml_FLOW_SEQUENCE_END_TOKEN:
|
||||
return "yaml_FLOW_SEQUENCE_END_TOKEN"
|
||||
case yaml_FLOW_MAPPING_START_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_START_TOKEN"
|
||||
case yaml_FLOW_MAPPING_END_TOKEN:
|
||||
return "yaml_FLOW_MAPPING_END_TOKEN"
|
||||
case yaml_BLOCK_ENTRY_TOKEN:
|
||||
return "yaml_BLOCK_ENTRY_TOKEN"
|
||||
case yaml_FLOW_ENTRY_TOKEN:
|
||||
return "yaml_FLOW_ENTRY_TOKEN"
|
||||
case yaml_KEY_TOKEN:
|
||||
return "yaml_KEY_TOKEN"
|
||||
case yaml_VALUE_TOKEN:
|
||||
return "yaml_VALUE_TOKEN"
|
||||
case yaml_ALIAS_TOKEN:
|
||||
return "yaml_ALIAS_TOKEN"
|
||||
case yaml_ANCHOR_TOKEN:
|
||||
return "yaml_ANCHOR_TOKEN"
|
||||
case yaml_TAG_TOKEN:
|
||||
return "yaml_TAG_TOKEN"
|
||||
case yaml_SCALAR_TOKEN:
|
||||
return "yaml_SCALAR_TOKEN"
|
||||
}
|
||||
return "<unknown token>"
|
||||
}
|
||||
|
||||
// The token structure.
|
||||
type yaml_token_t struct {
|
||||
// The token type.
|
||||
typ yaml_token_type_t
|
||||
|
||||
// The start/end of the token.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The stream encoding (for yaml_STREAM_START_TOKEN).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The alias/anchor/scalar value or tag/tag directive handle
|
||||
// (for yaml_ALIAS_TOKEN, yaml_ANCHOR_TOKEN, yaml_SCALAR_TOKEN, yaml_TAG_TOKEN, yaml_TAG_DIRECTIVE_TOKEN).
|
||||
value []byte
|
||||
|
||||
// The tag suffix (for yaml_TAG_TOKEN).
|
||||
suffix []byte
|
||||
|
||||
// The tag directive prefix (for yaml_TAG_DIRECTIVE_TOKEN).
|
||||
prefix []byte
|
||||
|
||||
// The scalar style (for yaml_SCALAR_TOKEN).
|
||||
style yaml_scalar_style_t
|
||||
|
||||
// The version directive major/minor (for yaml_VERSION_DIRECTIVE_TOKEN).
|
||||
major, minor int8
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
type yaml_event_type_t int8
|
||||
|
||||
// Event types.
|
||||
const (
|
||||
// An empty event.
|
||||
yaml_NO_EVENT yaml_event_type_t = iota
|
||||
|
||||
yaml_STREAM_START_EVENT // A STREAM-START event.
|
||||
yaml_STREAM_END_EVENT // A STREAM-END event.
|
||||
yaml_DOCUMENT_START_EVENT // A DOCUMENT-START event.
|
||||
yaml_DOCUMENT_END_EVENT // A DOCUMENT-END event.
|
||||
yaml_ALIAS_EVENT // An ALIAS event.
|
||||
yaml_SCALAR_EVENT // A SCALAR event.
|
||||
yaml_SEQUENCE_START_EVENT // A SEQUENCE-START event.
|
||||
yaml_SEQUENCE_END_EVENT // A SEQUENCE-END event.
|
||||
yaml_MAPPING_START_EVENT // A MAPPING-START event.
|
||||
yaml_MAPPING_END_EVENT // A MAPPING-END event.
|
||||
)
|
||||
|
||||
var eventStrings = []string{
|
||||
yaml_NO_EVENT: "none",
|
||||
yaml_STREAM_START_EVENT: "stream start",
|
||||
yaml_STREAM_END_EVENT: "stream end",
|
||||
yaml_DOCUMENT_START_EVENT: "document start",
|
||||
yaml_DOCUMENT_END_EVENT: "document end",
|
||||
yaml_ALIAS_EVENT: "alias",
|
||||
yaml_SCALAR_EVENT: "scalar",
|
||||
yaml_SEQUENCE_START_EVENT: "sequence start",
|
||||
yaml_SEQUENCE_END_EVENT: "sequence end",
|
||||
yaml_MAPPING_START_EVENT: "mapping start",
|
||||
yaml_MAPPING_END_EVENT: "mapping end",
|
||||
}
|
||||
|
||||
func (e yaml_event_type_t) String() string {
|
||||
if e < 0 || int(e) >= len(eventStrings) {
|
||||
return fmt.Sprintf("unknown event %d", e)
|
||||
}
|
||||
return eventStrings[e]
|
||||
}
|
||||
|
||||
// The event structure.
|
||||
type yaml_event_t struct {
|
||||
|
||||
// The event type.
|
||||
typ yaml_event_type_t
|
||||
|
||||
// The start and end of the event.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
|
||||
// The document encoding (for yaml_STREAM_START_EVENT).
|
||||
encoding yaml_encoding_t
|
||||
|
||||
// The version directive (for yaml_DOCUMENT_START_EVENT).
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives (for yaml_DOCUMENT_START_EVENT).
|
||||
tag_directives []yaml_tag_directive_t
|
||||
|
||||
// The anchor (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_ALIAS_EVENT).
|
||||
anchor []byte
|
||||
|
||||
// The tag (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
tag []byte
|
||||
|
||||
// The scalar value (for yaml_SCALAR_EVENT).
|
||||
value []byte
|
||||
|
||||
// Is the document start/end indicator implicit, or the tag optional?
|
||||
// (for yaml_DOCUMENT_START_EVENT, yaml_DOCUMENT_END_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT, yaml_SCALAR_EVENT).
|
||||
implicit bool
|
||||
|
||||
// Is the tag optional for any non-plain style? (for yaml_SCALAR_EVENT).
|
||||
quoted_implicit bool
|
||||
|
||||
// The style (for yaml_SCALAR_EVENT, yaml_SEQUENCE_START_EVENT, yaml_MAPPING_START_EVENT).
|
||||
style yaml_style_t
|
||||
}
|
||||
|
||||
func (e *yaml_event_t) scalar_style() yaml_scalar_style_t { return yaml_scalar_style_t(e.style) }
|
||||
func (e *yaml_event_t) sequence_style() yaml_sequence_style_t { return yaml_sequence_style_t(e.style) }
|
||||
func (e *yaml_event_t) mapping_style() yaml_mapping_style_t { return yaml_mapping_style_t(e.style) }
|
||||
|
||||
// Nodes
|
||||
|
||||
const (
|
||||
yaml_NULL_TAG = "tag:yaml.org,2002:null" // The tag !!null with the only possible value: null.
|
||||
yaml_BOOL_TAG = "tag:yaml.org,2002:bool" // The tag !!bool with the values: true and false.
|
||||
yaml_STR_TAG = "tag:yaml.org,2002:str" // The tag !!str for string values.
|
||||
yaml_INT_TAG = "tag:yaml.org,2002:int" // The tag !!int for integer values.
|
||||
yaml_FLOAT_TAG = "tag:yaml.org,2002:float" // The tag !!float for float values.
|
||||
yaml_TIMESTAMP_TAG = "tag:yaml.org,2002:timestamp" // The tag !!timestamp for date and time values.
|
||||
|
||||
yaml_SEQ_TAG = "tag:yaml.org,2002:seq" // The tag !!seq is used to denote sequences.
|
||||
yaml_MAP_TAG = "tag:yaml.org,2002:map" // The tag !!map is used to denote mapping.
|
||||
|
||||
// Not in original libyaml.
|
||||
yaml_BINARY_TAG = "tag:yaml.org,2002:binary"
|
||||
yaml_MERGE_TAG = "tag:yaml.org,2002:merge"
|
||||
|
||||
yaml_DEFAULT_SCALAR_TAG = yaml_STR_TAG // The default scalar tag is !!str.
|
||||
yaml_DEFAULT_SEQUENCE_TAG = yaml_SEQ_TAG // The default sequence tag is !!seq.
|
||||
yaml_DEFAULT_MAPPING_TAG = yaml_MAP_TAG // The default mapping tag is !!map.
|
||||
)
|
||||
|
||||
type yaml_node_type_t int
|
||||
|
||||
// Node types.
|
||||
const (
|
||||
// An empty node.
|
||||
yaml_NO_NODE yaml_node_type_t = iota
|
||||
|
||||
yaml_SCALAR_NODE // A scalar node.
|
||||
yaml_SEQUENCE_NODE // A sequence node.
|
||||
yaml_MAPPING_NODE // A mapping node.
|
||||
)
|
||||
|
||||
// An element of a sequence node.
|
||||
type yaml_node_item_t int
|
||||
|
||||
// An element of a mapping node.
|
||||
type yaml_node_pair_t struct {
|
||||
key int // The key of the element.
|
||||
value int // The value of the element.
|
||||
}
|
||||
|
||||
// The node structure.
|
||||
type yaml_node_t struct {
|
||||
typ yaml_node_type_t // The node type.
|
||||
tag []byte // The node tag.
|
||||
|
||||
// The node data.
|
||||
|
||||
// The scalar parameters (for yaml_SCALAR_NODE).
|
||||
scalar struct {
|
||||
value []byte // The scalar value.
|
||||
length int // The length of the scalar value.
|
||||
style yaml_scalar_style_t // The scalar style.
|
||||
}
|
||||
|
||||
// The sequence parameters (for YAML_SEQUENCE_NODE).
|
||||
sequence struct {
|
||||
items_data []yaml_node_item_t // The stack of sequence items.
|
||||
style yaml_sequence_style_t // The sequence style.
|
||||
}
|
||||
|
||||
// The mapping parameters (for yaml_MAPPING_NODE).
|
||||
mapping struct {
|
||||
pairs_data []yaml_node_pair_t // The stack of mapping pairs (key, value).
|
||||
pairs_start *yaml_node_pair_t // The beginning of the stack.
|
||||
pairs_end *yaml_node_pair_t // The end of the stack.
|
||||
pairs_top *yaml_node_pair_t // The top of the stack.
|
||||
style yaml_mapping_style_t // The mapping style.
|
||||
}
|
||||
|
||||
start_mark yaml_mark_t // The beginning of the node.
|
||||
end_mark yaml_mark_t // The end of the node.
|
||||
|
||||
}
|
||||
|
||||
// The document structure.
|
||||
type yaml_document_t struct {
|
||||
|
||||
// The document nodes.
|
||||
nodes []yaml_node_t
|
||||
|
||||
// The version directive.
|
||||
version_directive *yaml_version_directive_t
|
||||
|
||||
// The list of tag directives.
|
||||
tag_directives_data []yaml_tag_directive_t
|
||||
tag_directives_start int // The beginning of the tag directives list.
|
||||
tag_directives_end int // The end of the tag directives list.
|
||||
|
||||
start_implicit int // Is the document start indicator implicit?
|
||||
end_implicit int // Is the document end indicator implicit?
|
||||
|
||||
// The start/end of the document.
|
||||
start_mark, end_mark yaml_mark_t
|
||||
}
|
||||
|
||||
// The prototype of a read handler.
|
||||
//
|
||||
// The read handler is called when the parser needs to read more bytes from the
|
||||
// source. The handler should write not more than size bytes to the buffer.
|
||||
// The number of written bytes should be set to the size_read variable.
|
||||
//
|
||||
// [in,out] data A pointer to an application data specified by
|
||||
// yaml_parser_set_input().
|
||||
// [out] buffer The buffer to write the data from the source.
|
||||
// [in] size The size of the buffer.
|
||||
// [out] size_read The actual number of bytes read from the source.
|
||||
//
|
||||
// On success, the handler should return 1. If the handler failed,
|
||||
// the returned value should be 0. On EOF, the handler should set the
|
||||
// size_read to 0 and return 1.
|
||||
type yaml_read_handler_t func(parser *yaml_parser_t, buffer []byte) (n int, err error)
|
||||
|
||||
// This structure holds information about a potential simple key.
|
||||
type yaml_simple_key_t struct {
|
||||
possible bool // Is a simple key possible?
|
||||
required bool // Is a simple key required?
|
||||
token_number int // The number of the token.
|
||||
mark yaml_mark_t // The position mark.
|
||||
}
|
||||
|
||||
// The states of the parser.
|
||||
type yaml_parser_state_t int
|
||||
|
||||
const (
|
||||
yaml_PARSE_STREAM_START_STATE yaml_parser_state_t = iota
|
||||
|
||||
yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE // Expect the beginning of an implicit document.
|
||||
yaml_PARSE_DOCUMENT_START_STATE // Expect DOCUMENT-START.
|
||||
yaml_PARSE_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_PARSE_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_PARSE_BLOCK_NODE_STATE // Expect a block node.
|
||||
yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE // Expect a block node or indentless sequence.
|
||||
yaml_PARSE_FLOW_NODE_STATE // Expect a flow node.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a block sequence.
|
||||
yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE // Expect an entry of a block sequence.
|
||||
yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE // Expect an entry of an indentless sequence.
|
||||
yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_PARSE_BLOCK_MAPPING_KEY_STATE // Expect a block mapping key.
|
||||
yaml_PARSE_BLOCK_MAPPING_VALUE_STATE // Expect a block mapping value.
|
||||
yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE // Expect the first entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE // Expect an entry of a flow sequence.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE // Expect a key of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE // Expect a value of an ordered mapping.
|
||||
yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE // Expect the and of an ordered mapping entry.
|
||||
yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE // Expect an empty value of a flow mapping.
|
||||
yaml_PARSE_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
func (ps yaml_parser_state_t) String() string {
|
||||
switch ps {
|
||||
case yaml_PARSE_STREAM_START_STATE:
|
||||
return "yaml_PARSE_STREAM_START_STATE"
|
||||
case yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_IMPLICIT_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_START_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_START_STATE"
|
||||
case yaml_PARSE_DOCUMENT_CONTENT_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_CONTENT_STATE"
|
||||
case yaml_PARSE_DOCUMENT_END_STATE:
|
||||
return "yaml_PARSE_DOCUMENT_END_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE:
|
||||
return "yaml_PARSE_BLOCK_NODE_OR_INDENTLESS_SEQUENCE_STATE"
|
||||
case yaml_PARSE_FLOW_NODE_STATE:
|
||||
return "yaml_PARSE_FLOW_NODE_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_BLOCK_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_INDENTLESS_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_BLOCK_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_BLOCK_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE:
|
||||
return "yaml_PARSE_FLOW_SEQUENCE_ENTRY_MAPPING_END_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_FIRST_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_KEY_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_KEY_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_VALUE_STATE"
|
||||
case yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE:
|
||||
return "yaml_PARSE_FLOW_MAPPING_EMPTY_VALUE_STATE"
|
||||
case yaml_PARSE_END_STATE:
|
||||
return "yaml_PARSE_END_STATE"
|
||||
}
|
||||
return "<unknown parser state>"
|
||||
}
|
||||
|
||||
// This structure holds aliases data.
|
||||
type yaml_alias_data_t struct {
|
||||
anchor []byte // The anchor.
|
||||
index int // The node id.
|
||||
mark yaml_mark_t // The anchor mark.
|
||||
}
|
||||
|
||||
// The parser structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the
|
||||
// yaml_parser_ family of functions.
|
||||
type yaml_parser_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
|
||||
problem string // Error description.
|
||||
|
||||
// The byte about which the problem occurred.
|
||||
problem_offset int
|
||||
problem_value int
|
||||
problem_mark yaml_mark_t
|
||||
|
||||
// The error context.
|
||||
context string
|
||||
context_mark yaml_mark_t
|
||||
|
||||
// Reader stuff
|
||||
|
||||
read_handler yaml_read_handler_t // Read handler.
|
||||
|
||||
input_reader io.Reader // File input data.
|
||||
input []byte // String input data.
|
||||
input_pos int
|
||||
|
||||
eof bool // EOF flag
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
unread int // The number of unread characters in the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The input encoding.
|
||||
|
||||
offset int // The offset of the current position (in bytes).
|
||||
mark yaml_mark_t // The mark of the current position.
|
||||
|
||||
// Scanner stuff
|
||||
|
||||
stream_start_produced bool // Have we started to scan the input stream?
|
||||
stream_end_produced bool // Have we reached the end of the input stream?
|
||||
|
||||
flow_level int // The number of unclosed '[' and '{' indicators.
|
||||
|
||||
tokens []yaml_token_t // The tokens queue.
|
||||
tokens_head int // The head of the tokens queue.
|
||||
tokens_parsed int // The number of tokens fetched from the queue.
|
||||
token_available bool // Does the tokens queue contain a token ready for dequeueing.
|
||||
|
||||
indent int // The current indentation level.
|
||||
indents []int // The indentation levels stack.
|
||||
|
||||
simple_key_allowed bool // May a simple key occur at the current position?
|
||||
simple_keys []yaml_simple_key_t // The stack of simple keys.
|
||||
|
||||
// Parser stuff
|
||||
|
||||
state yaml_parser_state_t // The current parser state.
|
||||
states []yaml_parser_state_t // The parser states stack.
|
||||
marks []yaml_mark_t // The stack of marks.
|
||||
tag_directives []yaml_tag_directive_t // The list of TAG directives.
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
aliases []yaml_alias_data_t // The alias data.
|
||||
|
||||
document *yaml_document_t // The currently parsed document.
|
||||
}
|
||||
|
||||
// Emitter Definitions
|
||||
|
||||
// The prototype of a write handler.
|
||||
//
|
||||
// The write handler is called when the emitter needs to flush the accumulated
|
||||
// characters to the output. The handler should write @a size bytes of the
|
||||
// @a buffer to the output.
|
||||
//
|
||||
// @param[in,out] data A pointer to an application data specified by
|
||||
// yaml_emitter_set_output().
|
||||
// @param[in] buffer The buffer with bytes to be written.
|
||||
// @param[in] size The size of the buffer.
|
||||
//
|
||||
// @returns On success, the handler should return @c 1. If the handler failed,
|
||||
// the returned value should be @c 0.
|
||||
//
|
||||
type yaml_write_handler_t func(emitter *yaml_emitter_t, buffer []byte) error
|
||||
|
||||
type yaml_emitter_state_t int
|
||||
|
||||
// The emitter states.
|
||||
const (
|
||||
// Expect STREAM-START.
|
||||
yaml_EMIT_STREAM_START_STATE yaml_emitter_state_t = iota
|
||||
|
||||
yaml_EMIT_FIRST_DOCUMENT_START_STATE // Expect the first DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_START_STATE // Expect DOCUMENT-START or STREAM-END.
|
||||
yaml_EMIT_DOCUMENT_CONTENT_STATE // Expect the content of a document.
|
||||
yaml_EMIT_DOCUMENT_END_STATE // Expect DOCUMENT-END.
|
||||
yaml_EMIT_FLOW_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a flow sequence.
|
||||
yaml_EMIT_FLOW_SEQUENCE_ITEM_STATE // Expect an item of a flow sequence.
|
||||
yaml_EMIT_FLOW_MAPPING_FIRST_KEY_STATE // Expect the first key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_KEY_STATE // Expect a key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a flow mapping.
|
||||
yaml_EMIT_FLOW_MAPPING_VALUE_STATE // Expect a value of a flow mapping.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_FIRST_ITEM_STATE // Expect the first item of a block sequence.
|
||||
yaml_EMIT_BLOCK_SEQUENCE_ITEM_STATE // Expect an item of a block sequence.
|
||||
yaml_EMIT_BLOCK_MAPPING_FIRST_KEY_STATE // Expect the first key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_KEY_STATE // Expect the key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_SIMPLE_VALUE_STATE // Expect a value for a simple key of a block mapping.
|
||||
yaml_EMIT_BLOCK_MAPPING_VALUE_STATE // Expect a value of a block mapping.
|
||||
yaml_EMIT_END_STATE // Expect nothing.
|
||||
)
|
||||
|
||||
// The emitter structure.
|
||||
//
|
||||
// All members are internal. Manage the structure using the @c yaml_emitter_
|
||||
// family of functions.
|
||||
type yaml_emitter_t struct {
|
||||
|
||||
// Error handling
|
||||
|
||||
error yaml_error_type_t // Error type.
|
||||
problem string // Error description.
|
||||
|
||||
// Writer stuff
|
||||
|
||||
write_handler yaml_write_handler_t // Write handler.
|
||||
|
||||
output_buffer *[]byte // String output data.
|
||||
output_writer io.Writer // File output data.
|
||||
|
||||
buffer []byte // The working buffer.
|
||||
buffer_pos int // The current position of the buffer.
|
||||
|
||||
raw_buffer []byte // The raw buffer.
|
||||
raw_buffer_pos int // The current position of the buffer.
|
||||
|
||||
encoding yaml_encoding_t // The stream encoding.
|
||||
|
||||
// Emitter stuff
|
||||
|
||||
canonical bool // If the output is in the canonical style?
|
||||
best_indent int // The number of indentation spaces.
|
||||
best_width int // The preferred width of the output lines.
|
||||
unicode bool // Allow unescaped non-ASCII characters?
|
||||
line_break yaml_break_t // The preferred line break.
|
||||
|
||||
state yaml_emitter_state_t // The current emitter state.
|
||||
states []yaml_emitter_state_t // The stack of states.
|
||||
|
||||
events []yaml_event_t // The event queue.
|
||||
events_head int // The head of the event queue.
|
||||
|
||||
indents []int // The stack of indentation levels.
|
||||
|
||||
tag_directives []yaml_tag_directive_t // The list of tag directives.
|
||||
|
||||
indent int // The current indentation level.
|
||||
|
||||
flow_level int // The current flow level.
|
||||
|
||||
root_context bool // Is it the document root context?
|
||||
sequence_context bool // Is it a sequence context?
|
||||
mapping_context bool // Is it a mapping context?
|
||||
simple_key_context bool // Is it a simple mapping key context?
|
||||
|
||||
line int // The current line.
|
||||
column int // The current column.
|
||||
whitespace bool // If the last character was a whitespace?
|
||||
indention bool // If the last character was an indentation character (' ', '-', '?', ':')?
|
||||
open_ended bool // If an explicit document end is required?
|
||||
|
||||
// Anchor analysis.
|
||||
anchor_data struct {
|
||||
anchor []byte // The anchor value.
|
||||
alias bool // Is it an alias?
|
||||
}
|
||||
|
||||
// Tag analysis.
|
||||
tag_data struct {
|
||||
handle []byte // The tag handle.
|
||||
suffix []byte // The tag suffix.
|
||||
}
|
||||
|
||||
// Scalar analysis.
|
||||
scalar_data struct {
|
||||
value []byte // The scalar value.
|
||||
multiline bool // Does the scalar contain line breaks?
|
||||
flow_plain_allowed bool // Can the scalar be expessed in the flow plain style?
|
||||
block_plain_allowed bool // Can the scalar be expressed in the block plain style?
|
||||
single_quoted_allowed bool // Can the scalar be expressed in the single quoted style?
|
||||
block_allowed bool // Can the scalar be expressed in the literal or folded styles?
|
||||
style yaml_scalar_style_t // The output style.
|
||||
}
|
||||
|
||||
// Dumper stuff
|
||||
|
||||
opened bool // If the stream was already opened?
|
||||
closed bool // If the stream was already closed?
|
||||
|
||||
// The information associated with the document nodes.
|
||||
anchors *struct {
|
||||
references int // The number of references.
|
||||
anchor int // The anchor id.
|
||||
serialized bool // If the node has been emitted?
|
||||
}
|
||||
|
||||
last_anchor_id int // The last assigned anchor id.
|
||||
|
||||
document *yaml_document_t // The currently emitted document.
|
||||
}
|
|
@ -1,173 +0,0 @@
|
|||
package yaml
|
||||
|
||||
const (
|
||||
// The size of the input raw buffer.
|
||||
input_raw_buffer_size = 512
|
||||
|
||||
// The size of the input buffer.
|
||||
// It should be possible to decode the whole raw buffer.
|
||||
input_buffer_size = input_raw_buffer_size * 3
|
||||
|
||||
// The size of the output buffer.
|
||||
output_buffer_size = 128
|
||||
|
||||
// The size of the output raw buffer.
|
||||
// It should be possible to encode the whole output buffer.
|
||||
output_raw_buffer_size = (output_buffer_size*2 + 2)
|
||||
|
||||
// The size of other stacks and queues.
|
||||
initial_stack_size = 16
|
||||
initial_queue_size = 16
|
||||
initial_string_size = 16
|
||||
)
|
||||
|
||||
// Check if the character at the specified position is an alphabetical
|
||||
// character, a digit, '_', or '-'.
|
||||
func is_alpha(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'Z' || b[i] >= 'a' && b[i] <= 'z' || b[i] == '_' || b[i] == '-'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a digit.
|
||||
func is_digit(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9'
|
||||
}
|
||||
|
||||
// Get the value of a digit.
|
||||
func as_digit(b []byte, i int) int {
|
||||
return int(b[i]) - '0'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a hex-digit.
|
||||
func is_hex(b []byte, i int) bool {
|
||||
return b[i] >= '0' && b[i] <= '9' || b[i] >= 'A' && b[i] <= 'F' || b[i] >= 'a' && b[i] <= 'f'
|
||||
}
|
||||
|
||||
// Get the value of a hex-digit.
|
||||
func as_hex(b []byte, i int) int {
|
||||
bi := b[i]
|
||||
if bi >= 'A' && bi <= 'F' {
|
||||
return int(bi) - 'A' + 10
|
||||
}
|
||||
if bi >= 'a' && bi <= 'f' {
|
||||
return int(bi) - 'a' + 10
|
||||
}
|
||||
return int(bi) - '0'
|
||||
}
|
||||
|
||||
// Check if the character is ASCII.
|
||||
func is_ascii(b []byte, i int) bool {
|
||||
return b[i] <= 0x7F
|
||||
}
|
||||
|
||||
// Check if the character at the start of the buffer can be printed unescaped.
|
||||
func is_printable(b []byte, i int) bool {
|
||||
return ((b[i] == 0x0A) || // . == #x0A
|
||||
(b[i] >= 0x20 && b[i] <= 0x7E) || // #x20 <= . <= #x7E
|
||||
(b[i] == 0xC2 && b[i+1] >= 0xA0) || // #0xA0 <= . <= #xD7FF
|
||||
(b[i] > 0xC2 && b[i] < 0xED) ||
|
||||
(b[i] == 0xED && b[i+1] < 0xA0) ||
|
||||
(b[i] == 0xEE) ||
|
||||
(b[i] == 0xEF && // #xE000 <= . <= #xFFFD
|
||||
!(b[i+1] == 0xBB && b[i+2] == 0xBF) && // && . != #xFEFF
|
||||
!(b[i+1] == 0xBF && (b[i+2] == 0xBE || b[i+2] == 0xBF))))
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is NUL.
|
||||
func is_z(b []byte, i int) bool {
|
||||
return b[i] == 0x00
|
||||
}
|
||||
|
||||
// Check if the beginning of the buffer is a BOM.
|
||||
func is_bom(b []byte, i int) bool {
|
||||
return b[0] == 0xEF && b[1] == 0xBB && b[2] == 0xBF
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is space.
|
||||
func is_space(b []byte, i int) bool {
|
||||
return b[i] == ' '
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is tab.
|
||||
func is_tab(b []byte, i int) bool {
|
||||
return b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is blank (space or tab).
|
||||
func is_blank(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_tab(b, i)
|
||||
return b[i] == ' ' || b[i] == '\t'
|
||||
}
|
||||
|
||||
// Check if the character at the specified position is a line break.
|
||||
func is_break(b []byte, i int) bool {
|
||||
return (b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9) // PS (#x2029)
|
||||
}
|
||||
|
||||
func is_crlf(b []byte, i int) bool {
|
||||
return b[i] == '\r' && b[i+1] == '\n'
|
||||
}
|
||||
|
||||
// Check if the character is a line break or NUL.
|
||||
func is_breakz(b []byte, i int) bool {
|
||||
//return is_break(b, i) || is_z(b, i)
|
||||
return ( // is_break:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
// is_z:
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, or NUL.
|
||||
func is_spacez(b []byte, i int) bool {
|
||||
//return is_space(b, i) || is_breakz(b, i)
|
||||
return ( // is_space:
|
||||
b[i] == ' ' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Check if the character is a line break, space, tab, or NUL.
|
||||
func is_blankz(b []byte, i int) bool {
|
||||
//return is_blank(b, i) || is_breakz(b, i)
|
||||
return ( // is_blank:
|
||||
b[i] == ' ' || b[i] == '\t' ||
|
||||
// is_breakz:
|
||||
b[i] == '\r' || // CR (#xD)
|
||||
b[i] == '\n' || // LF (#xA)
|
||||
b[i] == 0xC2 && b[i+1] == 0x85 || // NEL (#x85)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA8 || // LS (#x2028)
|
||||
b[i] == 0xE2 && b[i+1] == 0x80 && b[i+2] == 0xA9 || // PS (#x2029)
|
||||
b[i] == 0)
|
||||
}
|
||||
|
||||
// Determine the width of the character.
|
||||
func width(b byte) int {
|
||||
// Don't replace these by a switch without first
|
||||
// confirming that it is being inlined.
|
||||
if b&0x80 == 0x00 {
|
||||
return 1
|
||||
}
|
||||
if b&0xE0 == 0xC0 {
|
||||
return 2
|
||||
}
|
||||
if b&0xF0 == 0xE0 {
|
||||
return 3
|
||||
}
|
||||
if b&0xF8 == 0xF0 {
|
||||
return 4
|
||||
}
|
||||
return 0
|
||||
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
|
@ -0,0 +1,15 @@
|
|||
language: go
|
||||
go_import_path: github.com/pkg/errors
|
||||
go:
|
||||
- 1.4.x
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- tip
|
||||
|
||||
script:
|
||||
- go test -v ./...
|
|
@ -0,0 +1,23 @@
|
|||
Copyright (c) 2015, Dave Cheney <dave@cheney.net>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,52 @@
|
|||
# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge)
|
||||
|
||||
Package errors provides simple error handling primitives.
|
||||
|
||||
`go get github.com/pkg/errors`
|
||||
|
||||
The traditional error handling idiom in Go is roughly akin to
|
||||
```go
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error.
|
||||
|
||||
## Adding context to an error
|
||||
|
||||
The errors.Wrap function returns a new error that adds context to the original error. For example
|
||||
```go
|
||||
_, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "read failed")
|
||||
}
|
||||
```
|
||||
## Retrieving the cause of an error
|
||||
|
||||
Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`.
|
||||
```go
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
```
|
||||
`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example:
|
||||
```go
|
||||
switch err := errors.Cause(err).(type) {
|
||||
case *MyError:
|
||||
// handle specifically
|
||||
default:
|
||||
// unknown error
|
||||
}
|
||||
```
|
||||
|
||||
[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high.
|
||||
|
||||
Before proposing a change, please discuss your change by raising an issue.
|
||||
|
||||
## License
|
||||
|
||||
BSD-2-Clause
|
|
@ -0,0 +1,32 @@
|
|||
version: build-{build}.{branch}
|
||||
|
||||
clone_folder: C:\gopath\src\github.com\pkg\errors
|
||||
shallow_clone: true # for startup speed
|
||||
|
||||
environment:
|
||||
GOPATH: C:\gopath
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
# http://www.appveyor.com/docs/installed-software
|
||||
install:
|
||||
# some helpful output for debugging builds
|
||||
- go version
|
||||
- go env
|
||||
# pre-installed MinGW at C:\MinGW is 32bit only
|
||||
# but MSYS2 at C:\msys64 has mingw64
|
||||
- set PATH=C:\msys64\mingw64\bin;%PATH%
|
||||
- gcc --version
|
||||
- g++ --version
|
||||
|
||||
build_script:
|
||||
- go install -v ./...
|
||||
|
||||
test_script:
|
||||
- set PATH=C:\gopath\bin;%PATH%
|
||||
- go test -v ./...
|
||||
|
||||
#artifacts:
|
||||
# - path: '%GOPATH%\bin\*.exe'
|
||||
deploy: off
|
|
@ -0,0 +1,282 @@
|
|||
// Package errors provides simple error handling primitives.
|
||||
//
|
||||
// The traditional error handling idiom in Go is roughly akin to
|
||||
//
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// which when applied recursively up the call stack results in error reports
|
||||
// without context or debugging information. The errors package allows
|
||||
// programmers to add context to the failure path in their code in a way
|
||||
// that does not destroy the original value of the error.
|
||||
//
|
||||
// Adding context to an error
|
||||
//
|
||||
// The errors.Wrap function returns a new error that adds context to the
|
||||
// original error by recording a stack trace at the point Wrap is called,
|
||||
// together with the supplied message. For example
|
||||
//
|
||||
// _, err := ioutil.ReadAll(r)
|
||||
// if err != nil {
|
||||
// return errors.Wrap(err, "read failed")
|
||||
// }
|
||||
//
|
||||
// If additional control is required, the errors.WithStack and
|
||||
// errors.WithMessage functions destructure errors.Wrap into its component
|
||||
// operations: annotating an error with a stack trace and with a message,
|
||||
// respectively.
|
||||
//
|
||||
// Retrieving the cause of an error
|
||||
//
|
||||
// Using errors.Wrap constructs a stack of errors, adding context to the
|
||||
// preceding error. Depending on the nature of the error it may be necessary
|
||||
// to reverse the operation of errors.Wrap to retrieve the original error
|
||||
// for inspection. Any error value which implements this interface
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// can be inspected by errors.Cause. errors.Cause will recursively retrieve
|
||||
// the topmost error that does not implement causer, which is assumed to be
|
||||
// the original cause. For example:
|
||||
//
|
||||
// switch err := errors.Cause(err).(type) {
|
||||
// case *MyError:
|
||||
// // handle specifically
|
||||
// default:
|
||||
// // unknown error
|
||||
// }
|
||||
//
|
||||
// Although the causer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// Formatted printing of errors
|
||||
//
|
||||
// All error values returned from this package implement fmt.Formatter and can
|
||||
// be formatted by the fmt package. The following verbs are supported:
|
||||
//
|
||||
// %s print the error. If the error has a Cause it will be
|
||||
// printed recursively.
|
||||
// %v see %s
|
||||
// %+v extended format. Each Frame of the error's StackTrace will
|
||||
// be printed in detail.
|
||||
//
|
||||
// Retrieving the stack trace of an error or wrapper
|
||||
//
|
||||
// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are
|
||||
// invoked. This information can be retrieved with the following interface:
|
||||
//
|
||||
// type stackTracer interface {
|
||||
// StackTrace() errors.StackTrace
|
||||
// }
|
||||
//
|
||||
// The returned errors.StackTrace type is defined as
|
||||
//
|
||||
// type StackTrace []Frame
|
||||
//
|
||||
// The Frame type represents a call site in the stack trace. Frame supports
|
||||
// the fmt.Formatter interface that can be used for printing information about
|
||||
// the stack trace of this error. For example:
|
||||
//
|
||||
// if err, ok := err.(stackTracer); ok {
|
||||
// for _, f := range err.StackTrace() {
|
||||
// fmt.Printf("%+s:%d", f)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Although the stackTracer interface is not exported by this package, it is
|
||||
// considered a part of its stable public interface.
|
||||
//
|
||||
// See the documentation for Frame.Format for more details.
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// New returns an error with the supplied message.
|
||||
// New also records the stack trace at the point it was called.
|
||||
func New(message string) error {
|
||||
return &fundamental{
|
||||
msg: message,
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Errorf formats according to a format specifier and returns the string
|
||||
// as a value that satisfies error.
|
||||
// Errorf also records the stack trace at the point it was called.
|
||||
func Errorf(format string, args ...interface{}) error {
|
||||
return &fundamental{
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
stack: callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// fundamental is an error that has a message and a stack, but no caller.
|
||||
type fundamental struct {
|
||||
msg string
|
||||
*stack
|
||||
}
|
||||
|
||||
func (f *fundamental) Error() string { return f.msg }
|
||||
|
||||
func (f *fundamental) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
io.WriteString(s, f.msg)
|
||||
f.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, f.msg)
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", f.msg)
|
||||
}
|
||||
}
|
||||
|
||||
// WithStack annotates err with a stack trace at the point WithStack was called.
|
||||
// If err is nil, WithStack returns nil.
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
type withStack struct {
|
||||
error
|
||||
*stack
|
||||
}
|
||||
|
||||
func (w *withStack) Cause() error { return w.error }
|
||||
|
||||
func (w *withStack) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v", w.Cause())
|
||||
w.stack.Format(s, verb)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's':
|
||||
io.WriteString(s, w.Error())
|
||||
case 'q':
|
||||
fmt.Fprintf(s, "%q", w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap returns an error annotating err with a stack trace
|
||||
// at the point Wrap is called, and the supplied message.
|
||||
// If err is nil, Wrap returns nil.
|
||||
func Wrap(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapf returns an error annotating err with a stack trace
|
||||
// at the point Wrapf is called, and the format specifier.
|
||||
// If err is nil, Wrapf returns nil.
|
||||
func Wrapf(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
err = &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
return &withStack{
|
||||
err,
|
||||
callers(),
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessage annotates err with a new message.
|
||||
// If err is nil, WithMessage returns nil.
|
||||
func WithMessage(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: message,
|
||||
}
|
||||
}
|
||||
|
||||
// WithMessagef annotates err with the format specifier.
|
||||
// If err is nil, WithMessagef returns nil.
|
||||
func WithMessagef(err error, format string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return &withMessage{
|
||||
cause: err,
|
||||
msg: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
type withMessage struct {
|
||||
cause error
|
||||
msg string
|
||||
}
|
||||
|
||||
func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() }
|
||||
func (w *withMessage) Cause() error { return w.cause }
|
||||
|
||||
func (w *withMessage) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
if s.Flag('+') {
|
||||
fmt.Fprintf(s, "%+v\n", w.Cause())
|
||||
io.WriteString(s, w.msg)
|
||||
return
|
||||
}
|
||||
fallthrough
|
||||
case 's', 'q':
|
||||
io.WriteString(s, w.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Cause returns the underlying cause of the error, if possible.
|
||||
// An error value has a cause if it implements the following
|
||||
// interface:
|
||||
//
|
||||
// type causer interface {
|
||||
// Cause() error
|
||||
// }
|
||||
//
|
||||
// If the error does not implement Cause, the original error will
|
||||
// be returned. If the error is nil, nil will be returned without further
|
||||
// investigation.
|
||||
func Cause(err error) error {
|
||||
type causer interface {
|
||||
Cause() error
|
||||
}
|
||||
|
||||
for err != nil {
|
||||
cause, ok := err.(causer)
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
err = cause.Cause()
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Frame represents a program counter inside a stack frame.
|
||||
type Frame uintptr
|
||||
|
||||
// pc returns the program counter for this frame;
|
||||
// multiple frames may have the same PC value.
|
||||
func (f Frame) pc() uintptr { return uintptr(f) - 1 }
|
||||
|
||||
// file returns the full path to the file that contains the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) file() string {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return "unknown"
|
||||
}
|
||||
file, _ := fn.FileLine(f.pc())
|
||||
return file
|
||||
}
|
||||
|
||||
// line returns the line number of source code of the
|
||||
// function for this Frame's pc.
|
||||
func (f Frame) line() int {
|
||||
fn := runtime.FuncForPC(f.pc())
|
||||
if fn == nil {
|
||||
return 0
|
||||
}
|
||||
_, line := fn.FileLine(f.pc())
|
||||
return line
|
||||
}
|
||||
|
||||
// Format formats the frame according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s source file
|
||||
// %d source line
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+s function name and path of source file relative to the compile time
|
||||
// GOPATH separated by \n\t (<funcname>\n\t<path>)
|
||||
// %+v equivalent to %+s:%d
|
||||
func (f Frame) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 's':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
pc := f.pc()
|
||||
fn := runtime.FuncForPC(pc)
|
||||
if fn == nil {
|
||||
io.WriteString(s, "unknown")
|
||||
} else {
|
||||
file, _ := fn.FileLine(pc)
|
||||
fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file)
|
||||
}
|
||||
default:
|
||||
io.WriteString(s, path.Base(f.file()))
|
||||
}
|
||||
case 'd':
|
||||
fmt.Fprintf(s, "%d", f.line())
|
||||
case 'n':
|
||||
name := runtime.FuncForPC(f.pc()).Name()
|
||||
io.WriteString(s, funcname(name))
|
||||
case 'v':
|
||||
f.Format(s, 's')
|
||||
io.WriteString(s, ":")
|
||||
f.Format(s, 'd')
|
||||
}
|
||||
}
|
||||
|
||||
// StackTrace is stack of Frames from innermost (newest) to outermost (oldest).
|
||||
type StackTrace []Frame
|
||||
|
||||
// Format formats the stack of Frames according to the fmt.Formatter interface.
|
||||
//
|
||||
// %s lists source files for each Frame in the stack
|
||||
// %v lists the source file and line number for each Frame in the stack
|
||||
//
|
||||
// Format accepts flags that alter the printing of some verbs, as follows:
|
||||
//
|
||||
// %+v Prints filename, function, and line number for each Frame in the stack.
|
||||
func (st StackTrace) Format(s fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case s.Flag('+'):
|
||||
for _, f := range st {
|
||||
fmt.Fprintf(s, "\n%+v", f)
|
||||
}
|
||||
case s.Flag('#'):
|
||||
fmt.Fprintf(s, "%#v", []Frame(st))
|
||||
default:
|
||||
fmt.Fprintf(s, "%v", []Frame(st))
|
||||
}
|
||||
case 's':
|
||||
fmt.Fprintf(s, "%s", []Frame(st))
|
||||
}
|
||||
}
|
||||
|
||||
// stack represents a stack of program counters.
|
||||
type stack []uintptr
|
||||
|
||||
func (s *stack) Format(st fmt.State, verb rune) {
|
||||
switch verb {
|
||||
case 'v':
|
||||
switch {
|
||||
case st.Flag('+'):
|
||||
for _, pc := range *s {
|
||||
f := Frame(pc)
|
||||
fmt.Fprintf(st, "\n%+v", f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stack) StackTrace() StackTrace {
|
||||
f := make([]Frame, len(*s))
|
||||
for i := 0; i < len(f); i++ {
|
||||
f[i] = Frame((*s)[i])
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func callers() *stack {
|
||||
const depth = 32
|
||||
var pcs [depth]uintptr
|
||||
n := runtime.Callers(3, pcs[:])
|
||||
var st stack = pcs[0:n]
|
||||
return &st
|
||||
}
|
||||
|
||||
// funcname removes the path prefix component of a function's name reported by func.Name().
|
||||
func funcname(name string) string {
|
||||
i := strings.LastIndex(name, "/")
|
||||
name = name[i+1:]
|
||||
i = strings.Index(name, ".")
|
||||
return name[i+1:]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- 1.6
|
||||
- 1.7
|
||||
- 1.8
|
||||
- 1.9
|
||||
- tip
|
||||
matrix:
|
||||
allow_failures:
|
||||
- go: tip
|
||||
fast_finish: true
|
||||
before_install:
|
||||
- go get github.com/mattn/goveralls
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
script:
|
||||
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||
notifications:
|
||||
email: false
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2017 Seth Vargo <seth@sethvargo.com>
|
||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
|
@ -0,0 +1,65 @@
|
|||
# UUID package for Go language
|
||||
|
||||
[![Build Status](https://travis-ci.org/satori/go.uuid.png?branch=master)](https://travis-ci.org/satori/go.uuid)
|
||||
[![Coverage Status](https://coveralls.io/repos/github/satori/go.uuid/badge.svg?branch=master)](https://coveralls.io/github/satori/go.uuid)
|
||||
[![GoDoc](http://godoc.org/github.com/satori/go.uuid?status.png)](http://godoc.org/github.com/satori/go.uuid)
|
||||
|
||||
This package provides pure Go implementation of Universally Unique Identifier (UUID). Supported both creation and parsing of UUIDs.
|
||||
|
||||
With 100% test coverage and benchmarks out of box.
|
||||
|
||||
Supported versions:
|
||||
* Version 1, based on timestamp and MAC address (RFC 4122)
|
||||
* Version 2, based on timestamp, MAC address and POSIX UID/GID (DCE 1.1)
|
||||
* Version 3, based on MD5 hashing (RFC 4122)
|
||||
* Version 4, based on random numbers (RFC 4122)
|
||||
* Version 5, based on SHA-1 hashing (RFC 4122)
|
||||
|
||||
## Installation
|
||||
|
||||
Use the `go` command:
|
||||
|
||||
$ go get github.com/satori/go.uuid
|
||||
|
||||
## Requirements
|
||||
|
||||
UUID package requires Go >= 1.2.
|
||||
|
||||
## Example
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Creating UUID Version 4
|
||||
u1 := uuid.NewV4()
|
||||
fmt.Printf("UUIDv4: %s\n", u1)
|
||||
|
||||
// Parsing UUID from string input
|
||||
u2, err := uuid.FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
||||
if err != nil {
|
||||
fmt.Printf("Something gone wrong: %s", err)
|
||||
}
|
||||
fmt.Printf("Successfully parsed: %s", u2)
|
||||
}
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
[Documentation](http://godoc.org/github.com/satori/go.uuid) is hosted at GoDoc project.
|
||||
|
||||
## Links
|
||||
* [RFC 4122](http://tools.ietf.org/html/rfc4122)
|
||||
* [DCE 1.1: Authentication and Security Services](http://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#tagcjh_08_02_01_01)
|
||||
|
||||
## Copyright
|
||||
|
||||
Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>.
|
||||
|
||||
UUID package released under MIT License.
|
||||
See [LICENSE](https://github.com/satori/go.uuid/blob/master/LICENSE) for details.
|
|
@ -0,0 +1,206 @@
|
|||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// FromBytes returns UUID converted from raw byte slice input.
|
||||
// It will return error if the slice isn't 16 bytes long.
|
||||
func FromBytes(input []byte) (u UUID, err error) {
|
||||
err = u.UnmarshalBinary(input)
|
||||
return
|
||||
}
|
||||
|
||||
// FromBytesOrNil returns UUID converted from raw byte slice input.
|
||||
// Same behavior as FromBytes, but returns a Nil UUID on error.
|
||||
func FromBytesOrNil(input []byte) UUID {
|
||||
uuid, err := FromBytes(input)
|
||||
if err != nil {
|
||||
return Nil
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// FromString returns UUID parsed from string input.
|
||||
// Input is expected in a form accepted by UnmarshalText.
|
||||
func FromString(input string) (u UUID, err error) {
|
||||
err = u.UnmarshalText([]byte(input))
|
||||
return
|
||||
}
|
||||
|
||||
// FromStringOrNil returns UUID parsed from string input.
|
||||
// Same behavior as FromString, but returns a Nil UUID on error.
|
||||
func FromStringOrNil(input string) UUID {
|
||||
uuid, err := FromString(input)
|
||||
if err != nil {
|
||||
return Nil
|
||||
}
|
||||
return uuid
|
||||
}
|
||||
|
||||
// MarshalText implements the encoding.TextMarshaler interface.
|
||||
// The encoding is the same as returned by String.
|
||||
func (u UUID) MarshalText() (text []byte, err error) {
|
||||
text = []byte(u.String())
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalText implements the encoding.TextUnmarshaler interface.
|
||||
// Following formats are supported:
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}",
|
||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
||||
// "6ba7b8109dad11d180b400c04fd430c8"
|
||||
// ABNF for supported UUID text representation follows:
|
||||
// uuid := canonical | hashlike | braced | urn
|
||||
// plain := canonical | hashlike
|
||||
// canonical := 4hexoct '-' 2hexoct '-' 2hexoct '-' 6hexoct
|
||||
// hashlike := 12hexoct
|
||||
// braced := '{' plain '}'
|
||||
// urn := URN ':' UUID-NID ':' plain
|
||||
// URN := 'urn'
|
||||
// UUID-NID := 'uuid'
|
||||
// 12hexoct := 6hexoct 6hexoct
|
||||
// 6hexoct := 4hexoct 2hexoct
|
||||
// 4hexoct := 2hexoct 2hexoct
|
||||
// 2hexoct := hexoct hexoct
|
||||
// hexoct := hexdig hexdig
|
||||
// hexdig := '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' |
|
||||
// 'a' | 'b' | 'c' | 'd' | 'e' | 'f' |
|
||||
// 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
|
||||
func (u *UUID) UnmarshalText(text []byte) (err error) {
|
||||
switch len(text) {
|
||||
case 32:
|
||||
return u.decodeHashLike(text)
|
||||
case 36:
|
||||
return u.decodeCanonical(text)
|
||||
case 38:
|
||||
return u.decodeBraced(text)
|
||||
case 41:
|
||||
fallthrough
|
||||
case 45:
|
||||
return u.decodeURN(text)
|
||||
default:
|
||||
return fmt.Errorf("uuid: incorrect UUID length: %s", text)
|
||||
}
|
||||
}
|
||||
|
||||
// decodeCanonical decodes UUID string in format
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8".
|
||||
func (u *UUID) decodeCanonical(t []byte) (err error) {
|
||||
if t[8] != '-' || t[13] != '-' || t[18] != '-' || t[23] != '-' {
|
||||
return fmt.Errorf("uuid: incorrect UUID format %s", t)
|
||||
}
|
||||
|
||||
src := t[:]
|
||||
dst := u[:]
|
||||
|
||||
for i, byteGroup := range byteGroups {
|
||||
if i > 0 {
|
||||
src = src[1:] // skip dash
|
||||
}
|
||||
_, err = hex.Decode(dst[:byteGroup/2], src[:byteGroup])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
src = src[byteGroup:]
|
||||
dst = dst[byteGroup/2:]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// decodeHashLike decodes UUID string in format
|
||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodeHashLike(t []byte) (err error) {
|
||||
src := t[:]
|
||||
dst := u[:]
|
||||
|
||||
if _, err = hex.Decode(dst, src); err != nil {
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// decodeBraced decodes UUID string in format
|
||||
// "{6ba7b810-9dad-11d1-80b4-00c04fd430c8}" or in format
|
||||
// "{6ba7b8109dad11d180b400c04fd430c8}".
|
||||
func (u *UUID) decodeBraced(t []byte) (err error) {
|
||||
l := len(t)
|
||||
|
||||
if t[0] != '{' || t[l-1] != '}' {
|
||||
return fmt.Errorf("uuid: incorrect UUID format %s", t)
|
||||
}
|
||||
|
||||
return u.decodePlain(t[1 : l-1])
|
||||
}
|
||||
|
||||
// decodeURN decodes UUID string in format
|
||||
// "urn:uuid:6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in format
|
||||
// "urn:uuid:6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodeURN(t []byte) (err error) {
|
||||
total := len(t)
|
||||
|
||||
urn_uuid_prefix := t[:9]
|
||||
|
||||
if !bytes.Equal(urn_uuid_prefix, urnPrefix) {
|
||||
return fmt.Errorf("uuid: incorrect UUID format: %s", t)
|
||||
}
|
||||
|
||||
return u.decodePlain(t[9:total])
|
||||
}
|
||||
|
||||
// decodePlain decodes UUID string in canonical format
|
||||
// "6ba7b810-9dad-11d1-80b4-00c04fd430c8" or in hash-like format
|
||||
// "6ba7b8109dad11d180b400c04fd430c8".
|
||||
func (u *UUID) decodePlain(t []byte) (err error) {
|
||||
switch len(t) {
|
||||
case 32:
|
||||
return u.decodeHashLike(t)
|
||||
case 36:
|
||||
return u.decodeCanonical(t)
|
||||
default:
|
||||
return fmt.Errorf("uuid: incorrrect UUID length: %s", t)
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalBinary implements the encoding.BinaryMarshaler interface.
|
||||
func (u UUID) MarshalBinary() (data []byte, err error) {
|
||||
data = u.Bytes()
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
|
||||
// It will return error if the slice isn't 16 bytes long.
|
||||
func (u *UUID) UnmarshalBinary(data []byte) (err error) {
|
||||
if len(data) != Size {
|
||||
err = fmt.Errorf("uuid: UUID must be exactly 16 bytes long, got %d bytes", len(data))
|
||||
return
|
||||
}
|
||||
copy(u[:], data)
|
||||
|
||||
return
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"hash"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Difference in 100-nanosecond intervals between
|
||||
// UUID epoch (October 15, 1582) and Unix epoch (January 1, 1970).
|
||||
const epochStart = 122192928000000000
|
||||
|
||||
var (
|
||||
global = newDefaultGenerator()
|
||||
|
||||
epochFunc = unixTimeFunc
|
||||
posixUID = uint32(os.Getuid())
|
||||
posixGID = uint32(os.Getgid())
|
||||
)
|
||||
|
||||
// NewV1 returns UUID based on current timestamp and MAC address.
|
||||
func NewV1() UUID {
|
||||
return global.NewV1()
|
||||
}
|
||||
|
||||
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
|
||||
func NewV2(domain byte) UUID {
|
||||
return global.NewV2(domain)
|
||||
}
|
||||
|
||||
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
|
||||
func NewV3(ns UUID, name string) UUID {
|
||||
return global.NewV3(ns, name)
|
||||
}
|
||||
|
||||
// NewV4 returns random generated UUID.
|
||||
func NewV4() UUID {
|
||||
return global.NewV4()
|
||||
}
|
||||
|
||||
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
|
||||
func NewV5(ns UUID, name string) UUID {
|
||||
return global.NewV5(ns, name)
|
||||
}
|
||||
|
||||
// Generator provides interface for generating UUIDs.
|
||||
type Generator interface {
|
||||
NewV1() UUID
|
||||
NewV2(domain byte) UUID
|
||||
NewV3(ns UUID, name string) UUID
|
||||
NewV4() UUID
|
||||
NewV5(ns UUID, name string) UUID
|
||||
}
|
||||
|
||||
// Default generator implementation.
|
||||
type generator struct {
|
||||
storageOnce sync.Once
|
||||
storageMutex sync.Mutex
|
||||
|
||||
lastTime uint64
|
||||
clockSequence uint16
|
||||
hardwareAddr [6]byte
|
||||
}
|
||||
|
||||
func newDefaultGenerator() Generator {
|
||||
return &generator{}
|
||||
}
|
||||
|
||||
// NewV1 returns UUID based on current timestamp and MAC address.
|
||||
func (g *generator) NewV1() UUID {
|
||||
u := UUID{}
|
||||
|
||||
timeNow, clockSeq, hardwareAddr := g.getStorage()
|
||||
|
||||
binary.BigEndian.PutUint32(u[0:], uint32(timeNow))
|
||||
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
|
||||
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
|
||||
binary.BigEndian.PutUint16(u[8:], clockSeq)
|
||||
|
||||
copy(u[10:], hardwareAddr)
|
||||
|
||||
u.SetVersion(V1)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV2 returns DCE Security UUID based on POSIX UID/GID.
|
||||
func (g *generator) NewV2(domain byte) UUID {
|
||||
u := UUID{}
|
||||
|
||||
timeNow, clockSeq, hardwareAddr := g.getStorage()
|
||||
|
||||
switch domain {
|
||||
case DomainPerson:
|
||||
binary.BigEndian.PutUint32(u[0:], posixUID)
|
||||
case DomainGroup:
|
||||
binary.BigEndian.PutUint32(u[0:], posixGID)
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(u[4:], uint16(timeNow>>32))
|
||||
binary.BigEndian.PutUint16(u[6:], uint16(timeNow>>48))
|
||||
binary.BigEndian.PutUint16(u[8:], clockSeq)
|
||||
u[9] = domain
|
||||
|
||||
copy(u[10:], hardwareAddr)
|
||||
|
||||
u.SetVersion(V2)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV3 returns UUID based on MD5 hash of namespace UUID and name.
|
||||
func (g *generator) NewV3(ns UUID, name string) UUID {
|
||||
u := newFromHash(md5.New(), ns, name)
|
||||
u.SetVersion(V3)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV4 returns random generated UUID.
|
||||
func (g *generator) NewV4() UUID {
|
||||
u := UUID{}
|
||||
g.safeRandom(u[:])
|
||||
u.SetVersion(V4)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
// NewV5 returns UUID based on SHA-1 hash of namespace UUID and name.
|
||||
func (g *generator) NewV5(ns UUID, name string) UUID {
|
||||
u := newFromHash(sha1.New(), ns, name)
|
||||
u.SetVersion(V5)
|
||||
u.SetVariant(VariantRFC4122)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
func (g *generator) initStorage() {
|
||||
g.initClockSequence()
|
||||
g.initHardwareAddr()
|
||||
}
|
||||
|
||||
func (g *generator) initClockSequence() {
|
||||
buf := make([]byte, 2)
|
||||
g.safeRandom(buf)
|
||||
g.clockSequence = binary.BigEndian.Uint16(buf)
|
||||
}
|
||||
|
||||
func (g *generator) initHardwareAddr() {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err == nil {
|
||||
for _, iface := range interfaces {
|
||||
if len(iface.HardwareAddr) >= 6 {
|
||||
copy(g.hardwareAddr[:], iface.HardwareAddr)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize hardwareAddr randomly in case
|
||||
// of real network interfaces absence
|
||||
g.safeRandom(g.hardwareAddr[:])
|
||||
|
||||
// Set multicast bit as recommended in RFC 4122
|
||||
g.hardwareAddr[0] |= 0x01
|
||||
}
|
||||
|
||||
func (g *generator) safeRandom(dest []byte) {
|
||||
if _, err := rand.Read(dest); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Returns UUID v1/v2 storage state.
|
||||
// Returns epoch timestamp, clock sequence, and hardware address.
|
||||
func (g *generator) getStorage() (uint64, uint16, []byte) {
|
||||
g.storageOnce.Do(g.initStorage)
|
||||
|
||||
g.storageMutex.Lock()
|
||||
defer g.storageMutex.Unlock()
|
||||
|
||||
timeNow := epochFunc()
|
||||
// Clock changed backwards since last UUID generation.
|
||||
// Should increase clock sequence.
|
||||
if timeNow <= g.lastTime {
|
||||
g.clockSequence++
|
||||
}
|
||||
g.lastTime = timeNow
|
||||
|
||||
return timeNow, g.clockSequence, g.hardwareAddr[:]
|
||||
}
|
||||
|
||||
// Returns difference in 100-nanosecond intervals between
|
||||
// UUID epoch (October 15, 1582) and current time.
|
||||
// This is default epoch calculation function.
|
||||
func unixTimeFunc() uint64 {
|
||||
return epochStart + uint64(time.Now().UnixNano()/100)
|
||||
}
|
||||
|
||||
// Returns UUID based on hashing of namespace UUID and name.
|
||||
func newFromHash(h hash.Hash, ns UUID, name string) UUID {
|
||||
u := UUID{}
|
||||
h.Write(ns[:])
|
||||
h.Write([]byte(name))
|
||||
copy(u[:], h.Sum(nil))
|
||||
|
||||
return u
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (u UUID) Value() (driver.Value, error) {
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
// A 16-byte slice is handled by UnmarshalBinary, while
|
||||
// a longer byte slice or a string is handled by UnmarshalText.
|
||||
func (u *UUID) Scan(src interface{}) error {
|
||||
switch src := src.(type) {
|
||||
case []byte:
|
||||
if len(src) == Size {
|
||||
return u.UnmarshalBinary(src)
|
||||
}
|
||||
return u.UnmarshalText(src)
|
||||
|
||||
case string:
|
||||
return u.UnmarshalText([]byte(src))
|
||||
}
|
||||
|
||||
return fmt.Errorf("uuid: cannot convert %T to UUID", src)
|
||||
}
|
||||
|
||||
// NullUUID can be used with the standard sql package to represent a
|
||||
// UUID value that can be NULL in the database
|
||||
type NullUUID struct {
|
||||
UUID UUID
|
||||
Valid bool
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface.
|
||||
func (u NullUUID) Value() (driver.Value, error) {
|
||||
if !u.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
// Delegate to UUID Value function
|
||||
return u.UUID.Value()
|
||||
}
|
||||
|
||||
// Scan implements the sql.Scanner interface.
|
||||
func (u *NullUUID) Scan(src interface{}) error {
|
||||
if src == nil {
|
||||
u.UUID, u.Valid = Nil, false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delegate to UUID Scan function
|
||||
u.Valid = true
|
||||
return u.UUID.Scan(src)
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
// Copyright (C) 2013-2018 by Maxim Bublis <b@codemonkey.ru>
|
||||
//
|
||||
// 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.
|
||||
|
||||
// Package uuid provides implementation of Universally Unique Identifier (UUID).
|
||||
// Supported versions are 1, 3, 4 and 5 (as specified in RFC 4122) and
|
||||
// version 2 (as specified in DCE 1.1).
|
||||
package uuid
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
// Size of a UUID in bytes.
|
||||
const Size = 16
|
||||
|
||||
// UUID representation compliant with specification
|
||||
// described in RFC 4122.
|
||||
type UUID [Size]byte
|
||||
|
||||
// UUID versions
|
||||
const (
|
||||
_ byte = iota
|
||||
V1
|
||||
V2
|
||||
V3
|
||||
V4
|
||||
V5
|
||||
)
|
||||
|
||||
// UUID layout variants.
|
||||
const (
|
||||
VariantNCS byte = iota
|
||||
VariantRFC4122
|
||||
VariantMicrosoft
|
||||
VariantFuture
|
||||
)
|
||||
|
||||
// UUID DCE domains.
|
||||
const (
|
||||
DomainPerson = iota
|
||||
DomainGroup
|
||||
DomainOrg
|
||||
)
|
||||
|
||||
// String parse helpers.
|
||||
var (
|
||||
urnPrefix = []byte("urn:uuid:")
|
||||
byteGroups = []int{8, 4, 4, 4, 12}
|
||||
)
|
||||
|
||||
// Nil is special form of UUID that is specified to have all
|
||||
// 128 bits set to zero.
|
||||
var Nil = UUID{}
|
||||
|
||||
// Predefined namespace UUIDs.
|
||||
var (
|
||||
NamespaceDNS = Must(FromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceURL = Must(FromString("6ba7b811-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceOID = Must(FromString("6ba7b812-9dad-11d1-80b4-00c04fd430c8"))
|
||||
NamespaceX500 = Must(FromString("6ba7b814-9dad-11d1-80b4-00c04fd430c8"))
|
||||
)
|
||||
|
||||
// Equal returns true if u1 and u2 equals, otherwise returns false.
|
||||
func Equal(u1 UUID, u2 UUID) bool {
|
||||
return bytes.Equal(u1[:], u2[:])
|
||||
}
|
||||
|
||||
// Version returns algorithm version used to generate UUID.
|
||||
func (u UUID) Version() byte {
|
||||
return u[6] >> 4
|
||||
}
|
||||
|
||||
// Variant returns UUID layout variant.
|
||||
func (u UUID) Variant() byte {
|
||||
switch {
|
||||
case (u[8] >> 7) == 0x00:
|
||||
return VariantNCS
|
||||
case (u[8] >> 6) == 0x02:
|
||||
return VariantRFC4122
|
||||
case (u[8] >> 5) == 0x06:
|
||||
return VariantMicrosoft
|
||||
case (u[8] >> 5) == 0x07:
|
||||
fallthrough
|
||||
default:
|
||||
return VariantFuture
|
||||
}
|
||||
}
|
||||
|
||||
// Bytes returns bytes slice representation of UUID.
|
||||
func (u UUID) Bytes() []byte {
|
||||
return u[:]
|
||||
}
|
||||
|
||||
// Returns canonical string representation of UUID:
|
||||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.
|
||||
func (u UUID) String() string {
|
||||
buf := make([]byte, 36)
|
||||
|
||||
hex.Encode(buf[0:8], u[0:4])
|
||||
buf[8] = '-'
|
||||
hex.Encode(buf[9:13], u[4:6])
|
||||
buf[13] = '-'
|
||||
hex.Encode(buf[14:18], u[6:8])
|
||||
buf[18] = '-'
|
||||
hex.Encode(buf[19:23], u[8:10])
|
||||
buf[23] = '-'
|
||||
hex.Encode(buf[24:], u[10:])
|
||||
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
// SetVersion sets version bits.
|
||||
func (u *UUID) SetVersion(v byte) {
|
||||
u[6] = (u[6] & 0x0f) | (v << 4)
|
||||
}
|
||||
|
||||
// SetVariant sets variant bits.
|
||||
func (u *UUID) SetVariant(v byte) {
|
||||
switch v {
|
||||
case VariantNCS:
|
||||
u[8] = (u[8]&(0xff>>1) | (0x00 << 7))
|
||||
case VariantRFC4122:
|
||||
u[8] = (u[8]&(0xff>>2) | (0x02 << 6))
|
||||
case VariantMicrosoft:
|
||||
u[8] = (u[8]&(0xff>>3) | (0x06 << 5))
|
||||
case VariantFuture:
|
||||
fallthrough
|
||||
default:
|
||||
u[8] = (u[8]&(0xff>>3) | (0x07 << 5))
|
||||
}
|
||||
}
|
||||
|
||||
// Must is a helper that wraps a call to a function returning (UUID, error)
|
||||
// and panics if the error is non-nil. It is intended for use in variable
|
||||
// initializations such as
|
||||
// var packageUUID = uuid.Must(uuid.FromString("123e4567-e89b-12d3-a456-426655440000"));
|
||||
func Must(u UUID, err error) UUID {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return u
|
||||
}
|
|
@ -1,242 +0,0 @@
|
|||
// Package password provides a library for generating high-entropy random
|
||||
// password strings via the crypto/rand package.
|
||||
//
|
||||
// res, err := Generate(64, 10, 10, false, false)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// log.Printf(res)
|
||||
//
|
||||
// Most functions are safe for concurrent use.
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// LowerLetters is the list of lowercase letters.
|
||||
LowerLetters = "abcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
// UpperLetters is the list of uppercase letters.
|
||||
UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
// Digits is the list of permitted digits.
|
||||
Digits = "0123456789"
|
||||
|
||||
// Symbols is the list of symbols.
|
||||
Symbols = "~!@#$%^&*()_+`-={}|[]\\:\"<>?,./"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrExceedsTotalLength is the error returned with the number of digits and
|
||||
// symbols is greater than the total length.
|
||||
ErrExceedsTotalLength = errors.New("number of digits and symbols must be less than total length")
|
||||
|
||||
// ErrLettersExceedsAvailable is the error returned with the number of letters
|
||||
// exceeds the number of available letters and repeats are not allowed.
|
||||
ErrLettersExceedsAvailable = errors.New("number of letters exceeds available letters and repeats are not allowed")
|
||||
|
||||
// ErrDigitsExceedsAvailable is the error returned with the number of digits
|
||||
// exceeds the number of available digits and repeats are not allowed.
|
||||
ErrDigitsExceedsAvailable = errors.New("number of digits exceeds available digits and repeats are not allowed")
|
||||
|
||||
// ErrSymbolsExceedsAvailable is the error returned with the number of symbols
|
||||
// exceeds the number of available symbols and repeats are not allowed.
|
||||
ErrSymbolsExceedsAvailable = errors.New("number of symbols exceeds available symbols and repeats are not allowed")
|
||||
)
|
||||
|
||||
// Generator is the stateful generator which can be used to customize the list
|
||||
// of letters, digits, and/or symbols.
|
||||
type Generator struct {
|
||||
lowerLetters string
|
||||
upperLetters string
|
||||
digits string
|
||||
symbols string
|
||||
}
|
||||
|
||||
// GeneratorInput is used as input to the NewGenerator function.
|
||||
type GeneratorInput struct {
|
||||
LowerLetters string
|
||||
UpperLetters string
|
||||
Digits string
|
||||
Symbols string
|
||||
}
|
||||
|
||||
// NewGenerator creates a new Generator from the specified configuration. If no
|
||||
// input is given, all the default values are used. This function is safe for
|
||||
// concurrent use.
|
||||
func NewGenerator(i *GeneratorInput) (*Generator, error) {
|
||||
if i == nil {
|
||||
i = new(GeneratorInput)
|
||||
}
|
||||
|
||||
g := &Generator{
|
||||
lowerLetters: i.LowerLetters,
|
||||
upperLetters: i.UpperLetters,
|
||||
digits: i.Digits,
|
||||
symbols: i.Symbols,
|
||||
}
|
||||
|
||||
if g.lowerLetters == "" {
|
||||
g.lowerLetters = LowerLetters
|
||||
}
|
||||
|
||||
if g.upperLetters == "" {
|
||||
g.upperLetters = UpperLetters
|
||||
}
|
||||
|
||||
if g.digits == "" {
|
||||
g.digits = Digits
|
||||
}
|
||||
|
||||
if g.symbols == "" {
|
||||
g.symbols = Symbols
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
|
||||
// Generate generates a password with the given requirements. length is the
|
||||
// total number of characters in the password. numDigits is the number of digits
|
||||
// to include in the result. numSymbols is the number of symbols to include in
|
||||
// the result. noUpper excludes uppercase letters from the results. allowRepeat
|
||||
// allows characters to repeat.
|
||||
//
|
||||
// The algorithm is fast, but it's not designed to be performant; it favors
|
||||
// entropy over speed. This function is safe for concurrent use.
|
||||
func (g *Generator) Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||
letters := g.lowerLetters
|
||||
if !noUpper {
|
||||
letters += g.upperLetters
|
||||
}
|
||||
|
||||
chars := length - numDigits - numSymbols
|
||||
if chars < 0 {
|
||||
return "", ErrExceedsTotalLength
|
||||
}
|
||||
|
||||
if !allowRepeat && chars > len(letters) {
|
||||
return "", ErrLettersExceedsAvailable
|
||||
}
|
||||
|
||||
if !allowRepeat && numDigits > len(g.digits) {
|
||||
return "", ErrDigitsExceedsAvailable
|
||||
}
|
||||
|
||||
if !allowRepeat && numSymbols > len(g.symbols) {
|
||||
return "", ErrSymbolsExceedsAvailable
|
||||
}
|
||||
|
||||
var result string
|
||||
|
||||
// Characters
|
||||
for i := 0; i < chars; i++ {
|
||||
ch, err := randomElement(letters)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, ch) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, ch)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Digits
|
||||
for i := 0; i < numDigits; i++ {
|
||||
d, err := randomElement(g.digits)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, d) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, d)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Symbols
|
||||
for i := 0; i < numSymbols; i++ {
|
||||
sym, err := randomElement(g.symbols)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !allowRepeat && strings.Contains(result, sym) {
|
||||
i--
|
||||
continue
|
||||
}
|
||||
|
||||
result, err = randomInsert(result, sym)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// MustGenerate is the same as Generate, but panics on error.
|
||||
func (g *Generator) MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||
res, err := g.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// See Generator.Generate for usage.
|
||||
func Generate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) (string, error) {
|
||||
gen, err := NewGenerator(nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return gen.Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
}
|
||||
|
||||
// See Generator.MustGenerate for usage.
|
||||
func MustGenerate(length, numDigits, numSymbols int, noUpper, allowRepeat bool) string {
|
||||
res, err := Generate(length, numDigits, numSymbols, noUpper, allowRepeat)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// randomInsert randomly inserts the given value into the given string.
|
||||
func randomInsert(s, val string) (string, error) {
|
||||
if s == "" {
|
||||
return val, nil
|
||||
}
|
||||
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s)+1)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
i := n.Int64()
|
||||
return s[0:i] + val + s[i:len(s)], nil
|
||||
}
|
||||
|
||||
// randomElement extracts a random element from the given string.
|
||||
func randomElement(s string) (string, error) {
|
||||
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(s))))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(s[n.Int64()]), nil
|
||||
}
|
|
@ -822,6 +822,16 @@ func (t *BTree) Len() int {
|
|||
return t.length
|
||||
}
|
||||
|
||||
// Context returns the context of the tree.
|
||||
func (t *BTree) Context() interface{} {
|
||||
return t.ctx
|
||||
}
|
||||
|
||||
// SetContext will replace the context of the tree.
|
||||
func (t *BTree) SetContext(ctx interface{}) {
|
||||
t.ctx = ctx
|
||||
}
|
||||
|
||||
// Int implements the Item interface for integers.
|
||||
type Int int
|
||||
|
||||
|
|
|
@ -334,8 +334,6 @@ func (idx *index) rebuild() {
|
|||
// less function to handle the content format and comparison.
|
||||
// There are some default less function that can be used such as
|
||||
// IndexString, IndexBinary, etc.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) CreateIndex(name, pattern string,
|
||||
less ...func(a, b string) bool) error {
|
||||
return db.Update(func(tx *Tx) error {
|
||||
|
@ -347,8 +345,6 @@ func (db *DB) CreateIndex(name, pattern string,
|
|||
// The items are ordered in an b-tree and can be retrieved using the
|
||||
// Ascend* and Descend* methods.
|
||||
// If a previous index with the same name exists, that index will be deleted.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) ReplaceIndex(name, pattern string,
|
||||
less ...func(a, b string) bool) error {
|
||||
return db.Update(func(tx *Tx) error {
|
||||
|
@ -381,8 +377,6 @@ func (db *DB) ReplaceIndex(name, pattern string,
|
|||
// Thus min[0] must be less-than-or-equal-to max[0].
|
||||
// The IndexRect is a default function that can be used for the rect
|
||||
// parameter.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) CreateSpatialIndex(name, pattern string,
|
||||
rect func(item string) (min, max []float64)) error {
|
||||
return db.Update(func(tx *Tx) error {
|
||||
|
@ -394,8 +388,6 @@ func (db *DB) CreateSpatialIndex(name, pattern string,
|
|||
// The items are organized in an r-tree and can be retrieved using the
|
||||
// Intersects method.
|
||||
// If a previous index with the same name exists, that index will be deleted.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) ReplaceSpatialIndex(name, pattern string,
|
||||
rect func(item string) (min, max []float64)) error {
|
||||
return db.Update(func(tx *Tx) error {
|
||||
|
@ -415,8 +407,6 @@ func (db *DB) ReplaceSpatialIndex(name, pattern string,
|
|||
}
|
||||
|
||||
// DropIndex removes an index.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) DropIndex(name string) error {
|
||||
return db.Update(func(tx *Tx) error {
|
||||
return tx.DropIndex(name)
|
||||
|
@ -424,8 +414,6 @@ func (db *DB) DropIndex(name string) error {
|
|||
}
|
||||
|
||||
// Indexes returns a list of index names.
|
||||
//
|
||||
// Deprecated: Use Transactions
|
||||
func (db *DB) Indexes() ([]string, error) {
|
||||
var names []string
|
||||
var err = db.View(func(tx *Tx) error {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
module github.com/tidwall/buntdb
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274
|
||||
github.com/tidwall/gjson v1.3.4
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb
|
||||
github.com/tidwall/match v1.0.1
|
||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 h1:G6Z6HvJuPjG6XfNGi/feOATzeJrfgTNJY+rGrHbA04E=
|
||||
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
|
||||
github.com/tidwall/gjson v1.3.4 h1:On5waDnyKKk3SWE4EthbjjirAWXp43xx5cKCUZY1eZw=
|
||||
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb h1:5NSYaAdrnblKByzd7XByQEJVT8+9v0W/tIY0Oo4OwrE=
|
||||
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
|
||||
github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc=
|
||||
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
|
||||
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e h1:+NL1GDIUOKxVfbp2KoJQD9cTQ6dyP2co9q4yzmT9FZo=
|
||||
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ3KKDyCkxapfufrqDqwmGjcHfAyXRrE=
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
|
@ -12,6 +12,7 @@ This document is designed to explain the structure of a GJSON Path through examp
|
|||
- [Queries](#queries)
|
||||
- [Dot vs Pipe](#dot-vs-pipe)
|
||||
- [Modifiers](#modifiers)
|
||||
- [Multipaths](#multipaths)
|
||||
|
||||
The definitive implemenation is [github.com/tidwall/gjson](https://github.com/tidwall/gjson).
|
||||
Use the [GJSON Playground](https://gjson.dev) to experiment with the syntax online.
|
||||
|
@ -235,7 +236,7 @@ gjson.AddModifier("case", func(json, arg string) string {
|
|||
"children.@case:lower.@reverse" ["jack","alex","sara"]
|
||||
```
|
||||
|
||||
#### Multipaths
|
||||
### Multipaths
|
||||
|
||||
Starting with v1.3.0, GJSON added the ability to join multiple paths together
|
||||
to form new documents. Wrapping comma-separated paths between `{...}` or
|
||||
|
|
|
@ -464,9 +464,9 @@ func ParseBytes(json []byte) Result {
|
|||
}
|
||||
|
||||
func squash(json string) string {
|
||||
// expects that the lead character is a '[' or '{'
|
||||
// expects that the lead character is a '[' or '{' or '('
|
||||
// squash the value, ignoring all nested arrays and objects.
|
||||
// the first '[' or '{' has already been read
|
||||
// the first '[' or '{' or '(', has already been read
|
||||
depth := 1
|
||||
for i := 1; i < len(json); i++ {
|
||||
if json[i] >= '"' && json[i] <= '}' {
|
||||
|
@ -495,9 +495,9 @@ func squash(json string) string {
|
|||
break
|
||||
}
|
||||
}
|
||||
case '{', '[':
|
||||
case '{', '[', '(':
|
||||
depth++
|
||||
case '}', ']':
|
||||
case '}', ']', ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return json[:i+1]
|
||||
|
@ -1056,9 +1056,9 @@ func parseObjectPath(path string) (r objectPathResult) {
|
|||
}
|
||||
|
||||
func parseSquash(json string, i int) (int, string) {
|
||||
// expects that the lead character is a '[' or '{'
|
||||
// expects that the lead character is a '[' or '{' or '('
|
||||
// squash the value, ignoring all nested arrays and objects.
|
||||
// the first '[' or '{' has already been read
|
||||
// the first '[' or '{' or '(' has already been read
|
||||
s := i
|
||||
i++
|
||||
depth := 1
|
||||
|
@ -1089,9 +1089,9 @@ func parseSquash(json string, i int) (int, string) {
|
|||
break
|
||||
}
|
||||
}
|
||||
case '{', '[':
|
||||
case '{', '[', '(':
|
||||
depth++
|
||||
case '}', ']':
|
||||
case '}', ']', ')':
|
||||
depth--
|
||||
if depth == 0 {
|
||||
i++
|
||||
|
@ -1556,19 +1556,30 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
|
|||
var jsons = make([]byte, 0, 64)
|
||||
jsons = append(jsons, '[')
|
||||
for j, k := 0, 0; j < len(alog); j++ {
|
||||
_, res, ok := parseAny(c.json, alog[j], true)
|
||||
if ok {
|
||||
res := res.Get(rp.alogkey)
|
||||
if res.Exists() {
|
||||
if k > 0 {
|
||||
jsons = append(jsons, ',')
|
||||
idx := alog[j]
|
||||
for idx < len(c.json) {
|
||||
switch c.json[idx] {
|
||||
case ' ', '\t', '\r', '\n':
|
||||
idx++
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
if idx < len(c.json) && c.json[idx] != ']' {
|
||||
_, res, ok := parseAny(c.json, idx, true)
|
||||
if ok {
|
||||
res := res.Get(rp.alogkey)
|
||||
if res.Exists() {
|
||||
if k > 0 {
|
||||
jsons = append(jsons, ',')
|
||||
}
|
||||
raw := res.Raw
|
||||
if len(raw) == 0 {
|
||||
raw = res.String()
|
||||
}
|
||||
jsons = append(jsons, []byte(raw)...)
|
||||
k++
|
||||
}
|
||||
raw := res.Raw
|
||||
if len(raw) == 0 {
|
||||
raw = res.String()
|
||||
}
|
||||
jsons = append(jsons, []byte(raw)...)
|
||||
k++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1615,10 +1626,21 @@ func splitPossiblePipe(path string) (left, right string, ok bool) {
|
|||
return
|
||||
}
|
||||
|
||||
if len(path) > 0 && path[0] == '{' {
|
||||
squashed := squash(path[1:])
|
||||
if len(squashed) < len(path)-1 {
|
||||
squashed = path[:len(squashed)+1]
|
||||
remain := path[len(squashed):]
|
||||
if remain[0] == '|' {
|
||||
return squashed, remain[1:], true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// split the left and right side of the path with the pipe character as
|
||||
// the delimiter. This is a little tricky because we'll need to basically
|
||||
// parse the entire path.
|
||||
|
||||
for i := 0; i < len(path); i++ {
|
||||
if path[i] == '\\' {
|
||||
i++
|
||||
|
@ -1699,6 +1721,7 @@ type subSelector struct {
|
|||
// first character in path is either '[' or '{', and has already been checked
|
||||
// prior to calling this function.
|
||||
func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
|
||||
modifer := 0
|
||||
depth := 1
|
||||
colon := 0
|
||||
start := 1
|
||||
|
@ -1719,8 +1742,12 @@ func parseSubSelectors(path string) (sels []subSelector, out string, ok bool) {
|
|||
switch path[i] {
|
||||
case '\\':
|
||||
i++
|
||||
case '@':
|
||||
if modifer == 0 && i > 0 && (path[i-1] == '.' || path[i-1] == '|') {
|
||||
modifer = i
|
||||
}
|
||||
case ':':
|
||||
if depth == 1 {
|
||||
if modifer == 0 && colon == 0 && depth == 1 {
|
||||
colon = i
|
||||
}
|
||||
case ',':
|
||||
|
|
|
@ -3,6 +3,14 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package blowfish implements Bruce Schneier's Blowfish encryption algorithm.
|
||||
//
|
||||
// Blowfish is a legacy cipher and its short block size makes it vulnerable to
|
||||
// birthday bound attacks (see https://sweet32.info). It should only be used
|
||||
// where compatibility with legacy systems, not security, is the goal.
|
||||
//
|
||||
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
|
||||
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
|
||||
// golang.org/x/crypto/chacha20poly1305).
|
||||
package blowfish // import "golang.org/x/crypto/blowfish"
|
||||
|
||||
// The code is a port of Bruce Schneier's C implementation.
|
||||
|
|
|
@ -1,94 +0,0 @@
|
|||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build ignore
|
||||
|
||||
/*
|
||||
This program is a server for the WebDAV 'litmus' compliance test at
|
||||
http://www.webdav.org/neon/litmus/
|
||||
To run the test:
|
||||
|
||||
go run litmus_test_server.go
|
||||
|
||||
and separately, from the downloaded litmus-xxx directory:
|
||||
|
||||
make URL=http://localhost:9999/ check
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/webdav"
|
||||
)
|
||||
|
||||
var port = flag.Int("port", 9999, "server port")
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
log.SetFlags(0)
|
||||
h := &webdav.Handler{
|
||||
FileSystem: webdav.NewMemFS(),
|
||||
LockSystem: webdav.NewMemLS(),
|
||||
Logger: func(r *http.Request, err error) {
|
||||
litmus := r.Header.Get("X-Litmus")
|
||||
if len(litmus) > 19 {
|
||||
litmus = litmus[:16] + "..."
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "COPY", "MOVE":
|
||||
dst := ""
|
||||
if u, err := url.Parse(r.Header.Get("Destination")); err == nil {
|
||||
dst = u.Path
|
||||
}
|
||||
o := r.Header.Get("Overwrite")
|
||||
log.Printf("%-20s%-10s%-30s%-30so=%-2s%v", litmus, r.Method, r.URL.Path, dst, o, err)
|
||||
default:
|
||||
log.Printf("%-20s%-10s%-30s%v", litmus, r.Method, r.URL.Path, err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// The next line would normally be:
|
||||
// http.Handle("/", h)
|
||||
// but we wrap that HTTP handler h to cater for a special case.
|
||||
//
|
||||
// The propfind_invalid2 litmus test case expects an empty namespace prefix
|
||||
// declaration to be an error. The FAQ in the webdav litmus test says:
|
||||
//
|
||||
// "What does the "propfind_invalid2" test check for?...
|
||||
//
|
||||
// If a request was sent with an XML body which included an empty namespace
|
||||
// prefix declaration (xmlns:ns1=""), then the server must reject that with
|
||||
// a "400 Bad Request" response, as it is invalid according to the XML
|
||||
// Namespace specification."
|
||||
//
|
||||
// On the other hand, the Go standard library's encoding/xml package
|
||||
// accepts an empty xmlns namespace, as per the discussion at
|
||||
// https://github.com/golang/go/issues/8068
|
||||
//
|
||||
// Empty namespaces seem disallowed in the second (2006) edition of the XML
|
||||
// standard, but allowed in a later edition. The grammar differs between
|
||||
// http://www.w3.org/TR/2006/REC-xml-names-20060816/#ns-decl and
|
||||
// http://www.w3.org/TR/REC-xml-names/#dt-prefix
|
||||
//
|
||||
// Thus, we assume that the propfind_invalid2 test is obsolete, and
|
||||
// hard-code the 400 Bad Request response that the test expects.
|
||||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("X-Litmus") == "props: 3 (propfind_invalid2)" {
|
||||
http.Error(w, "400 Bad Request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}))
|
||||
|
||||
addr := fmt.Sprintf(":%d", *port)
|
||||
log.Printf("Serving %v", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, nil))
|
||||
}
|
|
@ -556,7 +556,11 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return mw.write(makePropstatResponse(path.Join(h.Prefix, reqPath), pstats))
|
||||
href := path.Join(h.Prefix, reqPath)
|
||||
if href != "/" && info.IsDir() {
|
||||
href += "/"
|
||||
}
|
||||
return mw.write(makePropstatResponse(href, pstats))
|
||||
}
|
||||
|
||||
walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
|
||||
|
|
|
@ -1,29 +1,47 @@
|
|||
# github.com/alecthomas/kong v0.2.11
|
||||
## explicit
|
||||
github.com/alecthomas/kong
|
||||
# github.com/go-chi/chi v4.0.2+incompatible
|
||||
github.com/go-chi/chi
|
||||
## explicit
|
||||
# github.com/go-yaml/yaml v2.1.0+incompatible
|
||||
github.com/go-yaml/yaml
|
||||
## explicit
|
||||
# github.com/kr/pretty v0.1.0
|
||||
## explicit
|
||||
# github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/errors
|
||||
# github.com/satori/go.uuid v1.2.0
|
||||
## explicit
|
||||
github.com/satori/go.uuid
|
||||
# github.com/sethvargo/go-password v0.1.2
|
||||
github.com/sethvargo/go-password/password
|
||||
# github.com/tidwall/btree v0.0.0-20170113224114-9876f1454cf0
|
||||
## explicit
|
||||
# github.com/tidwall/btree v0.0.0-20191029221954-400434d76274
|
||||
github.com/tidwall/btree
|
||||
# github.com/tidwall/buntdb v1.1.0
|
||||
# github.com/tidwall/buntdb v1.1.2
|
||||
## explicit
|
||||
github.com/tidwall/buntdb
|
||||
# github.com/tidwall/gjson v1.3.2
|
||||
# github.com/tidwall/gjson v1.3.4
|
||||
github.com/tidwall/gjson
|
||||
# github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb
|
||||
## explicit
|
||||
github.com/tidwall/grect
|
||||
# github.com/tidwall/match v1.0.1
|
||||
github.com/tidwall/match
|
||||
# github.com/tidwall/pretty v1.0.0
|
||||
github.com/tidwall/pretty
|
||||
# github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e
|
||||
## explicit
|
||||
github.com/tidwall/rtree
|
||||
github.com/tidwall/rtree/base
|
||||
# github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563
|
||||
## explicit
|
||||
github.com/tidwall/tinyqueue
|
||||
# golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869
|
||||
# golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2
|
||||
## explicit
|
||||
golang.org/x/crypto/bcrypt
|
||||
golang.org/x/crypto/blowfish
|
||||
# golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
|
||||
# golang.org/x/net v0.0.0-20190620200207-3b0461eec859
|
||||
## explicit
|
||||
golang.org/x/net/webdav
|
||||
golang.org/x/net/webdav/internal/xml
|
||||
# gopkg.in/yaml.v2 v2.2.2
|
||||
## explicit
|
||||
|
|
Loading…
Reference in New Issue