bump go modules, enable LoginAuth option for email
That option is needed for outlook.com and Office 365, resolves #1472.
This commit is contained in:
committed by
Umputun
parent
3eccf01f1f
commit
cebe929118
@@ -14,29 +14,28 @@ require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.8.0 // indirect
|
||||
github.com/ajg/form v1.5.1 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.2.0 // indirect
|
||||
github.com/alecthomas/chroma/v2 v2.3.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/didip/tollbooth/v7 v7.0.0 // indirect
|
||||
github.com/didip/tollbooth/v7 v7.0.1 // indirect
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.7 // indirect
|
||||
github.com/go-chi/render v1.0.2 // indirect
|
||||
github.com/go-pkgz/expirable-cache v0.1.0 // indirect
|
||||
github.com/go-pkgz/rest v1.15.6 // indirect
|
||||
github.com/go-pkgz/rest v1.16.0 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rs/xid v1.4.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
go.etcd.io/bbolt v1.3.6 // indirect
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82 // indirect
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0g
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
|
||||
@@ -14,8 +14,9 @@ github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd3
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/didip/tollbooth/v7 v7.0.0 h1:XmyyNwZpz9j61PwR4A894MmmYO5zBF9xjgVi2n1fiQI=
|
||||
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulVAog=
|
||||
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
@@ -31,8 +32,9 @@ github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||
github.com/go-pkgz/jrpc v0.3.0/go.mod h1:MFtKs75JESiSqVicsQkgN2iDFFuCd3gVT1/vKiwRi00=
|
||||
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
||||
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
||||
github.com/go-pkgz/rest v1.15.6 h1:8RgOuY/c00CD0el8KdmscOCgDH+ML0ZsK2qa1Rcxal4=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.16.0 h1:3An51XJXUq1XtICHvfAEp048ZCbUHBEga3g0WEu3zK0=
|
||||
github.com/go-pkgz/rest v1.16.0/go.mod h1:HHlLOt02NJc2sgffXBF6hYVMcRo4Gz3vjg43zTzN7VM=
|
||||
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
|
||||
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@@ -47,9 +49,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
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/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||
github.com/pkg/errors v0.9.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=
|
||||
@@ -69,14 +70,14 @@ go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82 h1:KpZB5pUSBvrHltNEdK/tw0xlPeD13M6M6aGP32gKqiw=
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
|
||||
@@ -208,13 +208,14 @@ type TelegramGroup struct {
|
||||
|
||||
// SMTPGroup defines options for SMTP server connection, used in auth and notify modules
|
||||
type SMTPGroup struct {
|
||||
Host string `long:"host" env:"HOST" description:"SMTP host"`
|
||||
Port int `long:"port" env:"PORT" description:"SMTP port"`
|
||||
Username string `long:"username" env:"USERNAME" description:"SMTP user name"`
|
||||
Password string `long:"password" env:"PASSWORD" description:"SMTP password"`
|
||||
TLS bool `long:"tls" env:"TLS" description:"enable TLS"`
|
||||
StartTLS bool `long:"starttls" env:"STARTTLS" description:"enable StartTLS"`
|
||||
TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"`
|
||||
Host string `long:"host" env:"HOST" description:"SMTP host"`
|
||||
Port int `long:"port" env:"PORT" description:"SMTP port"`
|
||||
Username string `long:"username" env:"USERNAME" description:"SMTP user name"`
|
||||
Password string `long:"password" env:"PASSWORD" description:"SMTP password"`
|
||||
TLS bool `long:"tls" env:"TLS" description:"enable TLS"`
|
||||
LoginAuth bool `long:"login_auth" env:"LOGIN_AUTH" description:"enable LOGIN auth instead of PLAIN"`
|
||||
StartTLS bool `long:"starttls" env:"STARTTLS" description:"enable StartTLS"`
|
||||
TimeOut time.Duration `long:"timeout" env:"TIMEOUT" default:"10s" description:"SMTP TCP connection timeout"`
|
||||
}
|
||||
|
||||
// NotifyGroup defines options for notification
|
||||
@@ -880,7 +881,10 @@ func (s *ServerCommand) addAuthProviders(authenticator *auth.Service) error {
|
||||
SMTPUserName: s.SMTP.Username,
|
||||
SMTPPassword: s.SMTP.Password,
|
||||
TimeOut: s.SMTP.TimeOut,
|
||||
StartTLS: s.SMTP.StartTLS,
|
||||
LoginAuth: s.SMTP.LoginAuth,
|
||||
TLS: s.SMTP.TLS,
|
||||
Charset: "UTF-8",
|
||||
From: s.Auth.Email.From,
|
||||
Subject: s.Auth.Email.Subject,
|
||||
ContentType: s.Auth.Email.ContentType,
|
||||
@@ -1031,6 +1035,7 @@ func (s *ServerCommand) makeNotifyDestinations(authenticator *auth.Service) ([]n
|
||||
Port: s.SMTP.Port,
|
||||
TLS: s.SMTP.TLS,
|
||||
StartTLS: s.SMTP.StartTLS,
|
||||
LoginAuth: s.SMTP.LoginAuth,
|
||||
Username: s.SMTP.Username,
|
||||
Password: s.SMTP.Password,
|
||||
TimeOut: s.SMTP.TimeOut,
|
||||
|
||||
+14
-14
@@ -5,19 +5,19 @@ go 1.17
|
||||
require (
|
||||
github.com/Depado/bfchroma/v2 v2.0.0
|
||||
github.com/PuerkitoBio/goquery v1.8.0
|
||||
github.com/alecthomas/chroma/v2 v2.2.0
|
||||
github.com/didip/tollbooth/v7 v7.0.0
|
||||
github.com/alecthomas/chroma/v2 v2.3.0
|
||||
github.com/didip/tollbooth/v7 v7.0.1
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f
|
||||
github.com/go-chi/chi/v5 v5.0.7
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/go-chi/render v1.0.2
|
||||
github.com/go-pkgz/auth v1.19.1
|
||||
github.com/go-pkgz/auth v1.20.0
|
||||
github.com/go-pkgz/jrpc v0.3.0
|
||||
github.com/go-pkgz/lcw v1.0.1
|
||||
github.com/go-pkgz/lgr v0.10.4
|
||||
github.com/go-pkgz/notify v0.1.1
|
||||
github.com/go-pkgz/notify v0.2.0
|
||||
github.com/go-pkgz/repeater v1.1.3
|
||||
github.com/go-pkgz/rest v1.15.6
|
||||
github.com/go-pkgz/rest v1.16.0
|
||||
github.com/go-pkgz/syncs v1.2.0
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||
github.com/google/uuid v1.3.0
|
||||
@@ -25,16 +25,16 @@ require (
|
||||
github.com/hashicorp/go-multierror v1.1.1
|
||||
github.com/jessevdk/go-flags v1.5.0
|
||||
github.com/kyokomi/emoji/v2 v2.2.10
|
||||
github.com/microcosm-cc/bluemonday v1.0.19
|
||||
github.com/microcosm-cc/bluemonday v1.0.20
|
||||
github.com/rs/xid v1.4.0
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.8.0
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/goleak v1.1.12
|
||||
go.uber.org/goleak v1.2.0
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -47,11 +47,10 @@ require (
|
||||
github.com/dghubble/oauth1 v0.7.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dlclark/regexp2 v1.4.0 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3 // indirect
|
||||
github.com/go-pkgz/email v0.3.0 // indirect
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.1 // indirect
|
||||
github.com/go-pkgz/email v0.4.0 // indirect
|
||||
github.com/go-pkgz/expirable-cache v0.1.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5 // indirect
|
||||
github.com/go-stack/stack v1.8.1 // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/gorilla/css v1.0.0 // indirect
|
||||
@@ -59,18 +58,19 @@ require (
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||
github.com/klauspost/compress v1.15.2 // indirect
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
|
||||
github.com/nullrocks/identicon v0.0.0-20180626043057-7875f45b0022 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/slack-go/slack v0.10.3 // indirect
|
||||
github.com/slack-go/slack v0.11.3 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.1 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.3 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
go.mongodb.org/mongo-driver v1.9.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.10.2 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
|
||||
+37
-49
@@ -61,16 +61,18 @@ github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0g
|
||||
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
|
||||
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0 h1:83xfxrnjv8eK+Cf8qZDzNo3PPF9IbTWHs7z28GY6D0U=
|
||||
github.com/alecthomas/chroma/v2 v2.3.0/go.mod h1:mZxeWZlxP2Dy+/8cBob2PYd8O2DwNAzave5AY7A2eQw=
|
||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alecthomas/repr v0.1.0 h1:ENn2e1+J3k09gyj2shc0dHr/yjaWSHRlrJ4DPMevDqE=
|
||||
github.com/alecthomas/repr v0.1.0/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||
github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo=
|
||||
github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
|
||||
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
|
||||
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
@@ -101,8 +103,9 @@ github.com/dghubble/oauth1 v0.7.1 h1:JjbOVSVVkms9A4h/sTQy5Jb2nFuAAVb2qVYgenJPyrE
|
||||
github.com/dghubble/oauth1 v0.7.1/go.mod h1:0eEzON0UY/OLACQrmnjgJjmvCGXzjBCsZqL1kWDXtF0=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/didip/tollbooth/v7 v7.0.0 h1:XmyyNwZpz9j61PwR4A894MmmYO5zBF9xjgVi2n1fiQI=
|
||||
github.com/didip/tollbooth/v7 v7.0.0/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth/v7 v7.0.1 h1:TkT4sBKoQoHQFPf7blQ54iHrZiTDnr8TceU+MulVAog=
|
||||
github.com/didip/tollbooth/v7 v7.0.1/go.mod h1:VZhDSGl5bDSPj4wPsih3PFa4Uh9Ghv8hgacaTm5PRT4=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f h1:jtKwihcLmUC9BAhoJ9adCUqdSSZcOdH2KL7mPTUm2aw=
|
||||
github.com/didip/tollbooth_chi v0.0.0-20220719025231-d662a7f6928f/go.mod h1:q9C80dnsuVRP2dAskjnXRNWdUJqtGgwG9wNrzt0019s=
|
||||
github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E=
|
||||
@@ -135,12 +138,13 @@ github.com/go-chi/render v1.0.2/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIo
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3 h1:IiU3iAK5NEeZ4mUf5HV9tN7iNlx4AGUkIJ5M1C4ARxY=
|
||||
github.com/go-oauth2/oauth2/v4 v4.4.3/go.mod h1:NR9Hugz5/Qe2OGxoPBhsTRNjnm/amC+z9+XTwt63rhs=
|
||||
github.com/go-pkgz/auth v1.19.1 h1:yoxQvx6cBYJPHvMryJw+vj+/1QpjPSKERBfPL/mPGT0=
|
||||
github.com/go-pkgz/auth v1.19.1/go.mod h1:Uex9UlbhlHamrkO14f69BuZX5n5x1CSzIxZb/En+VPw=
|
||||
github.com/go-pkgz/email v0.3.0 h1:89FbVF6trHkdn2VVoXlBfZHkdYQ1vLmQ54OhdosaFtc=
|
||||
github.com/go-pkgz/email v0.3.0/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.1 h1:3vxp+cjLqDe1TbogbwtMyeHRHr1tD+ksrK7xNppYRDs=
|
||||
github.com/go-oauth2/oauth2/v4 v4.5.1/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
|
||||
github.com/go-pkgz/auth v1.20.0 h1:9SHkolgv9+zOI9U+8IFXra0pl/H90oE4+ciJWtXoDl4=
|
||||
github.com/go-pkgz/auth v1.20.0/go.mod h1:jz0djN+4XoCyieuOzc+rB9l0RdqeuiXoQfE02yonLAI=
|
||||
github.com/go-pkgz/email v0.3.1-0.20221002173339-19d25a20d99c/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
||||
github.com/go-pkgz/email v0.4.0 h1:NiYCwKPR6sW8nJsOR3GyB9Yw1AQZdnY78Xnhd9SLHEs=
|
||||
github.com/go-pkgz/email v0.4.0/go.mod h1:TpnmSLkQW3FyICit2hn7WIhCUDrhCX6btzz5wS3wHRI=
|
||||
github.com/go-pkgz/expirable-cache v0.1.0 h1:3bw0m8vlTK8qlwz5KXuygNBTkiKRTPrAGXU0Ej2AC1g=
|
||||
github.com/go-pkgz/expirable-cache v0.1.0/go.mod h1:GTrEl0X+q0mPNqN6dtcQXksACnzCBQ5k/k1SwXJsZKs=
|
||||
github.com/go-pkgz/jrpc v0.3.0 h1:Fls38KqPsHzvp0FWfivr6cGnncC+iFBodHBqvUPY+0U=
|
||||
@@ -149,21 +153,18 @@ github.com/go-pkgz/lcw v1.0.1 h1:svYC6LIyzRaHF3TwJ8GCS+2RkJreBfaFjoeS+UYwJBc=
|
||||
github.com/go-pkgz/lcw v1.0.1/go.mod h1:CPJJzunpmGToOtD0Ga82TV152eL69sYEIIPcy9fbxlU=
|
||||
github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw=
|
||||
github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0=
|
||||
github.com/go-pkgz/notify v0.1.1 h1:0EaPnBD/IX8EBVAmKnEHNesyH3rD1v6w0MBLRCwrTUI=
|
||||
github.com/go-pkgz/notify v0.1.1/go.mod h1:XTzsXgwXXsXbl8iz6qTsCq6gLkaOkimL8mJfLrk3bCk=
|
||||
github.com/go-pkgz/notify v0.2.0 h1:mxHjcLc3goT+k1qnBPJ06PpNuVUDcu21Xy+6hEo4IaU=
|
||||
github.com/go-pkgz/notify v0.2.0/go.mod h1:uB4bcohkZB+P8zT279KQv5aXqQEkeMXI3Ey0Y3r0SQk=
|
||||
github.com/go-pkgz/repeater v1.1.3 h1:q6+JQF14ESSy28Dd7F+wRelY4F+41HJ0LEy/szNnMiE=
|
||||
github.com/go-pkgz/repeater v1.1.3/go.mod h1:hVTavuO5x3Gxnu8zW7d6sQBfAneKV8X2FjU48kGfpKw=
|
||||
github.com/go-pkgz/rest v1.14.0/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.15.6 h1:8RgOuY/c00CD0el8KdmscOCgDH+ML0ZsK2qa1Rcxal4=
|
||||
github.com/go-pkgz/rest v1.15.6/go.mod h1:KUWAqbDteYGS/CiXftomQsKjtEOifXsJ36Ka0skYbmk=
|
||||
github.com/go-pkgz/rest v1.16.0 h1:3An51XJXUq1XtICHvfAEp048ZCbUHBEga3g0WEu3zK0=
|
||||
github.com/go-pkgz/rest v1.16.0/go.mod h1:HHlLOt02NJc2sgffXBF6hYVMcRo4Gz3vjg43zTzN7VM=
|
||||
github.com/go-pkgz/syncs v1.2.0 h1:aiizQFILlMZ4KtRNaYLcDffRbUQZH9fclsgr5KybWyY=
|
||||
github.com/go-pkgz/syncs v1.2.0/go.mod h1:fjThZdM2FkC/oSeiqBTOZOtHpbrCh4HuHbipB5qZJJM=
|
||||
github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
|
||||
github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
|
||||
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
@@ -283,9 +284,8 @@ github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/X
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.2 h1:3WH+AG7s2+T8o3nrM/8u2rdqUEcQhmga7smjrT41nAw=
|
||||
github.com/klauspost/compress v1.15.2/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
@@ -297,9 +297,9 @@ github.com/kyokomi/emoji/v2 v2.2.10 h1:1z5eMVcxFifsmEoNpdeq4UahbcicgQ4FEHuzrCVwm
|
||||
github.com/kyokomi/emoji/v2 v2.2.10/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
|
||||
github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 h1:flpzsq4KU3QIYAYGV/szUat7H+GPOXR0B2JU5A1Wp8Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.20/go.mod h1:yfBmMi8mxvaZut3Yytv+jTXRY8mxyjJ0/kQBTElld50=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
|
||||
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
|
||||
@@ -336,8 +336,8 @@ github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/slack-go/slack v0.10.3 h1:kKYwlKY73AfSrtAk9UHWCXXfitudkDztNI9GYBviLxw=
|
||||
github.com/slack-go/slack v0.10.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/slack-go/slack v0.11.3 h1:GN7revxEMax4amCc3El9a+9SGnjmBvSUobs0QnO6ZO8=
|
||||
github.com/slack-go/slack v0.11.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
@@ -373,15 +373,13 @@ github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 h1:Otn9S136ELckZ
|
||||
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A=
|
||||
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/valyala/fasthttp v1.34.0 h1:d3AAQJ2DRcxJYHm7OXNXtXt2as1vMDfxeIcFvhmGGm4=
|
||||
github.com/valyala/fasthttp v1.34.0/go.mod h1:epZA5N+7pY6ZaEKRmstzOuYJx9HI8DI1oaCGZpdH4h0=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs=
|
||||
github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM=
|
||||
github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
|
||||
@@ -409,9 +407,8 @@ github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLC
|
||||
github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.mongodb.org/mongo-driver v1.8.5/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.mongodb.org/mongo-driver v1.9.1 h1:m078y9v7sBItkt1aaoe2YlvWEXcD263e1a4E1fBrJ1c=
|
||||
go.mongodb.org/mongo-driver v1.9.1/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY=
|
||||
go.mongodb.org/mongo-driver v1.10.2 h1:4Wk3cnqOrQCn0P92L3/mmurMxzdvWWs5J9jinAVKD+k=
|
||||
go.mongodb.org/mongo-driver v1.10.2/go.mod h1:z4XpeoU6w+9Vht+jAFyLgVrD+jGSQQe0+CBWFHNiHt8=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
@@ -420,16 +417,15 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -509,17 +505,14 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b h1:ZmngSVLe/wycRns9MKikG9OWIEjGcGAkacif7oYQaUY=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -566,7 +559,6 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -584,7 +576,6 @@ golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
@@ -617,10 +608,8 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
|
||||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -646,7 +635,6 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
|
||||
+2
-2
@@ -38,7 +38,7 @@ Prefix | Language
|
||||
:----: | --------
|
||||
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
|
||||
B | Ballerina, Base Makefile, Bash, Batchfile, BibTeX, Bicep, BlitzBasic, BNF, Brainfuck
|
||||
C | C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
|
||||
C | C, C#, C++, Caddyfile, Caddyfile Directives, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
|
||||
D | D, Dart, Diff, Django/Jinja, Docker, DTD, Dylan
|
||||
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
||||
F | Factor, Fish, Forth, Fortran, FSharp
|
||||
@@ -51,7 +51,7 @@ L | Lighttpd configuration file, LLVM, Lua
|
||||
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, MLIR, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
|
||||
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
|
||||
O | Objective-C, OCaml, Octave, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org Mode
|
||||
P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, PromQL, Protocol Buffer, Puppet, Python 2, Python
|
||||
P | PacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, PromQL, Properties, Protocol Buffer, Puppet, Python 2, Python
|
||||
Q | QBasic
|
||||
R | R, Racket, Ragel, Raku, react, ReasonML, reg, reStructuredText, Rexx, Ruby, Rust
|
||||
S | SAS, Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Standard ML, Stylus, Svelte, Swift, SYSTEMD, systemverilog
|
||||
|
||||
+30
-2
@@ -92,7 +92,7 @@ func (c Colour) Brighten(factor float64) Colour {
|
||||
return NewColour(uint8(r), uint8(g), uint8(b))
|
||||
}
|
||||
|
||||
// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
|
||||
// BrightenOrDarken brightens a colour if it is < 0.5 brightness or darkens if > 0.5 brightness.
|
||||
func (c Colour) BrightenOrDarken(factor float64) Colour {
|
||||
if c.Brightness() < 0.5 {
|
||||
return c.Brighten(factor)
|
||||
@@ -100,7 +100,35 @@ func (c Colour) BrightenOrDarken(factor float64) Colour {
|
||||
return c.Brighten(-factor)
|
||||
}
|
||||
|
||||
// Brightness of the colour (roughly) in the range 0.0 to 1.0
|
||||
// ClampBrightness returns a copy of this colour with its brightness adjusted such that
|
||||
// it falls within the range [min, max] (or very close to it due to rounding errors).
|
||||
// The supplied values use the same [0.0, 1.0] range as Brightness.
|
||||
func (c Colour) ClampBrightness(min, max float64) Colour {
|
||||
if !c.IsSet() {
|
||||
return c
|
||||
}
|
||||
|
||||
min = math.Max(min, 0)
|
||||
max = math.Min(max, 1)
|
||||
current := c.Brightness()
|
||||
target := math.Min(math.Max(current, min), max)
|
||||
if current == target {
|
||||
return c
|
||||
}
|
||||
|
||||
r := float64(c.Red())
|
||||
g := float64(c.Green())
|
||||
b := float64(c.Blue())
|
||||
rgb := r + g + b
|
||||
if target > current {
|
||||
// Solve for x: target == ((255-r)*x + r + (255-g)*x + g + (255-b)*x + b) / 255 / 3
|
||||
return c.Brighten((target*255*3 - rgb) / (255*3 - rgb))
|
||||
}
|
||||
// Solve for x: target == (r*(x+1) + g*(x+1) + b*(x+1)) / 255 / 3
|
||||
return c.Brighten((target*255*3)/rgb - 1)
|
||||
}
|
||||
|
||||
// Brightness of the colour (roughly) in the range 0.0 to 1.0.
|
||||
func (c Colour) Brightness() float64 {
|
||||
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package lexers
|
||||
|
||||
import (
|
||||
. "github.com/alecthomas/chroma/v2" // nolint
|
||||
)
|
||||
|
||||
// Chapel lexer.
|
||||
var Chapel = Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Chapel",
|
||||
Aliases: []string{"chapel", "chpl"},
|
||||
Filenames: []string{"*.chpl"},
|
||||
MimeTypes: []string{},
|
||||
},
|
||||
func() Rules {
|
||||
return Rules{
|
||||
"root": {
|
||||
{`\n`, TextWhitespace, nil},
|
||||
{`\s+`, TextWhitespace, nil},
|
||||
{`\\\n`, Text, nil},
|
||||
{`//(.*?)\n`, CommentSingle, nil},
|
||||
{`/(\\\n)?[*](.|\n)*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||
{Words(``, `\b`, `config`, `const`, `in`, `inout`, `out`, `param`, `ref`, `type`, `var`), KeywordDeclaration, nil},
|
||||
{Words(``, `\b`, `false`, `nil`, `none`, `true`), KeywordConstant, nil},
|
||||
{Words(``, `\b`, `bool`, `bytes`, `complex`, `imag`, `int`, `locale`, `nothing`, `opaque`, `range`, `real`, `string`, `uint`, `void`), KeywordType, nil},
|
||||
{Words(``, `\b`, `atomic`, `single`, `sync`, `borrowed`, `owned`, `shared`, `unmanaged`, `align`, `as`, `begin`, `break`, `by`, `catch`, `cobegin`, `coforall`, `continue`, `defer`, `delete`, `dmapped`, `do`, `domain`, `else`, `enum`, `except`, `export`, `extern`, `for`, `forall`, `foreach`, `forwarding`, `if`, `implements`, `import`, `index`, `init`, `inline`, `label`, `lambda`, `let`, `lifetime`, `local`, `new`, `noinit`, `on`, `only`, `otherwise`, `override`, `pragma`, `primitive`, `private`, `prototype`, `public`, `reduce`, `require`, `return`, `scan`, `select`, `serial`, `sparse`, `subdomain`, `then`, `this`, `throw`, `throws`, `try`, `use`, `when`, `where`, `while`, `with`, `yield`, `zip`), Keyword, nil},
|
||||
{`(iter)(\s+)`, ByGroups(Keyword, TextWhitespace), Push("procname")},
|
||||
{`(proc)(\s+)`, ByGroups(Keyword, TextWhitespace), Push("procname")},
|
||||
{`(operator)(\s+)`, ByGroups(Keyword, TextWhitespace), Push("procname")},
|
||||
{`(class|interface|module|record|union)(\s+)`, ByGroups(Keyword, TextWhitespace), Push("classname")},
|
||||
{`\d+i`, LiteralNumber, nil},
|
||||
{`\d+\.\d*([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||
{`\.\d+([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||
{`\d+[Ee][-+]\d+i`, LiteralNumber, nil},
|
||||
{`(\d*\.\d+)([eE][+-]?[0-9]+)?i?`, LiteralNumberFloat, nil},
|
||||
{`\d+[eE][+-]?[0-9]+i?`, LiteralNumberFloat, nil},
|
||||
{`0[bB][01]+`, LiteralNumberBin, nil},
|
||||
{`0[xX][0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||
{`0[oO][0-7]+`, LiteralNumberOct, nil},
|
||||
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||
{`'(\\\\|\\'|[^'])*'`, LiteralString, nil},
|
||||
{`(=|\+=|-=|\*=|/=|\*\*=|%=|&=|\|=|\^=|&&=|\|\|=|<<=|>>=|<=>|<~>|\.\.|by|#|\.\.\.|&&|\|\||!|&|\||\^|~|<<|>>|==|!=|<=|>=|<|>|[+\-*/%]|\*\*)`, Operator, nil},
|
||||
{`[:;,.?()\[\]{}]`, Punctuation, nil},
|
||||
{`[a-zA-Z_][\w$]*`, NameOther, nil},
|
||||
},
|
||||
"classname": {
|
||||
{`[a-zA-Z_][\w$]*`, NameClass, Pop(1)},
|
||||
},
|
||||
"procname": {
|
||||
{`([a-zA-Z_][.\w$]*|\~[a-zA-Z_][.\w$]*|[+*/!~%<>=&^|\-:]{1,2})`, NameFunction, Pop(1)},
|
||||
{`\(`, Punctuation, Push("receivertype")},
|
||||
{`\)+\.`, Punctuation, nil},
|
||||
},
|
||||
"receivertype": {
|
||||
{Words(``, `\b`, `atomic`, `single`, `sync`, `borrowed`, `owned`, `shared`, `unmanaged`), Keyword, nil},
|
||||
{Words(``, `\b`, `bool`, `bytes`, `complex`, `imag`, `int`, `locale`, `nothing`, `opaque`, `range`, `real`, `string`, `uint`, `void`), KeywordType, nil},
|
||||
{`[^()]*`, NameOther, Pop(1)},
|
||||
},
|
||||
}
|
||||
},
|
||||
))
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package lexers
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// TODO(moorereason): can this be factored away?
|
||||
var zoneAnalyserRe = regexp.MustCompile(`(?m)^@\s+IN\s+SOA\s+`)
|
||||
|
||||
func init() { // nolint: gochecknoinits
|
||||
Get("dns").SetAnalyser(func(text string) float32 {
|
||||
if zoneAnalyserRe.FindString(text) != "" {
|
||||
return 1.0
|
||||
}
|
||||
return 0.0
|
||||
})
|
||||
}
|
||||
+1
-1
@@ -9,7 +9,7 @@ var Docker = Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Docker",
|
||||
Aliases: []string{"docker", "dockerfile"},
|
||||
Filenames: []string{"Dockerfile", "*.docker"},
|
||||
Filenames: []string{"Dockerfile", "Dockerfile.*", "*.docker"},
|
||||
MimeTypes: []string{"text/x-dockerfile-config"},
|
||||
CaseInsensitive: true,
|
||||
},
|
||||
|
||||
-12
@@ -70,18 +70,6 @@
|
||||
<token type="NameFunction"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(")([^"]+)(")(\s+)(string|slist|int|real)(\s*)(=>)(\s*)">
|
||||
<bygroups>
|
||||
<token type="Punctuation"/>
|
||||
<token type="NameVariable"/>
|
||||
<token type="Punctuation"/>
|
||||
<token type="Text"/>
|
||||
<token type="KeywordType"/>
|
||||
<token type="Text"/>
|
||||
<token type="Operator"/>
|
||||
<token type="Text"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="(\S+)(\s*)(=>)(\s*)">
|
||||
<bygroups>
|
||||
<token type="KeywordReserved"/>
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0"?>
|
||||
<lexer>
|
||||
<config>
|
||||
<name>dns</name>
|
||||
<alias>zone</alias>
|
||||
<alias>bind</alias>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="\b(IN|A|AAAA|AFSDB|APL|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|HIP|IPSECKEY|KEY|KX|LOC|MX|NAPTR|NS|NSEC|NSEC3|NSEC3PARAM|PTR|RRSIG|RP|SIG|SOA|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT)\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern=";.*(\S|$)">
|
||||
<token type="Comment"/>
|
||||
</rule>
|
||||
<rule pattern="\b((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}))|:)))\b">
|
||||
<token type="LiteralNumberIntegerLong"/>
|
||||
</rule>
|
||||
<rule pattern="\b((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\b">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<rule pattern="\b\d+[dhwm]?">
|
||||
<token type="LiteralStringChar"/>
|
||||
</rule>
|
||||
<rule pattern="\b([[:alnum:].-])+\.($|\s)">
|
||||
<token type="NameProperty"/>
|
||||
</rule>
|
||||
<rule pattern="^(@|[[:alnum:]-]+)">
|
||||
<token type="NameClass"/>
|
||||
</rule>
|
||||
<rule pattern="^\$(TTL|GENERATE|INCLUDE|ORIGIN)">
|
||||
<token type="NameAttribute"/>
|
||||
</rule>
|
||||
<rule pattern="\(|\)">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="[\r\n\s\t]+">
|
||||
<token type="TextWhitespace"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+21
-9
@@ -2,20 +2,29 @@
|
||||
<config>
|
||||
<name>Fortran</name>
|
||||
<alias>fortran</alias>
|
||||
<alias>f90</alias>
|
||||
<filename>*.f03</filename>
|
||||
<filename>*.f90</filename>
|
||||
<filename>*.f95</filename>
|
||||
<filename>*.F03</filename>
|
||||
<filename>*.F90</filename>
|
||||
<filename>*.F95</filename>
|
||||
<mime_type>text/x-fortran</mime_type>
|
||||
<case_insensitive>true</case_insensitive>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="core">
|
||||
<rule pattern="\b(NON_OVERRIDABLE|NON_INTRINSIC|ASYNCHRONOUS|ALLOCATABLE|CODIMENSION|EQUIVALENCE|CONCURRRENT|SYNCMEMORY|SUBROUTINE|CONTIGUOUS|SYNCIMAGES|ENUMERATOR|DEALLOCATE|BLOCKDATA|BACKSPACE|PROTECTED|RECURSIVE|PROCEDURE|SUBMODULE|EXTRINSIC|ASSOCIATE|PARAMETER|INTERFACE|ELEMENTAL|DIMENSION|INTRINSIC|ABSTRACT|EXTERNAL|CRITICAL|SEQUENCE|DEFERRED|CONTINUE|NAMELIST|CONTAINS|OPTIONAL|ALLOCATE|VOLATILE|IMPLICIT|FUNCTION|SYNCALL|INCLUDE|INQUIRE|EXTENDS|ALLSTOP|OPTIONS|PROGRAM|PRIVATE|POINTER|NULLIFY|GENERIC|FORMAT|FORALL|ENCODE|IMAGES|UNLOCK|ASSIGN|IMPURE|TARGET|PUBLIC|INTENT|RESULT|ACCEPT|COMMON|RETURN|MEMORY|MODULE|REWIND|DECODE|NOPASS|SELECT|IMPORT|ENTRY|BLOCK|CLASS|WRITE|WHERE|CLOSE|PAUSE|CYCLE|PRINT|FINAL|WHILE|ARRAY|VALUE|SAVE|PURE|READ|SYNC|ENUM|ELSE|EXIT|FILE|DATA|GOTO|STOP|PASS|LOCK|CASE|CALL|BYTE|OPEN|BIND|THEN|TYPE|NONE|USE|END|ALL|IF|IS|DO)\s*\b">
|
||||
<rule pattern="\b(DO)(\s+)(CONCURRENT)\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="\b(C_LONG_DOUBLE_COMPLEX|DOUBLE PRECISION|C_DOUBLE_COMPLEX|C_INT_LEAST32_T|C_INT_LEAST64_T|C_FLOAT_COMPLEX|C_INT_LEAST16_T|C_INT_LEAST8_T|C_INT_FAST32_T|C_INT_FAST64_T|DOUBLE COMPLEX|C_INT_FAST16_T|C_SIGNED_CHAR|C_LONG_DOUBLE|C_INT_FAST8_T|C_LONG_LONG|C_INTMAX_T|C_INTPTR_T|C_INT32_T|C_INT64_T|C_INT16_T|CHARACTER|C_DOUBLE|C_SIZE_T|C_INT8_T|C_FUNPTR|COMPLEX|C_FLOAT|C_SHORT|LOGICAL|INTEGER|C_LONG|C_BOOL|C_CHAR|C_INT|C_PTR|REAL)\s*\b">
|
||||
<token type="KeywordType"/>
|
||||
<rule pattern="\b(GO)(\s*)(TO)\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="\b(ABSTRACT|ACCEPT|ALL|ALLSTOP|ALLOCATABLE|ALLOCATE|ARRAY|ASSIGN|ASSOCIATE|ASYNCHRONOUS|BACKSPACE|BIND|BLOCK|BLOCKDATA|BYTE|CALL|CASE|CLASS|CLOSE|CODIMENSION|COMMON|CONTIGUOUS|CONTAINS|CONTINUE|CRITICAL|CYCLE|DATA|DEALLOCATE|DECODE|DEFERRED|DIMENSION|DO|ELEMENTAL|ELSE|ENCODE|END|ENDASSOCIATE|ENDBLOCK|ENDDO|ENDENUM|ENDFORALL|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDMODULE|ENDPROGRAM|ENDSELECT|ENDSUBMODULE|ENDSUBROUTINE|ENDTYPE|ENDWHERE|ENTRY|ENUM|ENUMERATOR|EQUIVALENCE|ERROR STOP|EXIT|EXTENDS|EXTERNAL|EXTRINSIC|FILE|FINAL|FORALL|FORMAT|FUNCTION|GENERIC|IF|IMAGES|IMPLICIT|IMPORT|IMPURE|INCLUDE|INQUIRE|INTENT|INTERFACE|INTRINSIC|IS|LOCK|MEMORY|MODULE|NAMELIST|NULLIFY|NONE|NON_INTRINSIC|NON_OVERRIDABLE|NOPASS|ONLY|OPEN|OPTIONAL|OPTIONS|PARAMETER|PASS|PAUSE|POINTER|PRINT|PRIVATE|PROGRAM|PROCEDURE|PROTECTED|PUBLIC|PURE|READ|RECURSIVE|RESULT|RETURN|REWIND|SAVE|SELECT|SEQUENCE|STOP|SUBMODULE|SUBROUTINE|SYNC|SYNCALL|SYNCIMAGES|SYNCMEMORY|TARGET|THEN|TYPE|UNLOCK|USE|VALUE|VOLATILE|WHERE|WRITE|WHILE)\s*\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="\b(CHARACTER|COMPLEX|DOUBLE PRECISION|DOUBLE COMPLEX|INTEGER|LOGICAL|REAL|C_INT|C_SHORT|C_LONG|C_LONG_LONG|C_SIGNED_CHAR|C_SIZE_T|C_INT8_T|C_INT16_T|C_INT32_T|C_INT64_T|C_INT_LEAST8_T|C_INT_LEAST16_T|C_INT_LEAST32_T|C_INT_LEAST64_T|C_INT_FAST8_T|C_INT_FAST16_T|C_INT_FAST32_T|C_INT_FAST64_T|C_INTMAX_T|C_INTPTR_T|C_FLOAT|C_DOUBLE|C_LONG_DOUBLE|C_FLOAT_COMPLEX|C_DOUBLE_COMPLEX|C_LONG_DOUBLE_COMPLEX|C_BOOL|C_CHAR|C_PTR|C_FUNPTR)\s*\b">
|
||||
<token type="Keyword"/>
|
||||
</rule>
|
||||
<rule pattern="(\*\*|\*|\+|-|\/|<|>|<=|>=|==|\/=|=)">
|
||||
<token type="Operator"/>
|
||||
@@ -26,7 +35,7 @@
|
||||
<rule pattern="[()\[\],:&%;.]">
|
||||
<token type="Punctuation"/>
|
||||
</rule>
|
||||
<rule pattern="\b(Get_Environment_Variable|Command_Argument_Count|Execute_Command_Line|Get_Command_Argument|Selected_Real_Kind|Selected_Char_Kind|C_Carriage_Return|Selected_Int_Kind|C_Horizontal_Tab|Extends_Type_Of|C_F_ProcPointer|C_Vertical_Tab|Date_and_Time|Is_Contiguous|C_Null_Funptr|Atomic_Define|Is_Iostat_Eor|Iso_C_Binding|Random_Number|Is_Iostat_End|C_Associated|Same_Type_As|Storage_Size|System_Clock|Set_Exponent|ErFC_Scaled|Get_Command|Dot_Product|C_Backspace|C_Null_Char|Image_Index|C_F_Pointer|MinExponent|Random_Seed|MaxExponent|C_Form_Feed|Move_Alloc|C_New_Line|Num_Images|Atomic_Ref|Associated|This_Image|Merge_Bits|C_Null_Ptr|Bessel_Y1|Bessel_JN|Bessel_J1|Bessel_J0|Transpose|RRSpacing|Allocated|Log_Gamma|Precision|Bessel_YN|Bessel_Y0|Bit_Size|Transfer|LCoBound|CPU_Time|UCoBound|Fraction|C_Sizeof|Exponent|ImagPart|C_FunLoc|New_Line|RealPart|Len_Trim|C_Alert|Spacing|Ceiling|Epsilon|AdjustR|Product|Complex|DShiftR|AdjustL|DShiftL|Present|IParity|Reshape|FindLoc|EOShift|Logical|Nearest|Digits|Modulo|DbesYN|MaxVal|TrailZ|MvBits|DbesJN|Second|IDNInt|IAChar|MinVal|IErrNo|RShift|TtyNam|MinLoc|Repeat|Rename|LShift|UBound|System|Parity|HostNm|LBound|SymLnk|MClock|GMTime|GetUId|GetPId|GetLog|PError|Spread|MaxLoc|Unlink|MatMul|GetGId|Signal|GetEnv|IShftC|IsaTty|ShiftA|GetCWD|CShift|LnBlnk|ShiftL|Unpack|Access|GetArg|Verify|GError|ShiftR|DNInt|FSeek|FStat|FTell|Gamma|FPutC|Flush|Floor|Float|FGetC|FDate|Radix|ETime|DTime|Range|DTanH|DSqRt|DSinH|DSign|Hypot|DProd|Scale|Norm2|DErFC|DCosH|IArgC|IBClr|IBits|IBSet|IChar|IDate|DbesY|IDInt|DbesJ|DATan|DASin|DACos|Shape|C_Loc|CTime|Index|CSqRt|Count|Conjg|Cmplx|Short|IShft|ChMod|ISign|ChDir|Sleep|BTest|SRand|ITime|Merge|BesYN|BesJN|ATanH|ASinH|ANInt|UMask|Alarm|AImag|MaskR|MaskL|ACosH|LTime|LStat|AChar|IRand|Abort|ZSqRt|SqRt|ZLog|ZExp|ZCos|Link|ZAbs|ACos|AInt|ALog|Kind|Kill|Long|ZSin|AMin|Imag|IFix|IEOr|Trim|IDiM|IAny|IAnd|Sign|NInt|AMod|Tiny|Null|IAbs|ASin|Pack|Huge|FPut|FNum|FGet|Time|Exit|Rand|ErFC|TanH|DTan|Real|DSin|DMod|DMin|DMax|DLog|DInt|ATan|DExp|Scan|DErF|DDiM|DCos|Dble|DAbs|BesJ|CSin|CosH|BesY|CLog|Char|CExp|SinH|Stat|CCos|Sngl|CAbs|IAll|AMax|IOr|Sin|BGE|Sum|Cos|BGT|DiM|Tan|ErF|Exp|Log|Not|Any|And|BLE|Mod|Min|Int|Len|All|LGe|LGt|Abs|XOr|Max|LLe|LLt|Loc|BLT|Or)\s*\b">
|
||||
<rule pattern="\b(Abort|Abs|Access|AChar|ACos|ACosH|AdjustL|AdjustR|AImag|AInt|Alarm|All|Allocated|ALog|AMax|AMin|AMod|And|ANInt|Any|ASin|ASinH|Associated|ATan|ATanH|Atomic_Define|Atomic_Ref|BesJ|BesJN|Bessel_J0|Bessel_J1|Bessel_JN|Bessel_Y0|Bessel_Y1|Bessel_YN|BesY|BesYN|BGE|BGT|BLE|BLT|Bit_Size|BTest|CAbs|CCos|Ceiling|CExp|Char|ChDir|ChMod|CLog|Cmplx|Command_Argument_Count|Complex|Conjg|Cos|CosH|Count|CPU_Time|CShift|CSin|CSqRt|CTime|C_Loc|C_Associated|C_Null_Ptr|C_Null_Funptr|C_F_Pointer|C_F_ProcPointer|C_Null_Char|C_Alert|C_Backspace|C_Form_Feed|C_FunLoc|C_Sizeof|C_New_Line|C_Carriage_Return|C_Horizontal_Tab|C_Vertical_Tab|DAbs|DACos|DASin|DATan|Date_and_Time|DbesJ|DbesJN|DbesY|DbesYN|Dble|DCos|DCosH|DDiM|DErF|DErFC|DExp|Digits|DiM|DInt|DLog|DMax|DMin|DMod|DNInt|Dot_Product|DProd|DSign|DSinH|DShiftL|DShiftR|DSin|DSqRt|DTanH|DTan|DTime|EOShift|Epsilon|ErF|ErFC|ErFC_Scaled|ETime|Execute_Command_Line|Exit|Exp|Exponent|Extends_Type_Of|FDate|FGet|FGetC|FindLoc|Float|Floor|Flush|FNum|FPutC|FPut|Fraction|FSeek|FStat|FTell|Gamma|GError|GetArg|Get_Command|Get_Command_Argument|Get_Environment_Variable|GetCWD|GetEnv|GetGId|GetLog|GetPId|GetUId|GMTime|HostNm|Huge|Hypot|IAbs|IAChar|IAll|IAnd|IAny|IArgC|IBClr|IBits|IBSet|IChar|IDate|IDiM|IDInt|IDNInt|IEOr|IErrNo|IFix|Imag|ImagPart|Image_Index|Index|Int|IOr|IParity|IRand|IsaTty|IShft|IShftC|ISign|Iso_C_Binding|Is_Contiguous|Is_Iostat_End|Is_Iostat_Eor|ITime|Kill|Kind|LBound|LCoBound|Len|Len_Trim|LGe|LGt|Link|LLe|LLt|LnBlnk|Loc|Log|Log_Gamma|Logical|Long|LShift|LStat|LTime|MaskL|MaskR|MatMul|Max|MaxExponent|MaxLoc|MaxVal|MClock|Merge|Merge_Bits|Move_Alloc|Min|MinExponent|MinLoc|MinVal|Mod|Modulo|MvBits|Nearest|New_Line|NInt|Norm2|Not|Null|Num_Images|Or|Pack|Parity|PError|Precision|Present|Product|Radix|Rand|Random_Number|Random_Seed|Range|Real|RealPart|Rename|Repeat|Reshape|RRSpacing|RShift|Same_Type_As|Scale|Scan|Second|Selected_Char_Kind|Selected_Int_Kind|Selected_Real_Kind|Set_Exponent|Shape|ShiftA|ShiftL|ShiftR|Short|Sign|Signal|SinH|Sin|Sleep|Sngl|Spacing|Spread|SqRt|SRand|Stat|Storage_Size|Sum|SymLnk|System|System_Clock|Tan|TanH|Time|This_Image|Tiny|TrailZ|Transfer|Transpose|Trim|TtyNam|UBound|UCoBound|UMask|Unlink|Unpack|Verify|XOr|ZAbs|ZCos|ZExp|ZLog|ZSin|ZSqRt)\s*\b">
|
||||
<token type="NameBuiltin"/>
|
||||
</rule>
|
||||
<rule pattern="\.(true|false)\.">
|
||||
@@ -45,13 +54,16 @@
|
||||
</rule>
|
||||
</state>
|
||||
<state name="nums">
|
||||
<rule pattern="\d+(?![.e])(_[a-z]\w+)?">
|
||||
<rule pattern="\d+(?![.e])(_([1-9]|[a-z]\w*))?">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<rule pattern="[+-]?\d*\.\d+([ed][-+]?\d+)?(_[a-z]\w+)?">
|
||||
<rule pattern="[+-]?\d*\.\d+([ed][-+]?\d+)?(_([1-9]|[a-z]\w+))?">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="[+-]?\d+\.\d*([ed][-+]?\d+)?(_[a-z]\w+)?">
|
||||
<rule pattern="[+-]?\d+\.\d*([ed][-+]?\d+)?(_([1-9]|[a-z]\w*))?">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
<rule pattern="[+-]?\d+(\.\d*)?[ed][-+]?\d+(_([1-9]|[a-z]\w*))?">
|
||||
<token type="LiteralNumberFloat"/>
|
||||
</rule>
|
||||
</state>
|
||||
@@ -75,8 +87,8 @@
|
||||
<include state="nums"/>
|
||||
</rule>
|
||||
<rule pattern="[\s]+">
|
||||
<token type="Text"/>
|
||||
<token type="TextWhitespace"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+31
-3
@@ -48,10 +48,24 @@
|
||||
<rule pattern="(?:0[xX][a-zA-Z0-9]+|\d+)">
|
||||
<token type="LiteralNumberInteger"/>
|
||||
</rule>
|
||||
<rule pattern="%(?:[a-zA-Z$_][\w$.@-]*|\.[\w$.@-]+)">
|
||||
<token type="NameVariable"/>
|
||||
</rule>
|
||||
<rule pattern="[\r\n]+">
|
||||
<token type="Text"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="([;#]|//).*?\n">
|
||||
<token type="CommentSingle"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="/[*].*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
</rule>
|
||||
<rule pattern="/[*].*?\n[\w\W]*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="punctuation"/>
|
||||
</rule>
|
||||
@@ -99,6 +113,17 @@
|
||||
<token type="Text"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="([;#]|//).*?\n">
|
||||
<token type="CommentSingle"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule pattern="/[*].*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
</rule>
|
||||
<rule pattern="/[*].*?\n[\w\W]*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
<pop depth="1"/>
|
||||
</rule>
|
||||
<rule>
|
||||
<include state="punctuation"/>
|
||||
</rule>
|
||||
@@ -113,9 +138,12 @@
|
||||
<rule pattern="\s+">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
<rule pattern="[;#].*?\n">
|
||||
<token type="Comment"/>
|
||||
<rule pattern="([;#]|//).*?\n">
|
||||
<token type="CommentSingle"/>
|
||||
</rule>
|
||||
<rule pattern="/[*][\w\W]*?[*]/">
|
||||
<token type="CommentMultiline"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+1
@@ -6,6 +6,7 @@
|
||||
<filename>*.js</filename>
|
||||
<filename>*.jsm</filename>
|
||||
<filename>*.mjs</filename>
|
||||
<filename>*.cjs</filename>
|
||||
<mime_type>application/javascript</mime_type>
|
||||
<mime_type>application/x-javascript</mime_type>
|
||||
<mime_type>text/x-javascript</mime_type>
|
||||
|
||||
+2
-2
@@ -92,7 +92,7 @@
|
||||
<rule pattern="#.*?\n">
|
||||
<token type="CommentSingle"/>
|
||||
</rule>
|
||||
<rule pattern="/?(geteduclientinfo|clearspawnpoint|defaultgamemode|transferserver|toggledownfall|immutableworld|detectredstone|setidletimeout|playanimation|classroommode|spreadplayers|testforblocks|setmaxplayers|setworldspawn|testforblock|worldbuilder|createagent|worldborder|camerashake|advancement|raytracefog|locatebiome|tickingarea|replaceitem|attributes|spawnpoint|difficulty|experience|scoreboard|whitelist|structure|playsound|stopsound|forceload|spectate|gamerule|function|schedule|wsserver|teleport|position|save-off|particle|setblock|datapack|mobevent|transfer|gamemode|save-all|bossbar|enchant|trigger|collect|execute|weather|teammsg|tpagent|banlist|dropall|publish|tellraw|testfor|save-on|destroy|ability|locate|summon|remove|effect|reload|ban-ip|recipe|pardon|detect|music|clear|clone|event|mixer|debug|title|ride|stop|list|turn|data|team|kick|loot|tell|help|give|flog|fill|move|time|seed|kill|save|item|deop|code|tag|ban|msg|say|tp|me|op|xp|w)\b">
|
||||
<rule pattern="/?(geteduclientinfo|clearspawnpoint|defaultgamemode|transferserver|toggledownfall|immutableworld|detectredstone|setidletimeout|playanimation|classroommode|spreadplayers|testforblocks|setmaxplayers|setworldspawn|testforblock|worldbuilder|createagent|worldborder|camerashake|advancement|raytracefog|locatebiome|tickingarea|replaceitem|attributes|spawnpoint|difficulty|experience|scoreboard|whitelist|structure|playsound|stopsound|forceload|spectate|gamerule|function|schedule|wsserver|teleport|position|save-off|particle|setblock|datapack|mobevent|transfer|gamemode|save-all|bossbar|enchant|trigger|collect|execute|weather|teammsg|tpagent|banlist|dropall|publish|tellraw|testfor|save-on|destroy|ability|locate|summon|remove|effect|reload|ban-ip|recipe|pardon|detect|music|clear|clone|event|mixer|debug|title|ride|stop|list|turn|data|team|kick|loot|tell|help|give|flog|fill|move|time|seed|kill|save|item|deop|code|tag|ban|msg|say|tp|me|op|xp|w|place)\b">
|
||||
<token type="KeywordReserved"/>
|
||||
</rule>
|
||||
<rule pattern="(@p|@r|@a|@e|@s|@c|@v)">
|
||||
@@ -179,4 +179,4 @@
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<lexer>
|
||||
<config>
|
||||
<name>properties</name>
|
||||
<alias>java-properties</alias>
|
||||
<filename>*.properties</filename>
|
||||
<mime_type>text/x-java-properties</mime_type>
|
||||
</config>
|
||||
<rules>
|
||||
<state name="root">
|
||||
<rule pattern="^([ \t\f]*)([#!].*)">
|
||||
<bygroups>
|
||||
<token type="Text"/>
|
||||
<token type="CommentSingle"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="^([ \t\f]*)(\S+?)([ \t\f]*)([=:])([ \t\f]*)(.*(?:(?<=\\)\n.*)*)">
|
||||
<bygroups>
|
||||
<token type="Text"/>
|
||||
<token type="NameAttribute"/>
|
||||
<token type="Text"/>
|
||||
<token type="Operator"/>
|
||||
<token type="Text"/>
|
||||
<token type="LiteralString"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="^([ \t\f]*)(\S+)([ \t\f]+)(.*(?:(?<=\\)\n.*)*)">
|
||||
<bygroups>
|
||||
<token type="Text"/>
|
||||
<token type="NameAttribute"/>
|
||||
<token type="Text"/>
|
||||
<token type="LiteralString"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="^([ \t\f]*)(\w+)$">
|
||||
<bygroups>
|
||||
<token type="Text"/>
|
||||
<token type="NameAttribute"/>
|
||||
</bygroups>
|
||||
</rule>
|
||||
<rule pattern="\n">
|
||||
<token type="Text"/>
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
+2
-2
@@ -36,7 +36,7 @@
|
||||
<rule pattern="(uniqueidentifier|datetimeoffset|smalldatetime|hierarchyid|sql_variant|smallmoney|varbinary|datetime2|timestamp|datetime|smallint|nvarchar|decimal|tinyint|varchar|numeric|binary|bigint|cursor|image|nchar|money|float|table|ntext|text|time|real|date|char|int|bit|xml)\b">
|
||||
<token type="NameClass"/>
|
||||
</rule>
|
||||
<rule pattern="(get_filestream_transaction_context|datetimeoffsetfromparts|fulltextserviceproperty|fulltextcatalogproperty|current_transaction_id|smalldatetimefromparts|min_active_rowversion|database_principal_id|current_request_id|databasepropertyex|connectionproperty|datetime2fromparts|object_schema_name|indexkey_property|filegroupproperty|current_timestamp|sysdatetimeoffset|has_perms_by_name|object_definition|datetimefromparts|assemblyproperty|todatetimeoffset|original_db_name|objectpropertyex|is_srvrolemember|error_procedure|binary_checksum|session_context|sp_helplanguage|newsequentialid|error_severity|original_login|objectproperty|opendatasource|columnproperty|sysutcdatetime|scope_identity|serverproperty|certprivatekey|filegroup_name|cursor_status|formatmessage|indexproperty|is_rolemember|datefromparts|timefromparts|string_escape|error_message|switchoffset|filegroup_id|string_split|current_user|checksum_agg|applock_test|session_user|typeproperty|rowcount_big|context_info|fileproperty|error_number|datediff_big|applock_mode|sysdatetime|error_state|json_modify|grouping_id|getansinull|try_convert|certencoded|system_user|permissions|schema_name|object_name|suser_sname|difference|xact_state|json_value|\$partition|row_number|json_query|getutcdate|col_length|decompress|error_line|openrowset|stats_date|pwdcompare|dense_rank|pwdencrypt|suser_name|is_member|index_col|quotename|host_name|charindex|parsename|isnumeric|user_name|replicate|openquery|count_big|type_name|object_id|try_parse|file_idex|substring|file_name|textvalid|suser_sid|schema_id|compress|grouping|suser_id|app_name|try_cast|col_name|datepart|datename|datediff|checksum|patindex|openjson|getdate|convert|openxml|file_id|db_name|radians|ceiling|unicode|reverse|dateadd|soundex|degrees|eomonth|user_id|type_id|host_id|textptr|replace|concat|square|isdate|stdevp|isjson|isnull|choose|format|stuff|floor|round|right|upper|log10|rtrim|lower|power|ltrim|space|db_id|month|parse|stdev|nchar|newid|count|ntile|ascii|char|year|varp|rank|left|sign|cast|acos|atn2|atan|asin|rand|sqrt|sin|max|min|tan|avg|cos|log|len|abs|cot|str|iif|exp|var|day|sum|pi)\b">
|
||||
<rule pattern="(get_filestream_transaction_context|datetimeoffsetfromparts|fulltextserviceproperty|fulltextcatalogproperty|current_transaction_id|smalldatetimefromparts|min_active_rowversion|database_principal_id|current_request_id|databasepropertyex|connectionproperty|datetime2fromparts|object_schema_name|indexkey_property|filegroupproperty|current_timestamp|sysdatetimeoffset|has_perms_by_name|object_definition|datetimefromparts|assemblyproperty|todatetimeoffset|original_db_name|objectpropertyex|is_srvrolemember|error_procedure|binary_checksum|session_context|sp_helplanguage|newsequentialid|error_severity|original_login|objectproperty|opendatasource|columnproperty|sysutcdatetime|scope_identity|serverproperty|certprivatekey|filegroup_name|cursor_status|formatmessage|indexproperty|is_rolemember|datefromparts|timefromparts|string_escape|error_message|switchoffset|filegroup_id|string_split|string_agg|current_user|checksum_agg|applock_test|session_user|typeproperty|rowcount_big|context_info|fileproperty|error_number|datediff_big|applock_mode|sysdatetime|error_state|json_modify|grouping_id|getansinull|try_convert|certencoded|system_user|permissions|schema_name|object_name|suser_sname|difference|xact_state|json_value|\$partition|row_number|json_query|getutcdate|col_length|decompress|error_line|openrowset|stats_date|pwdcompare|dense_rank|pwdencrypt|suser_name|is_member|index_col|quotename|host_name|charindex|parsename|isnumeric|user_name|replicate|openquery|count_big|type_name|object_id|try_parse|file_idex|substring|file_name|textvalid|suser_sid|schema_id|compress|grouping|suser_id|app_name|try_cast|col_name|datepart|datename|datediff|checksum|patindex|openjson|getdate|convert|openxml|file_id|db_name|radians|ceiling|unicode|reverse|dateadd|soundex|degrees|eomonth|user_id|type_id|host_id|textptr|replace|concat|concat_ws|square|isdate|stdevp|isjson|isnull|choose|format|stuff|floor|round|right|upper|log10|rtrim|lower|power|ltrim|space|db_id|month|parse|stdev|nchar|newid|count|ntile|ascii|char|year|varp|rank|left|sign|cast|acos|atn2|atan|asin|rand|sqrt|sin|max|min|tan|avg|cos|log|len|abs|cot|str|iif|exp|var|day|sum|pi)\b">
|
||||
<token type="NameFunction"/>
|
||||
</rule>
|
||||
<rule pattern="(goto)(\s+)(\w+\b)">
|
||||
@@ -134,4 +134,4 @@
|
||||
</rule>
|
||||
</state>
|
||||
</rules>
|
||||
</lexer>
|
||||
</lexer>
|
||||
|
||||
+5
-5
@@ -19,18 +19,18 @@ var FortranFixed = Register(MustNewLexer(
|
||||
"root": {
|
||||
{`[C*].*\n`, Comment, nil},
|
||||
{`#.*\n`, CommentPreproc, nil},
|
||||
{`[\t ]*!.*\n`, Comment, nil},
|
||||
{` {0,4}!.*\n`, Comment, nil},
|
||||
{`(.{5})`, NameLabel, Push("cont-char")},
|
||||
{`.*\n`, Using("Fortran"), nil},
|
||||
},
|
||||
"cont-char": {
|
||||
{` `, Text, Push("code")},
|
||||
{`0`, Comment, Push("code")},
|
||||
{` `, TextWhitespace, Push("code")},
|
||||
{`.`, GenericStrong, Push("code")},
|
||||
},
|
||||
"code": {
|
||||
{`(.{66})(.*)(\n)`, ByGroups(Using("Fortran"), Comment, Text), Push("root")},
|
||||
{`.*\n`, Using("Fortran"), Push("root")},
|
||||
{`(.{66})(.*)(\n)`, ByGroups(Using("Fortran"), Comment, TextWhitespace), Push("root")},
|
||||
{`(.*)(!.*)(\n)`, ByGroups(Using("Fortran"), Comment, TextWhitespace), Push("root")},
|
||||
{`(.*)(\n)`, ByGroups(Using("Fortran"), TextWhitespace), Push("root")},
|
||||
Default(Push("root")),
|
||||
},
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ var Makefile = Register(MustNewLexer(
|
||||
&Config{
|
||||
Name: "Base Makefile",
|
||||
Aliases: []string{"make", "makefile", "mf", "bsdmake"},
|
||||
Filenames: []string{"*.mak", "*.mk", "Makefile", "makefile", "Makefile.*", "GNUmakefile"},
|
||||
Filenames: []string{"*.mak", "*.mk", "Makefile", "makefile", "Makefile.*", "GNUmakefile", "BSDmakefile"},
|
||||
MimeTypes: []string{"text/x-makefile"},
|
||||
EnsureNL: true,
|
||||
},
|
||||
|
||||
+24
-2
@@ -157,9 +157,12 @@ func (s *StyleBuilder) AddAll(entries StyleEntries) *StyleBuilder {
|
||||
}
|
||||
|
||||
func (s *StyleBuilder) Get(ttype TokenType) StyleEntry {
|
||||
// This is less than ideal, but it's the price for having to check errors on each Add().
|
||||
// This is less than ideal, but it's the price for not having to check errors on each Add().
|
||||
entry, _ := ParseStyleEntry(s.entries[ttype])
|
||||
return entry.Inherit(s.parent.Get(ttype))
|
||||
if s.parent != nil {
|
||||
entry = entry.Inherit(s.parent.Get(ttype))
|
||||
}
|
||||
return entry
|
||||
}
|
||||
|
||||
// Add an entry to the Style map.
|
||||
@@ -175,6 +178,25 @@ func (s *StyleBuilder) AddEntry(ttype TokenType, entry StyleEntry) *StyleBuilder
|
||||
return s
|
||||
}
|
||||
|
||||
// Transform passes each style entry currently defined in the builder to the supplied
|
||||
// function and saves the returned value. This can be used to adjust a style's colours;
|
||||
// see Colour's ClampBrightness function, for example.
|
||||
func (s *StyleBuilder) Transform(transform func(StyleEntry) StyleEntry) *StyleBuilder {
|
||||
types := make(map[TokenType]struct{})
|
||||
for tt := range s.entries {
|
||||
types[tt] = struct{}{}
|
||||
}
|
||||
if s.parent != nil {
|
||||
for _, tt := range s.parent.Types() {
|
||||
types[tt] = struct{}{}
|
||||
}
|
||||
}
|
||||
for tt := range types {
|
||||
s.AddEntry(tt, transform(s.Get(tt)))
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *StyleBuilder) Build() (*Style, error) {
|
||||
style := &Style{
|
||||
Name: s.name,
|
||||
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
package styles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// colors used from https://github.com/primer/primitives
|
||||
ghRed2 = "#ffa198"
|
||||
ghRed3 = "#ff7b72"
|
||||
ghRed9 = "#490202"
|
||||
ghOrange2 = "#ffa657"
|
||||
ghOrange3 = "#f0883e"
|
||||
ghGreen1 = "#7ee787"
|
||||
ghGreen2 = "#56d364"
|
||||
ghGreen7 = "#0f5323"
|
||||
ghBlue1 = "#a5d6ff"
|
||||
ghBlue2 = "#79c0ff"
|
||||
ghPurple2 = "#d2a8ff"
|
||||
ghGray3 = "#8b949e"
|
||||
ghGray4 = "#6e7681"
|
||||
ghFgSubtle = "#6e7681"
|
||||
ghFgDefault = "#c9d1d9"
|
||||
ghBgDefault = "#0d1117"
|
||||
ghDangerFg = "#f85149"
|
||||
)
|
||||
|
||||
// GitHub Dark style.
|
||||
var GitHubDark = Register(chroma.MustNewStyle("github-dark", chroma.StyleEntries{
|
||||
// Default Token Style
|
||||
chroma.Background: fmt.Sprintf("bg:%s %s", ghBgDefault, ghFgDefault),
|
||||
|
||||
chroma.LineNumbers: ghGray4,
|
||||
// has transparency in VS Code theme as `colors.codemirror.activelineBg`
|
||||
chroma.LineHighlight: ghGray4,
|
||||
|
||||
chroma.Error: ghDangerFg,
|
||||
|
||||
chroma.Keyword: ghRed3,
|
||||
chroma.KeywordConstant: ghBlue2,
|
||||
chroma.KeywordPseudo: ghBlue2,
|
||||
|
||||
chroma.Name: ghFgDefault,
|
||||
chroma.NameClass: "bold " + ghOrange3,
|
||||
chroma.NameConstant: "bold " + ghBlue2,
|
||||
chroma.NameDecorator: "bold " + ghPurple2,
|
||||
chroma.NameEntity: ghOrange2,
|
||||
chroma.NameException: "bold " + ghOrange3,
|
||||
chroma.NameFunction: "bold " + ghPurple2,
|
||||
chroma.NameLabel: "bold " + ghBlue2,
|
||||
chroma.NameNamespace: ghRed3,
|
||||
chroma.NameProperty: ghBlue2,
|
||||
chroma.NameTag: ghGreen1,
|
||||
chroma.NameVariable: ghBlue2,
|
||||
|
||||
chroma.Literal: ghBlue1,
|
||||
chroma.LiteralDate: ghBlue2,
|
||||
chroma.LiteralStringAffix: ghBlue2,
|
||||
chroma.LiteralStringDelimiter: ghBlue2,
|
||||
chroma.LiteralStringEscape: ghBlue2,
|
||||
chroma.LiteralStringHeredoc: ghBlue2,
|
||||
chroma.LiteralStringRegex: ghBlue2,
|
||||
|
||||
chroma.Operator: "bold " + ghRed3,
|
||||
|
||||
chroma.Comment: "italic " + ghGray3,
|
||||
chroma.CommentPreproc: "bold " + ghGray3,
|
||||
chroma.CommentSpecial: "bold italic " + ghGray3,
|
||||
|
||||
chroma.Generic: ghFgDefault,
|
||||
chroma.GenericDeleted: fmt.Sprintf("bg:%s %s", ghRed9, ghRed2),
|
||||
chroma.GenericEmph: "italic",
|
||||
chroma.GenericError: ghRed2,
|
||||
chroma.GenericHeading: "bold " + ghBlue2,
|
||||
chroma.GenericInserted: fmt.Sprintf("bg:%s %s", ghGreen7, ghGreen2),
|
||||
chroma.GenericOutput: ghGray3,
|
||||
chroma.GenericPrompt: ghGray3,
|
||||
chroma.GenericStrong: "bold",
|
||||
chroma.GenericSubheading: ghBlue2,
|
||||
chroma.GenericTraceback: ghRed3,
|
||||
chroma.GenericUnderline: "underline",
|
||||
|
||||
chroma.TextWhitespace: ghFgSubtle,
|
||||
}))
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package styles
|
||||
|
||||
import (
|
||||
"github.com/alecthomas/chroma/v2"
|
||||
)
|
||||
|
||||
// Gruvbox light style.
|
||||
var GruvboxLight = Register(chroma.MustNewStyle("gruvbox-light", chroma.StyleEntries{
|
||||
chroma.CommentPreproc: "noinherit #427B58",
|
||||
chroma.Comment: "#928374 italic",
|
||||
chroma.GenericDeleted: "noinherit #282828 bg:#9D0006",
|
||||
chroma.GenericEmph: "#076678 underline",
|
||||
chroma.GenericError: "bg:#9D0006 bold",
|
||||
chroma.GenericHeading: "#79740E bold",
|
||||
chroma.GenericInserted: "noinherit #282828 bg:#79740E",
|
||||
chroma.GenericOutput: "noinherit #504945",
|
||||
chroma.GenericPrompt: "#3C3836",
|
||||
chroma.GenericStrong: "#3C3836",
|
||||
chroma.GenericSubheading: "#79740E bold",
|
||||
chroma.GenericTraceback: "bg:#3C3836 bold",
|
||||
chroma.Generic: "#3C3836",
|
||||
chroma.KeywordType: "noinherit #B57614",
|
||||
chroma.Keyword: "noinherit #AF3A03",
|
||||
chroma.NameAttribute: "#79740E bold",
|
||||
chroma.NameBuiltin: "#B57614",
|
||||
chroma.NameConstant: "noinherit #d3869b",
|
||||
chroma.NameEntity: "noinherit #B57614",
|
||||
chroma.NameException: "noinherit #fb4934",
|
||||
chroma.NameFunction: "#B57614",
|
||||
chroma.NameLabel: "noinherit #9D0006",
|
||||
chroma.NameTag: "noinherit #9D0006",
|
||||
chroma.NameVariable: "noinherit #3C3836",
|
||||
chroma.Name: "#3C3836",
|
||||
chroma.LiteralNumberFloat: "noinherit #8F3F71",
|
||||
chroma.LiteralNumber: "noinherit #8F3F71",
|
||||
chroma.Operator: "#AF3A03",
|
||||
chroma.LiteralStringSymbol: "#076678",
|
||||
chroma.LiteralString: "noinherit #79740E",
|
||||
chroma.Background: "noinherit #3C3836 bg:#FBF1C7 bg:#FBF1C7",
|
||||
}))
|
||||
+4
@@ -112,6 +112,10 @@ func ShouldSkipLimiter(lmt *limiter.Limiter, r *http.Request) bool {
|
||||
requestHeadersDefinedInLimiter = false
|
||||
|
||||
for headerKey, headerValues := range lmtHeaders {
|
||||
if len(headerValues) == 0 {
|
||||
requestHeadersDefinedInLimiter = true
|
||||
continue
|
||||
}
|
||||
for _, headerValue := range headerValues {
|
||||
if r.Header.Get(headerKey) == headerValue {
|
||||
requestHeadersDefinedInLimiter = true
|
||||
|
||||
+1
@@ -175,6 +175,7 @@ if !ok || !token.Valid {
|
||||
- [XORM (MySQL, client and token store)](https://github.com/rainlay/go-oauth2-xorm)
|
||||
- [GORM](https://github.com/techknowlogick/go-oauth2-gorm)
|
||||
- [Firestore](https://github.com/tslamic/go-oauth2-firestore)
|
||||
- [Hazelcast](https://github.com/clowre/go-oauth2-hazelcast) (token only)
|
||||
|
||||
## Handy Utilities
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ type Manager interface {
|
||||
GenerateAuthToken(ctx context.Context, rt ResponseType, tgr *TokenGenerateRequest) (authToken TokenInfo, err error)
|
||||
|
||||
// generate the access token
|
||||
GenerateAccessToken(ctx context.Context, rt GrantType, tgr *TokenGenerateRequest) (accessToken TokenInfo, err error)
|
||||
GenerateAccessToken(ctx context.Context, gt GrantType, tgr *TokenGenerateRequest) (accessToken TokenInfo, err error)
|
||||
|
||||
// refreshing an access token
|
||||
RefreshAccessToken(ctx context.Context, tgr *TokenGenerateRequest) (accessToken TokenInfo, err error)
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ type (
|
||||
UserAuthorizationHandler func(w http.ResponseWriter, r *http.Request) (userID string, err error)
|
||||
|
||||
// PasswordAuthorizationHandler get user id from username and password
|
||||
PasswordAuthorizationHandler func(ctx context.Context, username, password string) (userID string, err error)
|
||||
PasswordAuthorizationHandler func(ctx context.Context, clientID, username, password string) (userID string, err error)
|
||||
|
||||
// RefreshingScopeHandler check the scope of the refreshing token
|
||||
RefreshingScopeHandler func(tgr *oauth2.TokenGenerateRequest, oldScope string) (allowed bool, err error)
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ func NewServer(cfg *Config, manager oauth2.Manager) *Server {
|
||||
return "", errors.ErrAccessDenied
|
||||
}
|
||||
|
||||
srv.PasswordAuthorizationHandler = func(ctx context.Context, username, password string) (string, error) {
|
||||
srv.PasswordAuthorizationHandler = func(ctx context.Context, clientID, username, password string) (string, error) {
|
||||
return "", errors.ErrAccessDenied
|
||||
}
|
||||
return srv
|
||||
@@ -357,7 +357,7 @@ func (s *Server) ValidationTokenRequest(r *http.Request) (oauth2.GrantType, *oau
|
||||
return "", nil, errors.ErrInvalidRequest
|
||||
}
|
||||
|
||||
userID, err := s.PasswordAuthorizationHandler(r.Context(), username, password)
|
||||
userID, err := s.PasswordAuthorizationHandler(r.Context(), clientID, username, password)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
} else if userID == "" {
|
||||
|
||||
+20
@@ -210,6 +210,26 @@ The API for this provider:
|
||||
|
||||
The provider acts like any other, i.e. will be registered as `/auth/email/login`.
|
||||
|
||||
### Email
|
||||
|
||||
For email notify provider, please use `github.com/go-pkgz/auth/provider/sender` package:
|
||||
```go
|
||||
sndr := sender.NewEmailClient(sender.EmailParams{
|
||||
Host: "email.hostname",
|
||||
Port: 567,
|
||||
SMTPUserName: "username",
|
||||
SMTPPassword: "pass",
|
||||
StartTLS: true,
|
||||
From: "notify@email.hostname",
|
||||
Subject: "subject",
|
||||
ContentType: "text/html",
|
||||
Charset: "UTF-8",
|
||||
}, log.Default())
|
||||
authenticator.AddVerifProvider("email", "template goes here", sndr)
|
||||
```
|
||||
|
||||
See [that documentation](https://github.com/go-pkgz/email#options) for full options list.
|
||||
|
||||
### Telegram
|
||||
|
||||
Telegram provider allows your users to log in with Telegram account. First, you will need to create your bot.
|
||||
|
||||
+2
-5
@@ -1,7 +1,5 @@
|
||||
package avatar
|
||||
|
||||
//go:generate sh -c "mockery -inpkg -name Store -print > /tmp/mock.tmp && mv /tmp/mock.tmp store_mock.go"
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1" //nolint gosec
|
||||
@@ -48,14 +46,13 @@ func NewStore(uri string) (Store, error) {
|
||||
return NewLocalFS(strings.TrimPrefix(uri, "file://")), nil
|
||||
case !strings.Contains(uri, "://"):
|
||||
return NewLocalFS(uri), nil
|
||||
case strings.HasPrefix(uri, "mongodb://"):
|
||||
|
||||
case strings.HasPrefix(uri, "mongodb://"), strings.HasPrefix(uri, "mongodb+srv://"):
|
||||
db, bucketName, u, err := parseExtMongoURI(uri)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't parse mongo store uri %s: %w", uri, err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
|
||||
defer cancel()
|
||||
|
||||
client, err := mongo.Connect(ctx, options.Client().ApplyURI(u))
|
||||
|
||||
+56
-146
@@ -2,24 +2,18 @@
|
||||
package sender
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/quotedprintable"
|
||||
"net"
|
||||
"net/smtp"
|
||||
"time"
|
||||
|
||||
"github.com/go-pkgz/auth/logger"
|
||||
"github.com/go-pkgz/email"
|
||||
)
|
||||
|
||||
// Email implements sender interface for VerifyHandler
|
||||
// Uses common subject line and "from" for all messages
|
||||
type Email struct {
|
||||
logger.L
|
||||
SMTPClient
|
||||
EmailParams
|
||||
logger.L
|
||||
sender *email.Sender
|
||||
}
|
||||
|
||||
// EmailParams with all needed to make new Email client with smtp
|
||||
@@ -28,148 +22,64 @@ type EmailParams struct {
|
||||
Port int // SMTP port
|
||||
From string // From email field
|
||||
Subject string // Email subject
|
||||
ContentType string // Content type, optional. Will trigger MIME and Content-Type headers
|
||||
ContentType string // Content type
|
||||
|
||||
TLS bool // TLS auth
|
||||
SMTPUserName string // user name
|
||||
SMTPPassword string // password
|
||||
TimeOut time.Duration
|
||||
TLS bool // TLS auth
|
||||
StartTLS bool // StartTLS auth
|
||||
Charset string // Character set
|
||||
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
|
||||
SMTPUserName string // username
|
||||
SMTPPassword string // password
|
||||
TimeOut time.Duration // TCP connection timeout
|
||||
}
|
||||
|
||||
// SMTPClient interface defines subset of net/smtp used by email client
|
||||
type SMTPClient interface {
|
||||
Mail(string) error
|
||||
Auth(smtp.Auth) error
|
||||
Rcpt(string) error
|
||||
Data() (io.WriteCloser, error)
|
||||
Quit() error
|
||||
Close() error
|
||||
}
|
||||
// NewEmailClient creates email client
|
||||
func NewEmailClient(emailParams EmailParams, l logger.L) *Email {
|
||||
var opts []email.Option
|
||||
|
||||
// NewEmailClient creates email client with prepared smtp
|
||||
func NewEmailClient(p EmailParams, l logger.L) *Email {
|
||||
return &Email{EmailParams: p, L: l, SMTPClient: nil}
|
||||
if emailParams.SMTPUserName != "" {
|
||||
opts = append(opts, email.Auth(emailParams.SMTPUserName, emailParams.SMTPPassword))
|
||||
}
|
||||
|
||||
if emailParams.ContentType != "" {
|
||||
opts = append(opts, email.ContentType(emailParams.ContentType))
|
||||
}
|
||||
|
||||
if emailParams.Charset != "" {
|
||||
opts = append(opts, email.Charset(emailParams.Charset))
|
||||
}
|
||||
|
||||
if emailParams.LoginAuth {
|
||||
opts = append(opts, email.LoginAuth())
|
||||
}
|
||||
|
||||
if emailParams.Port != 0 {
|
||||
opts = append(opts, email.Port(emailParams.Port))
|
||||
}
|
||||
|
||||
if emailParams.TimeOut != 0 {
|
||||
opts = append(opts, email.TimeOut(emailParams.TimeOut))
|
||||
}
|
||||
|
||||
if emailParams.TLS {
|
||||
opts = append(opts, email.TLS(true))
|
||||
}
|
||||
|
||||
if emailParams.StartTLS {
|
||||
opts = append(opts, email.STARTTLS(true))
|
||||
}
|
||||
|
||||
sender := email.NewSender(emailParams.Host, opts...)
|
||||
|
||||
return &Email{EmailParams: emailParams, L: l, sender: sender}
|
||||
}
|
||||
|
||||
// Send email with given text
|
||||
// If SMTPClient defined in Email struct it will be used, if not - new smtp.Client on each send.
|
||||
// Always closes client on completion or failure.
|
||||
func (em *Email) Send(to, text string) error {
|
||||
em.Logf("[DEBUG] send %q to %s", text, to)
|
||||
client := em.SMTPClient
|
||||
if client == nil { // if client not set make new net/smtp
|
||||
c, err := em.client()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to make smtp client: %w", err)
|
||||
}
|
||||
client = c
|
||||
}
|
||||
|
||||
var quit bool
|
||||
defer func() {
|
||||
if quit { // quit set if Quit() call passed because it's closing connection as well.
|
||||
return
|
||||
}
|
||||
if err := client.Close(); err != nil {
|
||||
em.Logf("[WARN] can't close smtp connection, %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if em.SMTPUserName != "" && em.SMTPPassword != "" {
|
||||
auth := smtp.PlainAuth("", em.SMTPUserName, em.SMTPPassword, em.Host)
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("failed to auth to smtp %s:%d: %w", em.Host, em.Port, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := client.Mail(em.From); err != nil {
|
||||
return fmt.Errorf("bad from address %q: %w", em.From, err)
|
||||
}
|
||||
if err := client.Rcpt(to); err != nil {
|
||||
return fmt.Errorf("bad to address %q: %w", to, err)
|
||||
}
|
||||
|
||||
writer, err := client.Data()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't make email writer: %w", err)
|
||||
}
|
||||
|
||||
msg, err := em.buildMessage(text, to)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't make email message: %w", err)
|
||||
}
|
||||
buf := bytes.NewBufferString(msg)
|
||||
if _, err = buf.WriteTo(writer); err != nil {
|
||||
return fmt.Errorf("failed to send email body to %q: %w", to, err)
|
||||
}
|
||||
if err = writer.Close(); err != nil {
|
||||
em.Logf("[WARN] can't close smtp body writer, %v", err)
|
||||
}
|
||||
|
||||
if err = client.Quit(); err != nil {
|
||||
em.Logf("[WARN] failed to send quit command to %s:%d, %v", em.Host, em.Port, err)
|
||||
} else {
|
||||
quit = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (em *Email) client() (c *smtp.Client, err error) {
|
||||
srvAddress := fmt.Sprintf("%s:%d", em.Host, em.Port)
|
||||
if em.TLS {
|
||||
tlsConf := &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
ServerName: em.Host,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
conn, e := tls.Dial("tcp", srvAddress, tlsConf)
|
||||
if e != nil {
|
||||
return nil, fmt.Errorf("failed to dial smtp tls to %s: %w", srvAddress, e)
|
||||
}
|
||||
if c, err = smtp.NewClient(conn, em.Host); err != nil {
|
||||
return nil, fmt.Errorf("failed to make smtp client for %s: %w", srvAddress, err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", srvAddress, em.TimeOut)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("timeout connecting to %s: %w", srvAddress, err)
|
||||
}
|
||||
|
||||
c, err = smtp.NewClient(conn, em.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial: %w", err)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (em *Email) buildMessage(msg, to string) (message string, err error) {
|
||||
addHeader := func(msg, h, v string) string {
|
||||
msg += fmt.Sprintf("%s: %s\n", h, v)
|
||||
return msg
|
||||
}
|
||||
message = addHeader(message, "From", em.From)
|
||||
message = addHeader(message, "To", to)
|
||||
message = addHeader(message, "Subject", em.Subject)
|
||||
message = addHeader(message, "Content-Transfer-Encoding", "quoted-printable")
|
||||
|
||||
if em.ContentType != "" {
|
||||
message = addHeader(message, "MIME-version", "1.0")
|
||||
message = addHeader(message, "Content-Type", em.ContentType+`; charset="UTF-8"`)
|
||||
}
|
||||
message = addHeader(message, "Date", time.Now().Format(time.RFC1123Z))
|
||||
|
||||
buff := &bytes.Buffer{}
|
||||
qp := quotedprintable.NewWriter(buff)
|
||||
if _, err := qp.Write([]byte(msg)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// flush now, must NOT use defer, for small body, defer may cause buff.String() got empty body
|
||||
if err := qp.Close(); err != nil {
|
||||
return "", fmt.Errorf("quotedprintable Write failed: %w", err)
|
||||
}
|
||||
m := buff.String()
|
||||
message += "\n" + m
|
||||
return message, nil
|
||||
func (e *Email) Send(to, text string) error {
|
||||
e.Logf("[DEBUG] send %q to %s", text, to)
|
||||
return e.sender.Send(text, email.Params{
|
||||
From: e.From,
|
||||
To: []string{to},
|
||||
Subject: e.Subject,
|
||||
})
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
package provider
|
||||
|
||||
//go:generate moq -out telegram_moq_test.go . TelegramAPI
|
||||
//go:generate moq --out telegram_moq_test.go . TelegramAPI
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
+14
-18
@@ -31,29 +31,25 @@ linters-settings:
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- revive
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
- gas
|
||||
- gochecknoinits
|
||||
- gocritic
|
||||
- nakedret
|
||||
- gocyclo
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- megacheck
|
||||
- misspell
|
||||
- nakedret
|
||||
- prealloc
|
||||
- revive
|
||||
- stylecheck
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
fast: false
|
||||
disable-all: true
|
||||
|
||||
|
||||
+20
-19
@@ -2,17 +2,16 @@
|
||||
|
||||
[](https://github.com/go-pkgz/email/actions) [](https://coveralls.io/github/go-pkgz/email?branch=master) [](https://pkg.go.dev/github.com/go-pkgz/email)
|
||||
|
||||
|
||||
The library is a wrapper around the stdlib `net/smtp` simplifying email sending. It supports authentication, SSL/TLS,
|
||||
The library is a wrapper around the stdlib `net/smtp` simplifying email sending. It supports authentication, SSL/TLS,
|
||||
user-specified SMTP servers, content-type, charset, multiple recipients and more.
|
||||
|
||||
|
||||
Usage example:
|
||||
|
||||
```go
|
||||
client := email.NewSender("localhost", email.ContentType("text/html"), email.Auth("user", "pass"))
|
||||
err := client.Send("<html>some content, foo bar</html>",
|
||||
email.Params{From: "me@example.com", To: []string{"to@example.com"}, Subject: "Hello world!",
|
||||
Attachments: []string{"/path/to/file1.txt", "/path/to/file2.txt"},
|
||||
err := client.Send("<html>some content, foo bar</html>",
|
||||
email.Params{From: "me@example.com", To: []string{"to@example.com"}, Subject: "Hello world!",
|
||||
Attachments: []string{"/path/to/file1.txt", "/path/to/file2.txt"},
|
||||
InlineImages: []string{"/path/to/image1.png", "/path/to/image2.png"},
|
||||
})
|
||||
```
|
||||
@@ -25,6 +24,7 @@ err := client.Send("<html>some content, foo bar</html>",
|
||||
- `TLS`: Use TLS SMTP (default: false)
|
||||
- `STARTTLS`: Use STARTTLS (default: false)
|
||||
- `Auth(user, password)`: Username and password for SMTP authentication (default: empty, no authentication)
|
||||
- `LoginAuth`: Use [LOGIN mechanism](https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt) instead of PLAIN mechanism for SMTP authentication, e.g. this is relevant for Office 365 and Outlook.com
|
||||
- `ContentType`: Content type for the email (default: "text/plain")
|
||||
- `Charset`: Charset for the email (default: "utf-8")
|
||||
- `TimeOut`: Timeout for the SMTP connection (default: 30 seconds)
|
||||
@@ -41,16 +41,17 @@ To send email user need to create a sender first and then use `Send` method. The
|
||||
|
||||
- email content (string)
|
||||
- parameters (`email.Params`)
|
||||
```go
|
||||
type Params struct {
|
||||
From string // From email field
|
||||
To []string // From email field
|
||||
Subject string // Email subject
|
||||
UnsubscribeLink string // POST, https://support.google.com/mail/answer/81126 -> "Use one-click unsubscribe"
|
||||
Attachments []string // Attachments path
|
||||
InlineImages []string // Embedding directly to email body. Autogenerated Content-Id (cid) equals to file name
|
||||
}
|
||||
```
|
||||
```go
|
||||
type Params struct {
|
||||
From string // From email field
|
||||
To []string // From email field
|
||||
Subject string // Email subject
|
||||
UnsubscribeLink string // POST, https://support.google.com/mail/answer/81126 -> "Use one-click unsubscribe"
|
||||
InReplyTo string // Identifier for email group (category), used for email grouping
|
||||
Attachments []string // Attachments path
|
||||
InlineImages []string // Embedding directly to email body. Autogenerated Content-Id (cid) equals to file name
|
||||
}
|
||||
```
|
||||
|
||||
See [go docs](https://pkg.go.dev/github.com/go-pkgz/email#Sender.Send) for `Send` function.
|
||||
|
||||
@@ -60,11 +61,11 @@ See [go docs](https://pkg.go.dev/github.com/go-pkgz/email#Sender.Send) for `Send
|
||||
- Custom SMTP client (`smtp.Client` from stdlib) can be set by user with `SMTP` option. In this case it will be used instead of making a new smtp client internally.
|
||||
- Logger can be set with `Log` option. It should implement `email.Logger` interface with a single `Logf(format string, args ...interface{})` method. By default, "no logging" internal logger is used. This interface is compatible with the `go-pkgz/lgr` logger.
|
||||
- The library has no external dependencies, except for testing. It uses the stdlib `net/smtp` package.
|
||||
- SSL/TLS supported with `TLS` option. Pls note: this is not the same as `STARTTLS` (not supported) which is usually on port 587 vs SSL/TLS on port 465.
|
||||
- SSL/TLS supported with `TLS` option (usually on port 465) as well as with `STARTTLS` (usually on port 587).
|
||||
|
||||
## limitations
|
||||
|
||||
This library is not intended to be used for sending a lot of massive emails with
|
||||
This library is not intended to be used for sending a lot of massive emails with
|
||||
low latency requirements. The intended use case is sending simple messages, like alerts, notification and so on.
|
||||
For example, sending alerts from a monitoring system, or for authentication-related emails, i.e. "password reset email",
|
||||
For example, sending alerts from a monitoring system, or for authentication-related emails, i.e. "password reset email",
|
||||
"verification email", etc.
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package email
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
// authMethod is SMTP authentication method
|
||||
type authMethod string
|
||||
|
||||
// List of supported authentication methods
|
||||
const (
|
||||
authMethodPlain authMethod = "PLAIN"
|
||||
authMethodLogin authMethod = "LOGIN"
|
||||
)
|
||||
|
||||
// newLoginAuth returns smtp.Auth that implements the LOGIN authentication
|
||||
// mechanism as defined in the LOGIN SASL Mechanism document,
|
||||
// https://www.ietf.org/archive/id/draft-murchison-sasl-login-00.txt.
|
||||
// The returned smtp.Auth uses the given username and password to authenticate
|
||||
// to the host.
|
||||
//
|
||||
// LOGIN will only send the credentials if the connection is using TLS
|
||||
// or is connected to localhost. Otherwise authentication will fail with an
|
||||
// error, without sending the credentials.
|
||||
//
|
||||
// LOGIN is described as obsolete in the SASL Mechanisms document
|
||||
// but the mechanism is still in use, e.g. in Office 365 and Outlook.com.
|
||||
func newLoginAuth(usr, pwd, host string) smtp.Auth {
|
||||
return &loginAuth{usr, pwd, host}
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
user string
|
||||
password string
|
||||
host string
|
||||
}
|
||||
|
||||
func isLocalhost(name string) bool {
|
||||
return name == "localhost" || name == "127.0.0.1" || name == "::1"
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (proto string, toServer []byte, err error) {
|
||||
if !server.TLS && !isLocalhost(server.Name) {
|
||||
return "", nil, errors.New("unencrypted connection")
|
||||
}
|
||||
if server.Name != a.host {
|
||||
return "", nil, errors.New("wrong host name")
|
||||
}
|
||||
|
||||
return "LOGIN", []byte(a.user), nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) (toServer []byte, err error) {
|
||||
if more {
|
||||
return []byte(a.password), nil
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
+28
-9
@@ -28,13 +28,14 @@ import (
|
||||
type Sender struct {
|
||||
smtpClient SMTPClient
|
||||
logger Logger
|
||||
host string // SMTP host
|
||||
port int // SMTP port
|
||||
contentType string // Content type, optional. Will trigger MIME and Content-Type headers
|
||||
tls bool // TLS auth
|
||||
starttls bool // StartTLS
|
||||
smtpUserName string // username
|
||||
smtpPassword string // password
|
||||
host string // SMTP host
|
||||
port int // SMTP port
|
||||
contentType string // Content type, optional. Will trigger MIME and Content-Type headers
|
||||
tls bool // TLS auth
|
||||
starttls bool // StartTLS
|
||||
smtpUserName string // username
|
||||
smtpPassword string // password
|
||||
authMethod authMethod // auth method
|
||||
timeOut time.Duration
|
||||
contentCharset string
|
||||
timeNow func() time.Time
|
||||
@@ -46,6 +47,7 @@ type Params struct {
|
||||
To []string // From email field
|
||||
Subject string // Email subject
|
||||
UnsubscribeLink string // POST, https://support.google.com/mail/answer/81126 -> "Use one-click unsubscribe"
|
||||
InReplyTo string // Identifier for email group (category), used for email grouping
|
||||
Attachments []string // Attachments path
|
||||
InlineImages []string // InlineImages images path
|
||||
}
|
||||
@@ -76,6 +78,7 @@ func NewSender(smtpHost string, options ...Option) *Sender {
|
||||
tls: false,
|
||||
smtpUserName: "",
|
||||
smtpPassword: "",
|
||||
authMethod: authMethodPlain,
|
||||
contentCharset: "UTF-8",
|
||||
timeOut: time.Second * 30,
|
||||
timeNow: time.Now,
|
||||
@@ -119,8 +122,7 @@ func (em *Sender) Send(text string, params Params) error {
|
||||
return errors.New("no recipients")
|
||||
}
|
||||
|
||||
if em.smtpUserName != "" && em.smtpPassword != "" {
|
||||
auth := smtp.PlainAuth("", em.smtpUserName, em.smtpPassword, em.host)
|
||||
if auth := em.auth(); auth != nil {
|
||||
if err := client.Auth(auth); err != nil {
|
||||
return fmt.Errorf("failed to auth to smtp %s:%d, %w", em.host, em.port, err)
|
||||
}
|
||||
@@ -204,6 +206,19 @@ func (em *Sender) client() (c *smtp.Client, err error) {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// auth returns an smtp.Auth that implements SMTP authentication mechanism
|
||||
// depends on Sender settings.
|
||||
func (em *Sender) auth() smtp.Auth {
|
||||
if em.smtpUserName == "" || em.smtpPassword == "" {
|
||||
return nil // no auth
|
||||
}
|
||||
|
||||
if em.authMethod == authMethodLogin {
|
||||
return newLoginAuth(em.smtpUserName, em.smtpPassword, em.host)
|
||||
}
|
||||
return smtp.PlainAuth("", em.smtpUserName, em.smtpPassword, em.host)
|
||||
}
|
||||
|
||||
func (em *Sender) buildMessage(text string, params Params) (message string, err error) {
|
||||
addHeader := func(msg, h, v string) string {
|
||||
msg += fmt.Sprintf("%s: %s\n", h, v)
|
||||
@@ -218,6 +233,10 @@ func (em *Sender) buildMessage(text string, params Params) (message string, err
|
||||
message = addHeader(message, "List-Unsubscribe", "<"+params.UnsubscribeLink+">")
|
||||
}
|
||||
|
||||
if params.InReplyTo != "" {
|
||||
message = addHeader(message, "In-reply-to", "<"+params.InReplyTo+">")
|
||||
}
|
||||
|
||||
withAttachments := len(params.Attachments) > 0
|
||||
withInlineImg := len(params.InlineImages) > 0
|
||||
|
||||
|
||||
+7
@@ -62,6 +62,13 @@ func Auth(smtpUserName, smtpPasswd string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// LoginAuth sets LOGIN auth method
|
||||
func LoginAuth() Option {
|
||||
return func(s *Sender) {
|
||||
s.authMethod = authMethodLogin
|
||||
}
|
||||
}
|
||||
|
||||
// TimeOut sets smtp timeout
|
||||
func TimeOut(timeOut time.Duration) Option {
|
||||
return func(s *Sender) {
|
||||
|
||||
+5
@@ -19,6 +19,7 @@ type SMTPParams struct {
|
||||
StartTLS bool // StartTLS auth
|
||||
ContentType string // Content type
|
||||
Charset string // Character set
|
||||
LoginAuth bool // LOGIN auth method instead of default PLAIN, needed for Office 365 and outlook.com
|
||||
Username string // username
|
||||
Password string // password
|
||||
TimeOut time.Duration // TCP connection timeout
|
||||
@@ -46,6 +47,10 @@ func NewEmail(smtpParams SMTPParams) *Email {
|
||||
opts = append(opts, email.Charset(smtpParams.Charset))
|
||||
}
|
||||
|
||||
if smtpParams.LoginAuth {
|
||||
opts = append(opts, email.LoginAuth())
|
||||
}
|
||||
|
||||
if smtpParams.Port != 0 {
|
||||
opts = append(opts, email.Port(smtpParams.Port))
|
||||
}
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ type Telegram struct {
|
||||
}
|
||||
}
|
||||
|
||||
// telegramMsg is used to send message trough Telegram bot API
|
||||
// telegramMsg is used to send message through Telegram bot API
|
||||
type telegramMsg struct {
|
||||
Text string `json:"text"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
|
||||
+1
-4
@@ -31,17 +31,14 @@ linters:
|
||||
- govet
|
||||
- unconvert
|
||||
- megacheck
|
||||
- structcheck
|
||||
- gas
|
||||
- gocyclo
|
||||
- dupl
|
||||
- misspell
|
||||
- unparam
|
||||
- varcheck
|
||||
- deadcode
|
||||
- unused
|
||||
- typecheck
|
||||
- ineffassign
|
||||
- varcheck
|
||||
- stylecheck
|
||||
- gochecknoinits
|
||||
- exportloopref
|
||||
|
||||
+56
@@ -36,6 +36,45 @@ Org: Umputun
|
||||
pong
|
||||
```
|
||||
|
||||
### Health middleware
|
||||
|
||||
Responds with the status 200 if all health checks passed, 503 if any failed. Both health path and check functions passed by consumer.
|
||||
For production usage this middleware should be used with throttler/limiter and, optionally, with some auth middlewares
|
||||
|
||||
Example of usage:
|
||||
|
||||
```go
|
||||
check1 := func(ctx context.Context) (name string, err error) {
|
||||
// do some check, for example check DB connection
|
||||
return "check1", nil // all good, passed
|
||||
}
|
||||
check2 := func(ctx context.Context) (name string, err error) {
|
||||
// do some other check, for example ping an external service
|
||||
return "check2", errors.New("some error") // check failed
|
||||
}
|
||||
|
||||
router := chi.NewRouter()
|
||||
router.Use(rest.Health("/health", check1, check2))
|
||||
```
|
||||
|
||||
example of the actual call and response:
|
||||
|
||||
```
|
||||
> http GET https://example.com/health
|
||||
|
||||
HTTP/1.1 503 Service Unavailable
|
||||
Date: Sun, 15 Jul 2018 19:40:31 GMT
|
||||
Content-Type: application/json; charset=utf-8
|
||||
Content-Length: 36
|
||||
|
||||
[
|
||||
{"name":"check1","status":"ok"},
|
||||
{"name":"check2","status":"failed","error":"some error"}
|
||||
]
|
||||
```
|
||||
|
||||
_this middleware is pretty basic, but can be used for simple health checks. For more complex cases, like async/cached health checks see [alexliesenfeld/health](https://github.com/alexliesenfeld/health)_
|
||||
|
||||
### Logger middleware
|
||||
|
||||
Logs request, request handling time and response. Log record fields in order of occurrence:
|
||||
@@ -119,6 +158,23 @@ Maybe middleware will allow you to change the flow of the middleware stack execu
|
||||
value of maybeFn(request). This is useful for example if you'd like to skip a middleware handler if
|
||||
a request does not satisfy the maybeFn logic.
|
||||
|
||||
### Reject middleware
|
||||
|
||||
Reject is a middleware that rejects requests with a given status code and message based on a user-defined function.
|
||||
This is useful for example if you'd like to reject requests to a particular resource based on a request header, or want to implement a conditional request handler based on service parameters.
|
||||
|
||||
example with chi router:
|
||||
|
||||
```go
|
||||
router := chi.NewRouter()
|
||||
|
||||
rejectFn := func(r *http.Request) (bool) {
|
||||
return r.Header.Get("X-Request-Id") == "" // reject if no X-Request-Id header
|
||||
}
|
||||
|
||||
router.Use(rest.Reject(http.StatusBadRequest, "X-Request-Id header is required", rejectFn))
|
||||
```
|
||||
|
||||
### Benchmarks middleware
|
||||
|
||||
Benchmarks middleware allows to measure the time of request handling, number of request per second and report aggregated metrics. This middleware keeps track of the request in the memory and keep up to 900 points (15 minutes, data-point per second).
|
||||
|
||||
+57
@@ -1,6 +1,7 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
@@ -54,6 +55,47 @@ func Ping(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// Health middleware response with health info and status (200 if healthy). Stops chain if health request detected
|
||||
// passed checkers implements custom health checks and returns error if health check failed. The check has to return name
|
||||
// regardless to the error state.
|
||||
// For production usage this middleware should be used with throttler and, optionally, with BasicAuth middlewares
|
||||
func Health(path string, checkers ...func(ctx context.Context) (name string, err error)) func(http.Handler) http.Handler {
|
||||
|
||||
type hr struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || !strings.EqualFold(r.URL.Path, path) {
|
||||
h.ServeHTTP(w, r) // not the health check request, continue the chain
|
||||
return
|
||||
}
|
||||
resp := []hr{}
|
||||
var anyError bool
|
||||
for _, check := range checkers {
|
||||
name, err := check(r.Context())
|
||||
hh := hr{Name: name, Status: "ok"}
|
||||
if err != nil {
|
||||
hh.Status = "failed"
|
||||
hh.Error = err.Error()
|
||||
anyError = true
|
||||
}
|
||||
resp = append(resp, hh)
|
||||
}
|
||||
if anyError {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
RenderJSON(w, resp)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
// Recoverer is a middleware that recovers from panics, logs the panic and returns a HTTP 500 status if possible.
|
||||
func Recoverer(l logger.Backend) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
@@ -125,3 +167,18 @@ func RealIP(h http.Handler) http.Handler {
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
// Reject is a middleware that conditionally rejects requests with a given status code and message.
|
||||
// user-defined condition function rejectFn is used to determine if the request should be rejected.
|
||||
func Reject(errCode int, errMsg string, rejectFn func(r *http.Request) bool) func(h http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if rejectFn(r) {
|
||||
http.Error(w, errMsg, errCode)
|
||||
return
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
+6
-7
@@ -4,10 +4,9 @@ package rest
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// JSON is a map alias, just for convenience
|
||||
@@ -30,7 +29,7 @@ func RenderJSON(w http.ResponseWriter, data interface{}) {
|
||||
func RenderJSONFromBytes(w http.ResponseWriter, r *http.Request, data []byte) error {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, err := w.Write(data); err != nil {
|
||||
return errors.Wrapf(err, "failed to send response to %s", r.RemoteAddr)
|
||||
return fmt.Errorf("failed to send response to %s: %w", r.RemoteAddr, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -43,7 +42,7 @@ func RenderJSONWithHTML(w http.ResponseWriter, r *http.Request, v interface{}) e
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false)
|
||||
if err := enc.Encode(v); err != nil {
|
||||
return nil, errors.Wrap(err, "json encoding failed")
|
||||
return nil, fmt.Errorf("json encoding failed: %w", err)
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
@@ -86,15 +85,15 @@ func ParseFromTo(r *http.Request) (from, to time.Time, err error) {
|
||||
return t, nil
|
||||
}
|
||||
}
|
||||
return time.Time{}, errors.Errorf("can't parse date %q", ts)
|
||||
return time.Time{}, fmt.Errorf("can't parse date %q", ts)
|
||||
}
|
||||
|
||||
if from, err = parseTimeStamp(r.URL.Query().Get("from")); err != nil {
|
||||
return from, to, errors.Wrap(err, "incorrect from time")
|
||||
return from, to, fmt.Errorf("incorrect from time: %w", err)
|
||||
}
|
||||
|
||||
if to, err = parseTimeStamp(r.URL.Query().Get("to")); err != nil {
|
||||
return from, to, errors.Wrap(err, "incorrect to time")
|
||||
return from, to, fmt.Errorf("incorrect to time: %w", err)
|
||||
}
|
||||
return from, to, nil
|
||||
}
|
||||
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
[](https://godoc.org/github.com/go-stack/stack)
|
||||
[](https://goreportcard.com/report/go-stack/stack)
|
||||
[](https://travis-ci.org/go-stack/stack)
|
||||
[](https://coveralls.io/github/go-stack/stack?branch=master)
|
||||
|
||||
# stack
|
||||
|
||||
Package stack implements utilities to capture, manipulate, and format call
|
||||
stacks. It provides a simpler API than package runtime.
|
||||
|
||||
The implementation takes care of the minutia and special cases of interpreting
|
||||
the program counter (pc) values returned by runtime.Callers.
|
||||
|
||||
## Versioning
|
||||
|
||||
Package stack publishes releases via [semver](http://semver.org/) compatible Git
|
||||
tags prefixed with a single 'v'. The master branch always contains the latest
|
||||
release. The develop branch contains unreleased commits.
|
||||
|
||||
## Formatting
|
||||
|
||||
Package stack's types implement fmt.Formatter, which provides a simple and
|
||||
flexible way to declaratively configure formatting when used with logging or
|
||||
error tracking packages.
|
||||
|
||||
```go
|
||||
func DoTheThing() {
|
||||
c := stack.Caller(0)
|
||||
log.Print(c) // "source.go:10"
|
||||
log.Printf("%+v", c) // "pkg/path/source.go:10"
|
||||
log.Printf("%n", c) // "DoTheThing"
|
||||
|
||||
s := stack.Trace().TrimRuntime()
|
||||
log.Print(s) // "[source.go:15 caller.go:42 main.go:14]"
|
||||
}
|
||||
```
|
||||
|
||||
See the docs for all of the supported formatting options.
|
||||
-400
@@ -1,400 +0,0 @@
|
||||
// +build go1.7
|
||||
|
||||
// Package stack implements utilities to capture, manipulate, and format call
|
||||
// stacks. It provides a simpler API than package runtime.
|
||||
//
|
||||
// The implementation takes care of the minutia and special cases of
|
||||
// interpreting the program counter (pc) values returned by runtime.Callers.
|
||||
//
|
||||
// Package stack's types implement fmt.Formatter, which provides a simple and
|
||||
// flexible way to declaratively configure formatting when used with logging
|
||||
// or error tracking packages.
|
||||
package stack
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Call records a single function invocation from a goroutine stack.
|
||||
type Call struct {
|
||||
frame runtime.Frame
|
||||
}
|
||||
|
||||
// Caller returns a Call from the stack of the current goroutine. The argument
|
||||
// skip is the number of stack frames to ascend, with 0 identifying the
|
||||
// calling function.
|
||||
func Caller(skip int) Call {
|
||||
// As of Go 1.9 we need room for up to three PC entries.
|
||||
//
|
||||
// 0. An entry for the stack frame prior to the target to check for
|
||||
// special handling needed if that prior entry is runtime.sigpanic.
|
||||
// 1. A possible second entry to hold metadata about skipped inlined
|
||||
// functions. If inline functions were not skipped the target frame
|
||||
// PC will be here.
|
||||
// 2. A third entry for the target frame PC when the second entry
|
||||
// is used for skipped inline functions.
|
||||
var pcs [3]uintptr
|
||||
n := runtime.Callers(skip+1, pcs[:])
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
frame, _ := frames.Next()
|
||||
frame, _ = frames.Next()
|
||||
|
||||
return Call{
|
||||
frame: frame,
|
||||
}
|
||||
}
|
||||
|
||||
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
|
||||
func (c Call) String() string {
|
||||
return fmt.Sprint(c)
|
||||
}
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
|
||||
// as fmt.Sprintf("%v", c).
|
||||
func (c Call) MarshalText() ([]byte, error) {
|
||||
if c.frame == (runtime.Frame{}) {
|
||||
return nil, ErrNoFunc
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
fmt.Fprint(&buf, c)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
|
||||
// cause is a Call with the zero value.
|
||||
var ErrNoFunc = errors.New("no call stack information")
|
||||
|
||||
// Format implements fmt.Formatter with support for the following verbs.
|
||||
//
|
||||
// %s source file
|
||||
// %d line number
|
||||
// %n function name
|
||||
// %k last segment of the package path
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// It accepts the '+' and '#' flags for most of the verbs as follows.
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH,
|
||||
// or the module path joined to the path of source file relative
|
||||
// to module root
|
||||
// %#s full path of source file
|
||||
// %+n import path qualified function name
|
||||
// %+k full package path
|
||||
// %+v equivalent to %+s:%d
|
||||
// %#v equivalent to %#s:%d
|
||||
func (c Call) Format(s fmt.State, verb rune) {
|
||||
if c.frame == (runtime.Frame{}) {
|
||||
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb)
|
||||
return
|
||||
}
|
||||
|
||||
switch verb {
|
||||
case 's', 'v':
|
||||
file := c.frame.File
|
||||
switch {
|
||||
case s.Flag('#'):
|
||||
// done
|
||||
case s.Flag('+'):
|
||||
file = pkgFilePath(&c.frame)
|
||||
default:
|
||||
const sep = "/"
|
||||
if i := strings.LastIndex(file, sep); i != -1 {
|
||||
file = file[i+len(sep):]
|
||||
}
|
||||
}
|
||||
io.WriteString(s, file)
|
||||
if verb == 'v' {
|
||||
buf := [7]byte{':'}
|
||||
s.Write(strconv.AppendInt(buf[:1], int64(c.frame.Line), 10))
|
||||
}
|
||||
|
||||
case 'd':
|
||||
buf := [6]byte{}
|
||||
s.Write(strconv.AppendInt(buf[:0], int64(c.frame.Line), 10))
|
||||
|
||||
case 'k':
|
||||
name := c.frame.Function
|
||||
const pathSep = "/"
|
||||
start, end := 0, len(name)
|
||||
if i := strings.LastIndex(name, pathSep); i != -1 {
|
||||
start = i + len(pathSep)
|
||||
}
|
||||
const pkgSep = "."
|
||||
if i := strings.Index(name[start:], pkgSep); i != -1 {
|
||||
end = start + i
|
||||
}
|
||||
if s.Flag('+') {
|
||||
start = 0
|
||||
}
|
||||
io.WriteString(s, name[start:end])
|
||||
|
||||
case 'n':
|
||||
name := c.frame.Function
|
||||
if !s.Flag('+') {
|
||||
const pathSep = "/"
|
||||
if i := strings.LastIndex(name, pathSep); i != -1 {
|
||||
name = name[i+len(pathSep):]
|
||||
}
|
||||
const pkgSep = "."
|
||||
if i := strings.Index(name, pkgSep); i != -1 {
|
||||
name = name[i+len(pkgSep):]
|
||||
}
|
||||
}
|
||||
io.WriteString(s, name)
|
||||
}
|
||||
}
|
||||
|
||||
// Frame returns the call frame infomation for the Call.
|
||||
func (c Call) Frame() runtime.Frame {
|
||||
return c.frame
|
||||
}
|
||||
|
||||
// PC returns the program counter for this call frame; multiple frames may
|
||||
// have the same PC value.
|
||||
//
|
||||
// Deprecated: Use Call.Frame instead.
|
||||
func (c Call) PC() uintptr {
|
||||
return c.frame.PC
|
||||
}
|
||||
|
||||
// CallStack records a sequence of function invocations from a goroutine
|
||||
// stack.
|
||||
type CallStack []Call
|
||||
|
||||
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
|
||||
func (cs CallStack) String() string {
|
||||
return fmt.Sprint(cs)
|
||||
}
|
||||
|
||||
var (
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
spaceBytes = []byte(" ")
|
||||
)
|
||||
|
||||
// MarshalText implements encoding.TextMarshaler. It formats the CallStack the
|
||||
// same as fmt.Sprintf("%v", cs).
|
||||
func (cs CallStack) MarshalText() ([]byte, error) {
|
||||
buf := bytes.Buffer{}
|
||||
buf.Write(openBracketBytes)
|
||||
for i, pc := range cs {
|
||||
if i > 0 {
|
||||
buf.Write(spaceBytes)
|
||||
}
|
||||
fmt.Fprint(&buf, pc)
|
||||
}
|
||||
buf.Write(closeBracketBytes)
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// Format implements fmt.Formatter by printing the CallStack as square brackets
|
||||
// ([, ]) surrounding a space separated list of Calls each formatted with the
|
||||
// supplied verb and options.
|
||||
func (cs CallStack) Format(s fmt.State, verb rune) {
|
||||
s.Write(openBracketBytes)
|
||||
for i, pc := range cs {
|
||||
if i > 0 {
|
||||
s.Write(spaceBytes)
|
||||
}
|
||||
pc.Format(s, verb)
|
||||
}
|
||||
s.Write(closeBracketBytes)
|
||||
}
|
||||
|
||||
// Trace returns a CallStack for the current goroutine with element 0
|
||||
// identifying the calling function.
|
||||
func Trace() CallStack {
|
||||
var pcs [512]uintptr
|
||||
n := runtime.Callers(1, pcs[:])
|
||||
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
cs := make(CallStack, 0, n)
|
||||
|
||||
// Skip extra frame retrieved just to make sure the runtime.sigpanic
|
||||
// special case is handled.
|
||||
frame, more := frames.Next()
|
||||
|
||||
for more {
|
||||
frame, more = frames.Next()
|
||||
cs = append(cs, Call{frame: frame})
|
||||
}
|
||||
|
||||
return cs
|
||||
}
|
||||
|
||||
// TrimBelow returns a slice of the CallStack with all entries below c
|
||||
// removed.
|
||||
func (cs CallStack) TrimBelow(c Call) CallStack {
|
||||
for len(cs) > 0 && cs[0] != c {
|
||||
cs = cs[1:]
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// TrimAbove returns a slice of the CallStack with all entries above c
|
||||
// removed.
|
||||
func (cs CallStack) TrimAbove(c Call) CallStack {
|
||||
for len(cs) > 0 && cs[len(cs)-1] != c {
|
||||
cs = cs[:len(cs)-1]
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
// pkgIndex returns the index that results in file[index:] being the path of
|
||||
// file relative to the compile time GOPATH, and file[:index] being the
|
||||
// $GOPATH/src/ portion of file. funcName must be the name of a function in
|
||||
// file as returned by runtime.Func.Name.
|
||||
func pkgIndex(file, funcName string) int {
|
||||
// As of Go 1.6.2 there is no direct way to know the compile time GOPATH
|
||||
// at runtime, but we can infer the number of path segments in the GOPATH.
|
||||
// We note that runtime.Func.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// file[:idx] == /home/user/src/
|
||||
// file[idx:] == pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired result for file[idx:]. We count separators from the
|
||||
// end of the file path until it finds two more than in the function name
|
||||
// and then move one character forward to preserve the initial path
|
||||
// segment without a leading separator.
|
||||
const sep = "/"
|
||||
i := len(file)
|
||||
for n := strings.Count(funcName, sep) + 2; n > 0; n-- {
|
||||
i = strings.LastIndex(file[:i], sep)
|
||||
if i == -1 {
|
||||
i = -len(sep)
|
||||
break
|
||||
}
|
||||
}
|
||||
// get back to 0 or trim the leading separator
|
||||
return i + len(sep)
|
||||
}
|
||||
|
||||
// pkgFilePath returns the frame's filepath relative to the compile-time GOPATH,
|
||||
// or its module path joined to its path relative to the module root.
|
||||
//
|
||||
// As of Go 1.11 there is no direct way to know the compile time GOPATH or
|
||||
// module paths at runtime, but we can piece together the desired information
|
||||
// from available information. We note that runtime.Frame.Function contains the
|
||||
// function name qualified by the package path, which includes the module path
|
||||
// but not the GOPATH. We can extract the package path from that and append the
|
||||
// last segments of the file path to arrive at the desired package qualified
|
||||
// file path. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// import path pkg/sub
|
||||
// frame.File /home/user/src/pkg/sub/file.go
|
||||
// frame.Function pkg/sub.Type.Method
|
||||
// Desired return pkg/sub/file.go
|
||||
//
|
||||
// It appears that we simply need to trim ".Type.Method" from frame.Function and
|
||||
// append "/" + path.Base(file).
|
||||
//
|
||||
// But there are other wrinkles. Although it is idiomatic to do so, the internal
|
||||
// name of a package is not required to match the last segment of its import
|
||||
// path. In addition, the introduction of modules in Go 1.11 allows working
|
||||
// without a GOPATH. So we also must make these work right:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// import path pkg/go-sub
|
||||
// package name sub
|
||||
// frame.File /home/user/src/pkg/go-sub/file.go
|
||||
// frame.Function pkg/sub.Type.Method
|
||||
// Desired return pkg/go-sub/file.go
|
||||
//
|
||||
// Module path pkg/v2
|
||||
// import path pkg/v2/go-sub
|
||||
// package name sub
|
||||
// frame.File /home/user/cloned-pkg/go-sub/file.go
|
||||
// frame.Function pkg/v2/sub.Type.Method
|
||||
// Desired return pkg/v2/go-sub/file.go
|
||||
//
|
||||
// We can handle all of these situations by using the package path extracted
|
||||
// from frame.Function up to, but not including, the last segment as the prefix
|
||||
// and the last two segments of frame.File as the suffix of the returned path.
|
||||
// This preserves the existing behavior when working in a GOPATH without modules
|
||||
// and a semantically equivalent behavior when used in module aware project.
|
||||
func pkgFilePath(frame *runtime.Frame) string {
|
||||
pre := pkgPrefix(frame.Function)
|
||||
post := pathSuffix(frame.File)
|
||||
if pre == "" {
|
||||
return post
|
||||
}
|
||||
return pre + "/" + post
|
||||
}
|
||||
|
||||
// pkgPrefix returns the import path of the function's package with the final
|
||||
// segment removed.
|
||||
func pkgPrefix(funcName string) string {
|
||||
const pathSep = "/"
|
||||
end := strings.LastIndex(funcName, pathSep)
|
||||
if end == -1 {
|
||||
return ""
|
||||
}
|
||||
return funcName[:end]
|
||||
}
|
||||
|
||||
// pathSuffix returns the last two segments of path.
|
||||
func pathSuffix(path string) string {
|
||||
const pathSep = "/"
|
||||
lastSep := strings.LastIndex(path, pathSep)
|
||||
if lastSep == -1 {
|
||||
return path
|
||||
}
|
||||
return path[strings.LastIndex(path[:lastSep], pathSep)+1:]
|
||||
}
|
||||
|
||||
var runtimePath string
|
||||
|
||||
func init() {
|
||||
var pcs [3]uintptr
|
||||
runtime.Callers(0, pcs[:])
|
||||
frames := runtime.CallersFrames(pcs[:])
|
||||
frame, _ := frames.Next()
|
||||
file := frame.File
|
||||
|
||||
idx := pkgIndex(frame.File, frame.Function)
|
||||
|
||||
runtimePath = file[:idx]
|
||||
if runtime.GOOS == "windows" {
|
||||
runtimePath = strings.ToLower(runtimePath)
|
||||
}
|
||||
}
|
||||
|
||||
func inGoroot(c Call) bool {
|
||||
file := c.frame.File
|
||||
if len(file) == 0 || file[0] == '?' {
|
||||
return true
|
||||
}
|
||||
if runtime.GOOS == "windows" {
|
||||
file = strings.ToLower(file)
|
||||
}
|
||||
return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go")
|
||||
}
|
||||
|
||||
// TrimRuntime returns a slice of the CallStack with the topmost entries from
|
||||
// the go runtime removed. It considers any calls originating from unknown
|
||||
// files, files under GOROOT, or _testmain.go as part of the runtime.
|
||||
func (cs CallStack) TrimRuntime() CallStack {
|
||||
for len(cs) > 0 && inGoroot(cs[len(cs)-1]) {
|
||||
cs = cs[:len(cs)-1]
|
||||
}
|
||||
return cs
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
coverage.out
|
||||
.directory
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
before_install:
|
||||
- sudo pip install codecov
|
||||
script:
|
||||
- go test
|
||||
after_success:
|
||||
- codecov
|
||||
notifications:
|
||||
email:
|
||||
recipients:
|
||||
- montana@montanaflynn.me
|
||||
on_success: change
|
||||
on_failure: always
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
# Change Log
|
||||
|
||||
## [0.2.0](https://github.com/montanaflynn/stats/tree/0.2.0)
|
||||
|
||||
### Merged pull requests:
|
||||
|
||||
- Fixed typographical error, changed accomdate to accommodate in README. [\#5](https://github.com/montanaflynn/stats/pull/5) ([saromanov](https://github.com/orthographic-pedant))
|
||||
|
||||
### Package changes:
|
||||
|
||||
- Add `Correlation` function
|
||||
- Add `Covariance` function
|
||||
- Add `StandardDeviation` function to be the same as `StandardDeviationPopulation`
|
||||
- Change `Variance` function to be the same as `PopulationVariation`
|
||||
- Add helper methods to `Float64Data`
|
||||
- Add `Float64Data` type to use instead of `[]float64`
|
||||
- Add `Series` type which references to `[]Coordinate`
|
||||
|
||||
## [0.1.0](https://github.com/montanaflynn/stats/tree/0.1.0)
|
||||
|
||||
Several functions were renamed in this release. They will still function but may be deprecated in the future.
|
||||
|
||||
### Package changes:
|
||||
|
||||
- Rename `VarP` to `PopulationVariance`
|
||||
- Rename `VarS` to `SampleVariance`
|
||||
- Rename `LinReg` to `LinearRegression`
|
||||
- Rename `ExpReg` to `ExponentialRegression`
|
||||
- Rename `LogReg` to `LogarithmicRegression`
|
||||
- Rename `StdDevP` to `StandardDeviationPopulation`
|
||||
- Rename `StdDevS` to `StandardDeviationSample`
|
||||
|
||||
## [0.0.9](https://github.com/montanaflynn/stats/tree/0.0.9)
|
||||
|
||||
### Closed issues:
|
||||
|
||||
- Functions have unexpected side effects [\#3](https://github.com/montanaflynn/stats/issues/3)
|
||||
- Percentile is not calculated correctly [\#2](https://github.com/montanaflynn/stats/issues/2)
|
||||
|
||||
### Merged pull requests:
|
||||
|
||||
- Sample [\#4](https://github.com/montanaflynn/stats/pull/4) ([saromanov](https://github.com/saromanov))
|
||||
|
||||
### Package changes:
|
||||
|
||||
- Add HarmonicMean func
|
||||
- Add GeometricMean func
|
||||
- Add Outliers stuct and QuantileOutliers func
|
||||
- Add Interquartile Range, Midhinge and Trimean examples
|
||||
- Add Trimean
|
||||
- Add Midhinge
|
||||
- Add Inter Quartile Range
|
||||
- Add Quantiles struct and Quantile func
|
||||
- Add Nearest Rank method of calculating percentiles
|
||||
- Add errors for all functions
|
||||
- Add sample
|
||||
- Add Linear, Exponential and Logarithmic Regression
|
||||
- Add sample and population variance and deviation
|
||||
- Add Percentile and Float64ToInt
|
||||
- Add Round
|
||||
- Add Standard deviation
|
||||
- Add Sum
|
||||
- Add Min and Ma- x
|
||||
- Add Mean, Median and Mode
|
||||
Generated
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Chris Hines
|
||||
Copyright (c) 2014-2015 Montana Flynn (https://anonfunction.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
.PHONY: all
|
||||
|
||||
doc:
|
||||
godoc `pwd`
|
||||
|
||||
webdoc:
|
||||
godoc -http=:44444
|
||||
|
||||
format:
|
||||
go fmt
|
||||
|
||||
test:
|
||||
go test -race
|
||||
|
||||
check: format test
|
||||
|
||||
benchmark:
|
||||
go test -bench=. -benchmem
|
||||
|
||||
coverage:
|
||||
go test -coverprofile=coverage.out
|
||||
go tool cover -html="coverage.out"
|
||||
|
||||
lint: format
|
||||
go get github.com/alecthomas/gometalinter
|
||||
gometalinter --install
|
||||
gometalinter
|
||||
|
||||
default: lint test
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
# Stats [![][travis-svg]][travis-url] [![][coveralls-svg]][coveralls-url] [![][godoc-svg]][godoc-url] [![][license-svg]][license-url]
|
||||
|
||||
A statistics package with many functions missing from the Golang standard library. See the [CHANGELOG.md](https://github.com/montanaflynn/stats/blob/master/CHANGELOG.md) for API changes and tagged releases you can vendor into your projects.
|
||||
|
||||
> Statistics are used much like a drunk uses a lamppost: for support, not illumination. **- Vin Scully**
|
||||
|
||||
## Installation
|
||||
|
||||
```
|
||||
go get github.com/montanaflynn/stats
|
||||
```
|
||||
|
||||
**Protip:** `go get -u github.com/montanaflynn/stats` updates stats to the latest version.
|
||||
|
||||
## Usage
|
||||
|
||||
The [entire API documentation](http://godoc.org/github.com/montanaflynn/stats) is available on GoDoc.org
|
||||
|
||||
You can view docs offline with the following commands:
|
||||
|
||||
```
|
||||
godoc ./
|
||||
godoc ./ Median
|
||||
godoc ./ Float64Data
|
||||
```
|
||||
|
||||
**Protip:** Generate HTML docs with `godoc -http=:4444`
|
||||
|
||||
## Example
|
||||
|
||||
All the functions can be seen in [examples/main.go](https://github.com/montanaflynn/stats/blob/master/examples/main.go) but here's a little taste:
|
||||
|
||||
```go
|
||||
// start with the some source data to use
|
||||
var data = []float64{1, 2, 3, 4, 4, 5}
|
||||
|
||||
median, _ := stats.Median(data)
|
||||
fmt.Println(median) // 3.5
|
||||
|
||||
roundedMedian, _ := stats.Round(median, 0)
|
||||
fmt.Println(roundedMedian) // 4
|
||||
```
|
||||
|
||||
**Protip:** You can [call methods](https://github.com/montanaflynn/stats/blob/master/examples/methods.go) on the data if using the Float64Data type:
|
||||
|
||||
```
|
||||
var d stats.Float64Data = data
|
||||
|
||||
max, _ := d.Max()
|
||||
fmt.Println(max) // 5
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
If you have any suggestions, criticism or bug reports please [create an issue](https://github.com/montanaflynn/stats/issues) and I'll do my best to accommodate you. In addition simply starring the repo would show your support for the project and be very much appreciated!
|
||||
|
||||
### Pull Requests
|
||||
|
||||
Pull request are always welcome no matter how big or small. Here's an easy way to do it:
|
||||
|
||||
1. Fork it and clone your fork
|
||||
2. Create new branch (`git checkout -b some-thing`)
|
||||
3. Make the desired changes
|
||||
4. Ensure tests pass (`go test -cover` or `make test`)
|
||||
5. Commit changes (`git commit -am 'Did something'`)
|
||||
6. Push branch (`git push origin some-thing`)
|
||||
7. Submit pull request
|
||||
|
||||
To make things as seamless as possible please also consider the following steps:
|
||||
|
||||
- Update `README.md` to include new public types or functions in the documentation section.
|
||||
- Update `examples/main.go` with a simple example of the new feature.
|
||||
- Keep 100% code coverage (you can check with `make coverage`).
|
||||
- Run [`gometalinter`](https://github.com/alecthomas/gometalinter) and make your code pass.
|
||||
- Squash needless commits into single units of work with `git rebase -i new-feature`.
|
||||
|
||||
#### Makefile
|
||||
|
||||
I've included a [Makefile](https://github.com/montanaflynn/stats/blob/master/Makefile) that has a lot of helper targets for common actions such as linting, testing, code coverage reporting and more.
|
||||
|
||||
**Protip:** `watch -n 1 make check` will continuously format and test your code.
|
||||
|
||||
## MIT License
|
||||
|
||||
Copyright (c) 2014-2015 Montana Flynn <http://anonfunction.com>
|
||||
|
||||
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 AUTHORpublicS 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.
|
||||
|
||||
[travis-url]: https://travis-ci.org/montanaflynn/stats
|
||||
[travis-svg]: https://img.shields.io/travis/montanaflynn/stats.svg
|
||||
|
||||
[coveralls-url]: https://coveralls.io/r/montanaflynn/stats?branch=master
|
||||
[coveralls-svg]: https://img.shields.io/coveralls/montanaflynn/stats.svg
|
||||
|
||||
[godoc-url]: https://godoc.org/github.com/montanaflynn/stats
|
||||
[godoc-svg]: https://godoc.org/github.com/montanaflynn/stats?status.svg
|
||||
|
||||
[license-url]: https://github.com/montanaflynn/stats/blob/master/LICENSE
|
||||
[license-svg]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Correlation describes the degree of relationship between two sets of data
|
||||
func Correlation(data1, data2 Float64Data) (float64, error) {
|
||||
|
||||
l1 := data1.Len()
|
||||
l2 := data2.Len()
|
||||
|
||||
if l1 == 0 || l2 == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
if l1 != l2 {
|
||||
return math.NaN(), SizeErr
|
||||
}
|
||||
|
||||
sdev1, _ := StandardDeviationPopulation(data1)
|
||||
sdev2, _ := StandardDeviationPopulation(data2)
|
||||
|
||||
if sdev1 == 0 || sdev2 == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
covp, _ := CovariancePopulation(data1, data2)
|
||||
return covp / (sdev1 * sdev2), nil
|
||||
}
|
||||
|
||||
// Pearson calculates the Pearson product-moment correlation coefficient between two variables.
|
||||
func Pearson(data1, data2 Float64Data) (float64, error) {
|
||||
return Correlation(data1, data2)
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
package stats
|
||||
|
||||
// Float64Data is a named type for []float64 with helper methods
|
||||
type Float64Data []float64
|
||||
|
||||
// Get item in slice
|
||||
func (f Float64Data) Get(i int) float64 { return f[i] }
|
||||
|
||||
// Len returns length of slice
|
||||
func (f Float64Data) Len() int { return len(f) }
|
||||
|
||||
// Less returns if one number is less than another
|
||||
func (f Float64Data) Less(i, j int) bool { return f[i] < f[j] }
|
||||
|
||||
// Swap switches out two numbers in slice
|
||||
func (f Float64Data) Swap(i, j int) { f[i], f[j] = f[j], f[i] }
|
||||
|
||||
// Min returns the minimum number in the data
|
||||
func (f Float64Data) Min() (float64, error) { return Min(f) }
|
||||
|
||||
// Max returns the maximum number in the data
|
||||
func (f Float64Data) Max() (float64, error) { return Max(f) }
|
||||
|
||||
// Sum returns the total of all the numbers in the data
|
||||
func (f Float64Data) Sum() (float64, error) { return Sum(f) }
|
||||
|
||||
// Mean returns the mean of the data
|
||||
func (f Float64Data) Mean() (float64, error) { return Mean(f) }
|
||||
|
||||
// Median returns the median of the data
|
||||
func (f Float64Data) Median() (float64, error) { return Median(f) }
|
||||
|
||||
// Mode returns the mode of the data
|
||||
func (f Float64Data) Mode() ([]float64, error) { return Mode(f) }
|
||||
|
||||
// GeometricMean returns the median of the data
|
||||
func (f Float64Data) GeometricMean() (float64, error) { return GeometricMean(f) }
|
||||
|
||||
// HarmonicMean returns the mode of the data
|
||||
func (f Float64Data) HarmonicMean() (float64, error) { return HarmonicMean(f) }
|
||||
|
||||
// MedianAbsoluteDeviation the median of the absolute deviations from the dataset median
|
||||
func (f Float64Data) MedianAbsoluteDeviation() (float64, error) {
|
||||
return MedianAbsoluteDeviation(f)
|
||||
}
|
||||
|
||||
// MedianAbsoluteDeviationPopulation finds the median of the absolute deviations from the population median
|
||||
func (f Float64Data) MedianAbsoluteDeviationPopulation() (float64, error) {
|
||||
return MedianAbsoluteDeviationPopulation(f)
|
||||
}
|
||||
|
||||
// StandardDeviation the amount of variation in the dataset
|
||||
func (f Float64Data) StandardDeviation() (float64, error) {
|
||||
return StandardDeviation(f)
|
||||
}
|
||||
|
||||
// StandardDeviationPopulation finds the amount of variation from the population
|
||||
func (f Float64Data) StandardDeviationPopulation() (float64, error) {
|
||||
return StandardDeviationPopulation(f)
|
||||
}
|
||||
|
||||
// StandardDeviationSample finds the amount of variation from a sample
|
||||
func (f Float64Data) StandardDeviationSample() (float64, error) {
|
||||
return StandardDeviationSample(f)
|
||||
}
|
||||
|
||||
// QuartileOutliers finds the mild and extreme outliers
|
||||
func (f Float64Data) QuartileOutliers() (Outliers, error) {
|
||||
return QuartileOutliers(f)
|
||||
}
|
||||
|
||||
// Percentile finds the relative standing in a slice of floats
|
||||
func (f Float64Data) Percentile(p float64) (float64, error) {
|
||||
return Percentile(f, p)
|
||||
}
|
||||
|
||||
// PercentileNearestRank finds the relative standing using the Nearest Rank method
|
||||
func (f Float64Data) PercentileNearestRank(p float64) (float64, error) {
|
||||
return PercentileNearestRank(f, p)
|
||||
}
|
||||
|
||||
// Correlation describes the degree of relationship between two sets of data
|
||||
func (f Float64Data) Correlation(d Float64Data) (float64, error) {
|
||||
return Correlation(f, d)
|
||||
}
|
||||
|
||||
// Pearson calculates the Pearson product-moment correlation coefficient between two variables.
|
||||
func (f Float64Data) Pearson(d Float64Data) (float64, error) {
|
||||
return Pearson(f, d)
|
||||
}
|
||||
|
||||
// Quartile returns the three quartile points from a slice of data
|
||||
func (f Float64Data) Quartile(d Float64Data) (Quartiles, error) {
|
||||
return Quartile(d)
|
||||
}
|
||||
|
||||
// InterQuartileRange finds the range between Q1 and Q3
|
||||
func (f Float64Data) InterQuartileRange() (float64, error) {
|
||||
return InterQuartileRange(f)
|
||||
}
|
||||
|
||||
// Midhinge finds the average of the first and third quartiles
|
||||
func (f Float64Data) Midhinge(d Float64Data) (float64, error) {
|
||||
return Midhinge(d)
|
||||
}
|
||||
|
||||
// Trimean finds the average of the median and the midhinge
|
||||
func (f Float64Data) Trimean(d Float64Data) (float64, error) {
|
||||
return Trimean(d)
|
||||
}
|
||||
|
||||
// Sample returns sample from input with replacement or without
|
||||
func (f Float64Data) Sample(n int, r bool) ([]float64, error) {
|
||||
return Sample(f, n, r)
|
||||
}
|
||||
|
||||
// Variance the amount of variation in the dataset
|
||||
func (f Float64Data) Variance() (float64, error) {
|
||||
return Variance(f)
|
||||
}
|
||||
|
||||
// PopulationVariance finds the amount of variance within a population
|
||||
func (f Float64Data) PopulationVariance() (float64, error) {
|
||||
return PopulationVariance(f)
|
||||
}
|
||||
|
||||
// SampleVariance finds the amount of variance within a sample
|
||||
func (f Float64Data) SampleVariance() (float64, error) {
|
||||
return SampleVariance(f)
|
||||
}
|
||||
|
||||
// Covariance is a measure of how much two sets of data change
|
||||
func (f Float64Data) Covariance(d Float64Data) (float64, error) {
|
||||
return Covariance(f, d)
|
||||
}
|
||||
|
||||
// CovariancePopulation computes covariance for entire population between two variables.
|
||||
func (f Float64Data) CovariancePopulation(d Float64Data) (float64, error) {
|
||||
return CovariancePopulation(f, d)
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Validate data for distance calculation
|
||||
func validateData(dataPointX, dataPointY []float64) error {
|
||||
if len(dataPointX) == 0 || len(dataPointY) == 0 {
|
||||
return EmptyInput
|
||||
}
|
||||
|
||||
if len(dataPointX) != len(dataPointY) {
|
||||
return SizeErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Computes Chebyshev distance between two data sets
|
||||
func ChebyshevDistance(dataPointX, dataPointY []float64) (distance float64, err error) {
|
||||
err = validateData(dataPointX, dataPointY)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
var tempDistance float64
|
||||
for i := 0; i < len(dataPointY); i++ {
|
||||
tempDistance = math.Abs(dataPointX[i] - dataPointY[i])
|
||||
if distance < tempDistance {
|
||||
distance = tempDistance
|
||||
}
|
||||
}
|
||||
return distance, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Computes Euclidean distance between two data sets
|
||||
//
|
||||
func EuclideanDistance(dataPointX, dataPointY []float64) (distance float64, err error) {
|
||||
|
||||
err = validateData(dataPointX, dataPointY)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
distance = 0
|
||||
for i := 0; i < len(dataPointX); i++ {
|
||||
distance = distance + ((dataPointX[i] - dataPointY[i]) * (dataPointX[i] - dataPointY[i]))
|
||||
}
|
||||
return math.Sqrt(distance), nil
|
||||
}
|
||||
|
||||
//
|
||||
// Computes Manhattan distance between two data sets
|
||||
//
|
||||
func ManhattanDistance(dataPointX, dataPointY []float64) (distance float64, err error) {
|
||||
err = validateData(dataPointX, dataPointY)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
distance = 0
|
||||
for i := 0; i < len(dataPointX); i++ {
|
||||
distance = distance + math.Abs(dataPointX[i]-dataPointY[i])
|
||||
}
|
||||
return distance, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Computes minkowski distance between two data sets.
|
||||
//
|
||||
// Input:
|
||||
// dataPointX: First set of data points
|
||||
// dataPointY: Second set of data points. Length of both data
|
||||
// sets must be equal.
|
||||
// lambda: aka p or city blocks; With lambda = 1
|
||||
// returned distance is manhattan distance and
|
||||
// lambda = 2; it is euclidean distance. Lambda
|
||||
// reaching to infinite - distance would be chebysev
|
||||
// distance.
|
||||
// Output:
|
||||
// Distance or error
|
||||
//
|
||||
func MinkowskiDistance(dataPointX, dataPointY []float64, lambda float64) (distance float64, err error) {
|
||||
err = validateData(dataPointX, dataPointY)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
for i := 0; i < len(dataPointY); i++ {
|
||||
distance = distance + math.Pow(math.Abs(dataPointX[i]-dataPointY[i]), lambda)
|
||||
}
|
||||
distance = math.Pow(distance, float64(1/lambda))
|
||||
if math.IsInf(distance, 1) == true {
|
||||
return math.NaN(), InfValue
|
||||
}
|
||||
return distance, nil
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// MedianAbsoluteDeviation finds the median of the absolute deviations from the dataset median
|
||||
func MedianAbsoluteDeviation(input Float64Data) (mad float64, err error) {
|
||||
return MedianAbsoluteDeviationPopulation(input)
|
||||
}
|
||||
|
||||
// MedianAbsoluteDeviationPopulation finds the median of the absolute deviations from the population median
|
||||
func MedianAbsoluteDeviationPopulation(input Float64Data) (mad float64, err error) {
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
i := copyslice(input)
|
||||
m, _ := Median(i)
|
||||
|
||||
for key, value := range i {
|
||||
i[key] = math.Abs(value - m)
|
||||
}
|
||||
|
||||
return Median(i)
|
||||
}
|
||||
|
||||
// StandardDeviation the amount of variation in the dataset
|
||||
func StandardDeviation(input Float64Data) (sdev float64, err error) {
|
||||
return StandardDeviationPopulation(input)
|
||||
}
|
||||
|
||||
// StandardDeviationPopulation finds the amount of variation from the population
|
||||
func StandardDeviationPopulation(input Float64Data) (sdev float64, err error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the population variance
|
||||
vp, _ := PopulationVariance(input)
|
||||
|
||||
// Return the population standard deviation
|
||||
return math.Pow(vp, 0.5), nil
|
||||
}
|
||||
|
||||
// StandardDeviationSample finds the amount of variation from a sample
|
||||
func StandardDeviationSample(input Float64Data) (sdev float64, err error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the sample variance
|
||||
vs, _ := SampleVariance(input)
|
||||
|
||||
// Return the sample standard deviation
|
||||
return math.Pow(vs, 0.5), nil
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package stats
|
||||
|
||||
type statsErr struct {
|
||||
err string
|
||||
}
|
||||
|
||||
func (s statsErr) Error() string {
|
||||
return s.err
|
||||
}
|
||||
|
||||
// These are the package-wide error values.
|
||||
// All error identification should use these values.
|
||||
var (
|
||||
EmptyInput = statsErr{"Input must not be empty."}
|
||||
SampleSize = statsErr{"Samples number must be less than input length."}
|
||||
NaNErr = statsErr{"Not a number"}
|
||||
NegativeErr = statsErr{"Slice must not contain negative values."}
|
||||
ZeroErr = statsErr{"Slice must not contain zero values."}
|
||||
BoundsErr = statsErr{"Input is outside of range."}
|
||||
SizeErr = statsErr{"Slices must be the same length."}
|
||||
InfValue = statsErr{"Value is infinite."}
|
||||
)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package stats
|
||||
|
||||
// VarP is a shortcut to PopulationVariance
|
||||
func VarP(input Float64Data) (sdev float64, err error) {
|
||||
return PopulationVariance(input)
|
||||
}
|
||||
|
||||
// VarS is a shortcut to SampleVariance
|
||||
func VarS(input Float64Data) (sdev float64, err error) {
|
||||
return SampleVariance(input)
|
||||
}
|
||||
|
||||
// StdDevP is a shortcut to StandardDeviationPopulation
|
||||
func StdDevP(input Float64Data) (sdev float64, err error) {
|
||||
return StandardDeviationPopulation(input)
|
||||
}
|
||||
|
||||
// StdDevS is a shortcut to StandardDeviationSample
|
||||
func StdDevS(input Float64Data) (sdev float64, err error) {
|
||||
return StandardDeviationSample(input)
|
||||
}
|
||||
|
||||
// LinReg is a shortcut to LinearRegression
|
||||
func LinReg(s []Coordinate) (regressions []Coordinate, err error) {
|
||||
return LinearRegression(s)
|
||||
}
|
||||
|
||||
// ExpReg is a shortcut to ExponentialRegression
|
||||
func ExpReg(s []Coordinate) (regressions []Coordinate, err error) {
|
||||
return ExponentialRegression(s)
|
||||
}
|
||||
|
||||
// LogReg is a shortcut to LogarithmicRegression
|
||||
func LogReg(s []Coordinate) (regressions []Coordinate, err error) {
|
||||
return LogarithmicRegression(s)
|
||||
}
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LoadRawData parses and converts a slice of mixed data types to floats
|
||||
func LoadRawData(raw interface{}) (f Float64Data) {
|
||||
var r []interface{}
|
||||
var s Float64Data
|
||||
|
||||
switch t := raw.(type) {
|
||||
case []interface{}:
|
||||
r = t
|
||||
case []uint:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []uint8:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []uint16:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []uint32:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []uint64:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []bool:
|
||||
for _, v := range t {
|
||||
if v == true {
|
||||
s = append(s, 1.0)
|
||||
} else {
|
||||
s = append(s, 0.0)
|
||||
}
|
||||
}
|
||||
return s
|
||||
case []float64:
|
||||
return Float64Data(t)
|
||||
case []int:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []int8:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []int16:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []int32:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []int64:
|
||||
for _, v := range t {
|
||||
s = append(s, float64(v))
|
||||
}
|
||||
return s
|
||||
case []string:
|
||||
for _, v := range t {
|
||||
r = append(r, v)
|
||||
}
|
||||
case []time.Duration:
|
||||
for _, v := range t {
|
||||
r = append(r, v)
|
||||
}
|
||||
case map[int]int:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]int8:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]int16:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]int32:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]int64:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]string:
|
||||
for i := 0; i < len(t); i++ {
|
||||
r = append(r, t[i])
|
||||
}
|
||||
case map[int]uint:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]uint8:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]uint16:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]uint32:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]uint64:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, float64(t[i]))
|
||||
}
|
||||
return s
|
||||
case map[int]bool:
|
||||
for i := 0; i < len(t); i++ {
|
||||
if t[i] == true {
|
||||
s = append(s, 1.0)
|
||||
} else {
|
||||
s = append(s, 0.0)
|
||||
}
|
||||
}
|
||||
return s
|
||||
case map[int]float64:
|
||||
for i := 0; i < len(t); i++ {
|
||||
s = append(s, t[i])
|
||||
}
|
||||
return s
|
||||
case map[int]time.Duration:
|
||||
for i := 0; i < len(t); i++ {
|
||||
r = append(r, t[i])
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range r {
|
||||
switch t := v.(type) {
|
||||
case int:
|
||||
a := float64(t)
|
||||
f = append(f, a)
|
||||
case uint:
|
||||
f = append(f, float64(t))
|
||||
case float64:
|
||||
f = append(f, t)
|
||||
case string:
|
||||
fl, err := strconv.ParseFloat(t, 64)
|
||||
if err == nil {
|
||||
f = append(f, fl)
|
||||
}
|
||||
case bool:
|
||||
if t == true {
|
||||
f = append(f, 1.0)
|
||||
} else {
|
||||
f = append(f, 0.0)
|
||||
}
|
||||
case time.Duration:
|
||||
f = append(f, float64(t))
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Max finds the highest number in a slice
|
||||
func Max(input Float64Data) (max float64, err error) {
|
||||
|
||||
// Return an error if there are no numbers
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the first value as the starting point
|
||||
max = input.Get(0)
|
||||
|
||||
// Loop and replace higher values
|
||||
for i := 1; i < input.Len(); i++ {
|
||||
if input.Get(i) > max {
|
||||
max = input.Get(i)
|
||||
}
|
||||
}
|
||||
|
||||
return max, nil
|
||||
}
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Mean gets the average of a slice of numbers
|
||||
func Mean(input Float64Data) (float64, error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
sum, _ := input.Sum()
|
||||
|
||||
return sum / float64(input.Len()), nil
|
||||
}
|
||||
|
||||
// GeometricMean gets the geometric mean for a slice of numbers
|
||||
func GeometricMean(input Float64Data) (float64, error) {
|
||||
|
||||
l := input.Len()
|
||||
if l == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the product of all the numbers
|
||||
var p float64
|
||||
for _, n := range input {
|
||||
if p == 0 {
|
||||
p = n
|
||||
} else {
|
||||
p *= n
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the geometric mean
|
||||
return math.Pow(p, 1/float64(l)), nil
|
||||
}
|
||||
|
||||
// HarmonicMean gets the harmonic mean for a slice of numbers
|
||||
func HarmonicMean(input Float64Data) (float64, error) {
|
||||
|
||||
l := input.Len()
|
||||
if l == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the sum of all the numbers reciprocals and return an
|
||||
// error for values that cannot be included in harmonic mean
|
||||
var p float64
|
||||
for _, n := range input {
|
||||
if n < 0 {
|
||||
return math.NaN(), NegativeErr
|
||||
} else if n == 0 {
|
||||
return math.NaN(), ZeroErr
|
||||
}
|
||||
p += (1 / n)
|
||||
}
|
||||
|
||||
return float64(l) / p, nil
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Median gets the median number in a slice of numbers
|
||||
func Median(input Float64Data) (median float64, err error) {
|
||||
|
||||
// Start by sorting a copy of the slice
|
||||
c := sortedCopy(input)
|
||||
|
||||
// No math is needed if there are no numbers
|
||||
// For even numbers we add the two middle numbers
|
||||
// and divide by two using the mean function above
|
||||
// For odd numbers we just use the middle number
|
||||
l := len(c)
|
||||
if l == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
} else if l%2 == 0 {
|
||||
median, _ = Mean(c[l/2-1 : l/2+1])
|
||||
} else {
|
||||
median = float64(c[l/2])
|
||||
}
|
||||
|
||||
return median, nil
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Min finds the lowest number in a set of data
|
||||
func Min(input Float64Data) (min float64, err error) {
|
||||
|
||||
// Get the count of numbers in the slice
|
||||
l := input.Len()
|
||||
|
||||
// Return an error if there are no numbers
|
||||
if l == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Get the first value as the starting point
|
||||
min = input.Get(0)
|
||||
|
||||
// Iterate until done checking for a lower value
|
||||
for i := 1; i < l; i++ {
|
||||
if input.Get(i) < min {
|
||||
min = input.Get(i)
|
||||
}
|
||||
}
|
||||
return min, nil
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package stats
|
||||
|
||||
// Mode gets the mode [most frequent value(s)] of a slice of float64s
|
||||
func Mode(input Float64Data) (mode []float64, err error) {
|
||||
// Return the input if there's only one number
|
||||
l := input.Len()
|
||||
if l == 1 {
|
||||
return input, nil
|
||||
} else if l == 0 {
|
||||
return nil, EmptyInput
|
||||
}
|
||||
|
||||
c := sortedCopyDif(input)
|
||||
// Traverse sorted array,
|
||||
// tracking the longest repeating sequence
|
||||
mode = make([]float64, 5)
|
||||
cnt, maxCnt := 1, 1
|
||||
for i := 1; i < l; i++ {
|
||||
switch {
|
||||
case c[i] == c[i-1]:
|
||||
cnt++
|
||||
case cnt == maxCnt && maxCnt != 1:
|
||||
mode = append(mode, c[i-1])
|
||||
cnt = 1
|
||||
case cnt > maxCnt:
|
||||
mode = append(mode[:0], c[i-1])
|
||||
maxCnt, cnt = cnt, 1
|
||||
default:
|
||||
cnt = 1
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case cnt == maxCnt:
|
||||
mode = append(mode, c[l-1])
|
||||
case cnt > maxCnt:
|
||||
mode = append(mode[:0], c[l-1])
|
||||
maxCnt = cnt
|
||||
}
|
||||
|
||||
// Since length must be greater than 1,
|
||||
// check for slices of distinct values
|
||||
if maxCnt == 1 {
|
||||
return Float64Data{}, nil
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package stats
|
||||
|
||||
// Outliers holds mild and extreme outliers found in data
|
||||
type Outliers struct {
|
||||
Mild Float64Data
|
||||
Extreme Float64Data
|
||||
}
|
||||
|
||||
// QuartileOutliers finds the mild and extreme outliers
|
||||
func QuartileOutliers(input Float64Data) (Outliers, error) {
|
||||
if input.Len() == 0 {
|
||||
return Outliers{}, EmptyInput
|
||||
}
|
||||
|
||||
// Start by sorting a copy of the slice
|
||||
copy := sortedCopy(input)
|
||||
|
||||
// Calculate the quartiles and interquartile range
|
||||
qs, _ := Quartile(copy)
|
||||
iqr, _ := InterQuartileRange(copy)
|
||||
|
||||
// Calculate the lower and upper inner and outer fences
|
||||
lif := qs.Q1 - (1.5 * iqr)
|
||||
uif := qs.Q3 + (1.5 * iqr)
|
||||
lof := qs.Q1 - (3 * iqr)
|
||||
uof := qs.Q3 + (3 * iqr)
|
||||
|
||||
// Find the data points that are outside of the
|
||||
// inner and upper fences and add them to mild
|
||||
// and extreme outlier slices
|
||||
var mild Float64Data
|
||||
var extreme Float64Data
|
||||
for _, v := range copy {
|
||||
|
||||
if v < lof || v > uof {
|
||||
extreme = append(extreme, v)
|
||||
} else if v < lif || v > uif {
|
||||
mild = append(mild, v)
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap them into our struct
|
||||
return Outliers{mild, extreme}, nil
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Percentile finds the relative standing in a slice of floats
|
||||
func Percentile(input Float64Data, percent float64) (percentile float64, err error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
if percent <= 0 || percent > 100 {
|
||||
return math.NaN(), BoundsErr
|
||||
}
|
||||
|
||||
// Start by sorting a copy of the slice
|
||||
c := sortedCopy(input)
|
||||
|
||||
// Multiply percent by length of input
|
||||
index := (percent / 100) * float64(len(c))
|
||||
|
||||
// Check if the index is a whole number
|
||||
if index == float64(int64(index)) {
|
||||
|
||||
// Convert float to int
|
||||
i := int(index)
|
||||
|
||||
// Find the value at the index
|
||||
percentile = c[i-1]
|
||||
|
||||
} else if index > 1 {
|
||||
|
||||
// Convert float to int via truncation
|
||||
i := int(index)
|
||||
|
||||
// Find the average of the index and following values
|
||||
percentile, _ = Mean(Float64Data{c[i-1], c[i]})
|
||||
|
||||
} else {
|
||||
return math.NaN(), BoundsErr
|
||||
}
|
||||
|
||||
return percentile, nil
|
||||
|
||||
}
|
||||
|
||||
// PercentileNearestRank finds the relative standing in a slice of floats using the Nearest Rank method
|
||||
func PercentileNearestRank(input Float64Data, percent float64) (percentile float64, err error) {
|
||||
|
||||
// Find the length of items in the slice
|
||||
il := input.Len()
|
||||
|
||||
// Return an error for empty slices
|
||||
if il == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Return error for less than 0 or greater than 100 percentages
|
||||
if percent < 0 || percent > 100 {
|
||||
return math.NaN(), BoundsErr
|
||||
}
|
||||
|
||||
// Start by sorting a copy of the slice
|
||||
c := sortedCopy(input)
|
||||
|
||||
// Return the last item
|
||||
if percent == 100.0 {
|
||||
return c[il-1], nil
|
||||
}
|
||||
|
||||
// Find ordinal ranking
|
||||
or := int(math.Ceil(float64(il) * percent / 100))
|
||||
|
||||
// Return the item that is in the place of the ordinal rank
|
||||
if or == 0 {
|
||||
return c[0], nil
|
||||
}
|
||||
return c[or-1], nil
|
||||
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Quartiles holds the three quartile points
|
||||
type Quartiles struct {
|
||||
Q1 float64
|
||||
Q2 float64
|
||||
Q3 float64
|
||||
}
|
||||
|
||||
// Quartile returns the three quartile points from a slice of data
|
||||
func Quartile(input Float64Data) (Quartiles, error) {
|
||||
|
||||
il := input.Len()
|
||||
if il == 0 {
|
||||
return Quartiles{}, EmptyInput
|
||||
}
|
||||
|
||||
// Start by sorting a copy of the slice
|
||||
copy := sortedCopy(input)
|
||||
|
||||
// Find the cutoff places depeding on if
|
||||
// the input slice length is even or odd
|
||||
var c1 int
|
||||
var c2 int
|
||||
if il%2 == 0 {
|
||||
c1 = il / 2
|
||||
c2 = il / 2
|
||||
} else {
|
||||
c1 = (il - 1) / 2
|
||||
c2 = c1 + 1
|
||||
}
|
||||
|
||||
// Find the Medians with the cutoff points
|
||||
Q1, _ := Median(copy[:c1])
|
||||
Q2, _ := Median(copy)
|
||||
Q3, _ := Median(copy[c2:])
|
||||
|
||||
return Quartiles{Q1, Q2, Q3}, nil
|
||||
|
||||
}
|
||||
|
||||
// InterQuartileRange finds the range between Q1 and Q3
|
||||
func InterQuartileRange(input Float64Data) (float64, error) {
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
qs, _ := Quartile(input)
|
||||
iqr := qs.Q3 - qs.Q1
|
||||
return iqr, nil
|
||||
}
|
||||
|
||||
// Midhinge finds the average of the first and third quartiles
|
||||
func Midhinge(input Float64Data) (float64, error) {
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
qs, _ := Quartile(input)
|
||||
mh := (qs.Q1 + qs.Q3) / 2
|
||||
return mh, nil
|
||||
}
|
||||
|
||||
// Trimean finds the average of the median and the midhinge
|
||||
func Trimean(input Float64Data) (float64, error) {
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
c := sortedCopy(input)
|
||||
q, _ := Quartile(c)
|
||||
|
||||
return (q.Q1 + (q.Q2 * 2) + q.Q3) / 4, nil
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Series is a container for a series of data
|
||||
type Series []Coordinate
|
||||
|
||||
// Coordinate holds the data in a series
|
||||
type Coordinate struct {
|
||||
X, Y float64
|
||||
}
|
||||
|
||||
// LinearRegression finds the least squares linear regression on data series
|
||||
func LinearRegression(s Series) (regressions Series, err error) {
|
||||
|
||||
if len(s) == 0 {
|
||||
return nil, EmptyInput
|
||||
}
|
||||
|
||||
// Placeholder for the math to be done
|
||||
var sum [5]float64
|
||||
|
||||
// Loop over data keeping index in place
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
sum[0] += s[i].X
|
||||
sum[1] += s[i].Y
|
||||
sum[2] += s[i].X * s[i].X
|
||||
sum[3] += s[i].X * s[i].Y
|
||||
sum[4] += s[i].Y * s[i].Y
|
||||
}
|
||||
|
||||
// Find gradient and intercept
|
||||
f := float64(i)
|
||||
gradient := (f*sum[3] - sum[0]*sum[1]) / (f*sum[2] - sum[0]*sum[0])
|
||||
intercept := (sum[1] / f) - (gradient * sum[0] / f)
|
||||
|
||||
// Create the new regression series
|
||||
for j := 0; j < len(s); j++ {
|
||||
regressions = append(regressions, Coordinate{
|
||||
X: s[j].X,
|
||||
Y: s[j].X*gradient + intercept,
|
||||
})
|
||||
}
|
||||
|
||||
return regressions, nil
|
||||
|
||||
}
|
||||
|
||||
// ExponentialRegression returns an exponential regression on data series
|
||||
func ExponentialRegression(s Series) (regressions Series, err error) {
|
||||
|
||||
if len(s) == 0 {
|
||||
return nil, EmptyInput
|
||||
}
|
||||
|
||||
var sum [6]float64
|
||||
|
||||
for i := 0; i < len(s); i++ {
|
||||
sum[0] += s[i].X
|
||||
sum[1] += s[i].Y
|
||||
sum[2] += s[i].X * s[i].X * s[i].Y
|
||||
sum[3] += s[i].Y * math.Log(s[i].Y)
|
||||
sum[4] += s[i].X * s[i].Y * math.Log(s[i].Y)
|
||||
sum[5] += s[i].X * s[i].Y
|
||||
}
|
||||
|
||||
denominator := (sum[1]*sum[2] - sum[5]*sum[5])
|
||||
a := math.Pow(math.E, (sum[2]*sum[3]-sum[5]*sum[4])/denominator)
|
||||
b := (sum[1]*sum[4] - sum[5]*sum[3]) / denominator
|
||||
|
||||
for j := 0; j < len(s); j++ {
|
||||
regressions = append(regressions, Coordinate{
|
||||
X: s[j].X,
|
||||
Y: a * math.Exp(b*s[j].X),
|
||||
})
|
||||
}
|
||||
|
||||
return regressions, nil
|
||||
|
||||
}
|
||||
|
||||
// LogarithmicRegression returns an logarithmic regression on data series
|
||||
func LogarithmicRegression(s Series) (regressions Series, err error) {
|
||||
|
||||
if len(s) == 0 {
|
||||
return nil, EmptyInput
|
||||
}
|
||||
|
||||
var sum [4]float64
|
||||
|
||||
i := 0
|
||||
for ; i < len(s); i++ {
|
||||
sum[0] += math.Log(s[i].X)
|
||||
sum[1] += s[i].Y * math.Log(s[i].X)
|
||||
sum[2] += s[i].Y
|
||||
sum[3] += math.Pow(math.Log(s[i].X), 2)
|
||||
}
|
||||
|
||||
f := float64(i)
|
||||
a := (f*sum[1] - sum[2]*sum[0]) / (f*sum[3] - sum[0]*sum[0])
|
||||
b := (sum[2] - a*sum[0]) / f
|
||||
|
||||
for j := 0; j < len(s); j++ {
|
||||
regressions = append(regressions, Coordinate{
|
||||
X: s[j].X,
|
||||
Y: b + a*math.Log(s[j].X),
|
||||
})
|
||||
}
|
||||
|
||||
return regressions, nil
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Round a float to a specific decimal place or precision
|
||||
func Round(input float64, places int) (rounded float64, err error) {
|
||||
|
||||
// If the float is not a number
|
||||
if math.IsNaN(input) {
|
||||
return math.NaN(), NaNErr
|
||||
}
|
||||
|
||||
// Find out the actual sign and correct the input for later
|
||||
sign := 1.0
|
||||
if input < 0 {
|
||||
sign = -1
|
||||
input *= -1
|
||||
}
|
||||
|
||||
// Use the places arg to get the amount of precision wanted
|
||||
precision := math.Pow(10, float64(places))
|
||||
|
||||
// Find the decimal place we are looking to round
|
||||
digit := input * precision
|
||||
|
||||
// Get the actual decimal number as a fraction to be compared
|
||||
_, decimal := math.Modf(digit)
|
||||
|
||||
// If the decimal is less than .5 we round down otherwise up
|
||||
if decimal >= 0.5 {
|
||||
rounded = math.Ceil(digit)
|
||||
} else {
|
||||
rounded = math.Floor(digit)
|
||||
}
|
||||
|
||||
// Finally we do the math to actually create a rounded number
|
||||
return rounded / precision * sign, nil
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package stats
|
||||
|
||||
import "math/rand"
|
||||
|
||||
// Sample returns sample from input with replacement or without
|
||||
func Sample(input Float64Data, takenum int, replacement bool) ([]float64, error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return nil, EmptyInput
|
||||
}
|
||||
|
||||
length := input.Len()
|
||||
if replacement {
|
||||
|
||||
result := Float64Data{}
|
||||
rand.Seed(unixnano())
|
||||
|
||||
// In every step, randomly take the num for
|
||||
for i := 0; i < takenum; i++ {
|
||||
idx := rand.Intn(length)
|
||||
result = append(result, input[idx])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
} else if !replacement && takenum <= length {
|
||||
|
||||
rand.Seed(unixnano())
|
||||
|
||||
// Get permutation of number of indexies
|
||||
perm := rand.Perm(length)
|
||||
result := Float64Data{}
|
||||
|
||||
// Get element of input by permutated index
|
||||
for _, idx := range perm[0:takenum] {
|
||||
result = append(result, input[idx])
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
return nil, BoundsErr
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// Sum adds all the numbers of a slice together
|
||||
func Sum(input Float64Data) (sum float64, err error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Add em up
|
||||
for _, n := range input {
|
||||
sum += n
|
||||
}
|
||||
|
||||
return sum, nil
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
)
|
||||
|
||||
// float64ToInt rounds a float64 to an int
|
||||
func float64ToInt(input float64) (output int) {
|
||||
r, _ := Round(input, 0)
|
||||
return int(r)
|
||||
}
|
||||
|
||||
// unixnano returns nanoseconds from UTC epoch
|
||||
func unixnano() int64 {
|
||||
return time.Now().UTC().UnixNano()
|
||||
}
|
||||
|
||||
// copyslice copies a slice of float64s
|
||||
func copyslice(input Float64Data) Float64Data {
|
||||
s := make(Float64Data, input.Len())
|
||||
copy(s, input)
|
||||
return s
|
||||
}
|
||||
|
||||
// sortedCopy returns a sorted copy of float64s
|
||||
func sortedCopy(input Float64Data) (copy Float64Data) {
|
||||
copy = copyslice(input)
|
||||
sort.Float64s(copy)
|
||||
return
|
||||
}
|
||||
|
||||
// sortedCopyDif returns a sorted copy of float64s
|
||||
// only if the original data isn't sorted.
|
||||
// Only use this if returned slice won't be manipulated!
|
||||
func sortedCopyDif(input Float64Data) (copy Float64Data) {
|
||||
if sort.Float64sAreSorted(input) {
|
||||
return input
|
||||
}
|
||||
copy = copyslice(input)
|
||||
sort.Float64s(copy)
|
||||
return
|
||||
}
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
package stats
|
||||
|
||||
import "math"
|
||||
|
||||
// _variance finds the variance for both population and sample data
|
||||
func _variance(input Float64Data, sample int) (variance float64, err error) {
|
||||
|
||||
if input.Len() == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
// Sum the square of the mean subtracted from each number
|
||||
m, _ := Mean(input)
|
||||
|
||||
for _, n := range input {
|
||||
variance += (float64(n) - m) * (float64(n) - m)
|
||||
}
|
||||
|
||||
// When getting the mean of the squared differences
|
||||
// "sample" will allow us to know if it's a sample
|
||||
// or population and wether to subtract by one or not
|
||||
return variance / float64((input.Len() - (1 * sample))), nil
|
||||
}
|
||||
|
||||
// Variance the amount of variation in the dataset
|
||||
func Variance(input Float64Data) (sdev float64, err error) {
|
||||
return PopulationVariance(input)
|
||||
}
|
||||
|
||||
// PopulationVariance finds the amount of variance within a population
|
||||
func PopulationVariance(input Float64Data) (pvar float64, err error) {
|
||||
|
||||
v, err := _variance(input, 0)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// SampleVariance finds the amount of variance within a sample
|
||||
func SampleVariance(input Float64Data) (svar float64, err error) {
|
||||
|
||||
v, err := _variance(input, 1)
|
||||
if err != nil {
|
||||
return math.NaN(), err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// Covariance is a measure of how much two sets of data change
|
||||
func Covariance(data1, data2 Float64Data) (float64, error) {
|
||||
|
||||
l1 := data1.Len()
|
||||
l2 := data2.Len()
|
||||
|
||||
if l1 == 0 || l2 == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
if l1 != l2 {
|
||||
return math.NaN(), SizeErr
|
||||
}
|
||||
|
||||
m1, _ := Mean(data1)
|
||||
m2, _ := Mean(data2)
|
||||
|
||||
// Calculate sum of squares
|
||||
var ss float64
|
||||
for i := 0; i < l1; i++ {
|
||||
delta1 := (data1.Get(i) - m1)
|
||||
delta2 := (data2.Get(i) - m2)
|
||||
ss += (delta1*delta2 - ss) / float64(i+1)
|
||||
}
|
||||
|
||||
return ss * float64(l1) / float64(l1-1), nil
|
||||
}
|
||||
|
||||
// CovariancePopulation computes covariance for entire population between two variables.
|
||||
func CovariancePopulation(data1, data2 Float64Data) (float64, error) {
|
||||
|
||||
l1 := data1.Len()
|
||||
l2 := data2.Len()
|
||||
|
||||
if l1 == 0 || l2 == 0 {
|
||||
return math.NaN(), EmptyInput
|
||||
}
|
||||
|
||||
if l1 != l2 {
|
||||
return math.NaN(), SizeErr
|
||||
}
|
||||
|
||||
m1, _ := Mean(data1)
|
||||
m2, _ := Mean(data2)
|
||||
|
||||
var s float64
|
||||
for i := 0; i < l1; i++ {
|
||||
delta1 := (data1.Get(i) - m1)
|
||||
delta2 := (data2.Get(i) - m2)
|
||||
s += delta1 * delta2
|
||||
}
|
||||
|
||||
return s / float64(l1), nil
|
||||
}
|
||||
+7
-1
@@ -39,7 +39,7 @@ func main() {
|
||||
// If you set debugging, it will log all requests to the console
|
||||
// Useful when encountering issues
|
||||
// slack.New("YOUR_TOKEN_HERE", slack.OptionDebug(true))
|
||||
groups, err := api.GetUserGroups(false)
|
||||
groups, err := api.GetUserGroups(slack.GetUserGroupsOptionIncludeUsers(false))
|
||||
if err != nil {
|
||||
fmt.Printf("%s\n", err)
|
||||
return
|
||||
@@ -86,7 +86,13 @@ See https://github.com/slack-go/slack/blob/master/examples/websocket/websocket.g
|
||||
|
||||
See https://github.com/slack-go/slack/blob/master/examples/eventsapi/events.go
|
||||
|
||||
## Socketmode Event Handler (Experimental)
|
||||
|
||||
When using socket mode, dealing with an event can be pretty lengthy as it requires you to route the event to the right place.
|
||||
|
||||
Instead, you can use `SocketmodeHandler` much like you use an HTTP handler to register which event you would like to listen to and what callback function will process that event when it occurs.
|
||||
|
||||
See [./examples/socketmode_handler/socketmode_handler.go](./examples/socketmode_handler/socketmode_handler.go)
|
||||
## Contributing
|
||||
|
||||
You are more than welcome to contribute to this project. Fork and
|
||||
|
||||
+2
-1
@@ -19,11 +19,12 @@ func (s InputBlock) BlockType() MessageBlockType {
|
||||
}
|
||||
|
||||
// NewInputBlock returns a new instance of an input block
|
||||
func NewInputBlock(blockID string, label *TextBlockObject, element BlockElement) *InputBlock {
|
||||
func NewInputBlock(blockID string, label, hint *TextBlockObject, element BlockElement) *InputBlock {
|
||||
return &InputBlock{
|
||||
Type: MBTInput,
|
||||
BlockID: blockID,
|
||||
Label: label,
|
||||
Element: element,
|
||||
Hint: hint,
|
||||
}
|
||||
}
|
||||
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type Bookmark struct {
|
||||
ID string `json:"id"`
|
||||
ChannelID string `json:"channel_id"`
|
||||
Title string `json:"title"`
|
||||
Link string `json:"link"`
|
||||
Emoji string `json:"emoji"`
|
||||
IconURL string `json:"icon_url"`
|
||||
Type string `json:"type"`
|
||||
Created JSONTime `json:"date_created"`
|
||||
Updated JSONTime `json:"date_updated"`
|
||||
Rank string `json:"rank"`
|
||||
|
||||
LastUpdatedByUserID string `json:"last_updated_by_user_id"`
|
||||
LastUpdatedByTeamID string `json:"last_updated_by_team_id"`
|
||||
|
||||
ShortcutID string `json:"shortcut_id"`
|
||||
EntityID string `json:"entity_id"`
|
||||
AppID string `json:"app_id"`
|
||||
}
|
||||
|
||||
type AddBookmarkParameters struct {
|
||||
Title string // A required title for the bookmark
|
||||
Type string // A required type for the bookmark
|
||||
Link string // URL required for type:link
|
||||
Emoji string // An optional emoji
|
||||
EntityID string
|
||||
ParentID string
|
||||
}
|
||||
|
||||
type EditBookmarkParameters struct {
|
||||
Title *string // Change the title. Set to "" to clear
|
||||
Emoji *string // Change the emoji. Set to "" to clear
|
||||
Link string // Change the link
|
||||
}
|
||||
|
||||
type addBookmarkResponse struct {
|
||||
Bookmark Bookmark `json:"bookmark"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
type editBookmarkResponse struct {
|
||||
Bookmark Bookmark `json:"bookmark"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
type listBookmarksResponse struct {
|
||||
Bookmarks []Bookmark `json:"bookmarks"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
// AddBookmark adds a bookmark in a channel
|
||||
func (api *Client) AddBookmark(channelID string, params AddBookmarkParameters) (Bookmark, error) {
|
||||
return api.AddBookmarkContext(context.Background(), channelID, params)
|
||||
}
|
||||
|
||||
// AddBookmarkContext adds a bookmark in a channel with a custom context
|
||||
func (api *Client) AddBookmarkContext(ctx context.Context, channelID string, params AddBookmarkParameters) (Bookmark, error) {
|
||||
values := url.Values{
|
||||
"channel_id": {channelID},
|
||||
"token": {api.token},
|
||||
"title": {params.Title},
|
||||
"type": {params.Type},
|
||||
}
|
||||
if params.Link != "" {
|
||||
values.Set("link", params.Link)
|
||||
}
|
||||
if params.Emoji != "" {
|
||||
values.Set("emoji", params.Emoji)
|
||||
}
|
||||
if params.EntityID != "" {
|
||||
values.Set("entity_id", params.EntityID)
|
||||
}
|
||||
if params.ParentID != "" {
|
||||
values.Set("parent_id", params.ParentID)
|
||||
}
|
||||
|
||||
response := &addBookmarkResponse{}
|
||||
if err := api.postMethod(ctx, "bookmarks.add", values, response); err != nil {
|
||||
return Bookmark{}, err
|
||||
}
|
||||
|
||||
return response.Bookmark, response.Err()
|
||||
}
|
||||
|
||||
// RemoveBookmark removes a bookmark from a channel
|
||||
func (api *Client) RemoveBookmark(channelID, bookmarkID string) error {
|
||||
return api.RemoveBookmarkContext(context.Background(), channelID, bookmarkID)
|
||||
}
|
||||
|
||||
// RemoveBookmarkContext removes a bookmark from a channel with a custom context
|
||||
func (api *Client) RemoveBookmarkContext(ctx context.Context, channelID, bookmarkID string) error {
|
||||
values := url.Values{
|
||||
"channel_id": {channelID},
|
||||
"token": {api.token},
|
||||
"bookmark_id": {bookmarkID},
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := api.postMethod(ctx, "bookmarks.remove", values, response); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
// ListBookmarks returns all bookmarks for a channel.
|
||||
func (api *Client) ListBookmarks(channelID string) ([]Bookmark, error) {
|
||||
return api.ListBookmarksContext(context.Background(), channelID)
|
||||
}
|
||||
|
||||
// ListBookmarksContext returns all bookmarks for a channel with a custom context.
|
||||
func (api *Client) ListBookmarksContext(ctx context.Context, channelID string) ([]Bookmark, error) {
|
||||
values := url.Values{
|
||||
"channel_id": {channelID},
|
||||
"token": {api.token},
|
||||
}
|
||||
|
||||
response := &listBookmarksResponse{}
|
||||
err := api.postMethod(ctx, "bookmarks.list", values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response.Bookmarks, response.Err()
|
||||
}
|
||||
|
||||
func (api *Client) EditBookmark(channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
|
||||
return api.EditBookmarkContext(context.Background(), channelID, bookmarkID, params)
|
||||
}
|
||||
|
||||
func (api *Client) EditBookmarkContext(ctx context.Context, channelID, bookmarkID string, params EditBookmarkParameters) (Bookmark, error) {
|
||||
values := url.Values{
|
||||
"channel_id": {channelID},
|
||||
"token": {api.token},
|
||||
"bookmark_id": {bookmarkID},
|
||||
}
|
||||
if params.Link != "" {
|
||||
values.Set("link", params.Link)
|
||||
}
|
||||
if params.Emoji != nil {
|
||||
values.Set("emoji", *params.Emoji)
|
||||
}
|
||||
if params.Title != nil {
|
||||
values.Set("title", *params.Title)
|
||||
}
|
||||
|
||||
response := &editBookmarkResponse{}
|
||||
if err := api.postMethod(ctx, "bookmarks.edit", values, response); err != nil {
|
||||
return Bookmark{}, err
|
||||
}
|
||||
|
||||
return response.Bookmark, response.Err()
|
||||
}
|
||||
+19
@@ -64,6 +64,9 @@ type PostMessageParameters struct {
|
||||
// chat.postEphemeral support
|
||||
Channel string `json:"channel"`
|
||||
User string `json:"user"`
|
||||
|
||||
// chat metadata support
|
||||
MetaData SlackMetadata `json:"metadata"`
|
||||
}
|
||||
|
||||
// NewPostMessageParameters provides an instance of PostMessageParameters with all the sane default values set
|
||||
@@ -285,6 +288,7 @@ type sendConfig struct {
|
||||
endpoint string
|
||||
values url.Values
|
||||
attachments []Attachment
|
||||
metadata SlackMetadata
|
||||
blocks Blocks
|
||||
responseType string
|
||||
replaceOriginal bool
|
||||
@@ -306,6 +310,7 @@ func (t sendConfig) BuildRequestContext(ctx context.Context, token, channelID st
|
||||
endpoint: t.endpoint,
|
||||
values: t.values,
|
||||
attachments: t.attachments,
|
||||
metadata: t.metadata,
|
||||
blocks: t.blocks,
|
||||
responseType: t.responseType,
|
||||
replaceOriginal: t.replaceOriginal,
|
||||
@@ -336,6 +341,7 @@ type responseURLSender struct {
|
||||
endpoint string
|
||||
values url.Values
|
||||
attachments []Attachment
|
||||
metadata SlackMetadata
|
||||
blocks Blocks
|
||||
responseType string
|
||||
replaceOriginal bool
|
||||
@@ -352,6 +358,7 @@ func (t responseURLSender) BuildRequestContext(ctx context.Context) (*http.Reque
|
||||
Timestamp: t.values.Get("ts"),
|
||||
Attachments: t.attachments,
|
||||
Blocks: t.blocks,
|
||||
Metadata: t.metadata,
|
||||
ResponseType: t.responseType,
|
||||
ReplaceOriginal: t.replaceOriginal,
|
||||
DeleteOriginal: t.deleteOriginal,
|
||||
@@ -662,6 +669,18 @@ func MsgOptionIconEmoji(iconEmoji string) MsgOption {
|
||||
}
|
||||
}
|
||||
|
||||
// MsgOptionMetadata sets message metadata
|
||||
func MsgOptionMetadata(metadata SlackMetadata) MsgOption {
|
||||
return func(config *sendConfig) error {
|
||||
config.metadata = metadata
|
||||
meta, err := json.Marshal(metadata)
|
||||
if err == nil {
|
||||
config.values.Set("metadata", string(meta))
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// UnsafeMsgOptionEndpoint deliver the message to the specified endpoint.
|
||||
// NOTE: USE AT YOUR OWN RISK: No issues relating to the use of this Option
|
||||
// will be supported by the library, it is subject to change without notice that
|
||||
|
||||
+12
-6
@@ -571,12 +571,13 @@ func (api *Client) JoinConversationContext(ctx context.Context, channelID string
|
||||
}
|
||||
|
||||
type GetConversationHistoryParameters struct {
|
||||
ChannelID string
|
||||
Cursor string
|
||||
Inclusive bool
|
||||
Latest string
|
||||
Limit int
|
||||
Oldest string
|
||||
ChannelID string
|
||||
Cursor string
|
||||
Inclusive bool
|
||||
Latest string
|
||||
Limit int
|
||||
Oldest string
|
||||
IncludeAllMetadata bool
|
||||
}
|
||||
|
||||
type GetConversationHistoryResponse struct {
|
||||
@@ -615,6 +616,11 @@ func (api *Client) GetConversationHistoryContext(ctx context.Context, params *Ge
|
||||
if params.Oldest != "" {
|
||||
values.Add("oldest", params.Oldest)
|
||||
}
|
||||
if params.IncludeAllMetadata {
|
||||
values.Add("include_all_metadata", "1")
|
||||
} else {
|
||||
values.Add("include_all_metadata", "0")
|
||||
}
|
||||
|
||||
response := GetConversationHistoryResponse{}
|
||||
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ const (
|
||||
)
|
||||
|
||||
// TextInputElement subtype of DialogInput
|
||||
// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes
|
||||
// https://api.slack.com/dialogs#option_element_attributes#text_element_attributes
|
||||
type TextInputElement struct {
|
||||
DialogInput
|
||||
MaxLength int `json:"max_length,omitempty"`
|
||||
|
||||
+1
-1
@@ -299,7 +299,7 @@ func (api *Client) UploadFile(params FileUploadParameters) (file *File, err erro
|
||||
func (api *Client) UploadFileContext(ctx context.Context, params FileUploadParameters) (file *File, err error) {
|
||||
// Test if user token is valid. This helps because client.Do doesn't like this for some reason. XXX: More
|
||||
// investigation needed, but for now this will do.
|
||||
_, err = api.AuthTest()
|
||||
_, err = api.AuthTestContext(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+5
@@ -409,6 +409,11 @@ func (t JSONTime) Time() time.Time {
|
||||
func (t *JSONTime) UnmarshalJSON(buf []byte) error {
|
||||
s := bytes.Trim(buf, `"`)
|
||||
|
||||
if bytes.EqualFold(s, []byte("null")) {
|
||||
*t = JSONTime(0)
|
||||
return nil
|
||||
}
|
||||
|
||||
v, err := strconv.Atoi(string(s))
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ func (b *Backoff) Duration() (dur time.Duration) {
|
||||
return dur
|
||||
}
|
||||
|
||||
//Resets the current value of the counter back to Min
|
||||
// Resets the current value of the counter back to Min
|
||||
func (b *Backoff) Reset() {
|
||||
b.attempts = 0
|
||||
}
|
||||
|
||||
+5
@@ -129,8 +129,13 @@ type Msg struct {
|
||||
ReplaceOriginal bool `json:"replace_original"`
|
||||
DeleteOriginal bool `json:"delete_original"`
|
||||
|
||||
// metadata
|
||||
Metadata SlackMetadata `json:"metadata,omitempty"`
|
||||
|
||||
// Block type Message
|
||||
Blocks Blocks `json:"blocks,omitempty"`
|
||||
// permalink
|
||||
Permalink string `json:"permalink,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package slack
|
||||
|
||||
// SlackMetadata https://api.slack.com/reference/metadata
|
||||
type SlackMetadata struct {
|
||||
EventType string `json:"event_type"`
|
||||
EventPayload map[string]interface{} `json:"event_payload"`
|
||||
}
|
||||
+2
-4
@@ -18,8 +18,6 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/slack-go/slack/internal/misc"
|
||||
)
|
||||
|
||||
// SlackResponse handles parsing out errors from the web api.
|
||||
@@ -52,7 +50,7 @@ type SlackErrorResponse struct {
|
||||
|
||||
func (r SlackErrorResponse) Error() string { return r.Err }
|
||||
|
||||
// RateLimitedError represents the rate limit respond from slack
|
||||
// RateLimitedError represents the rate limit response from slack
|
||||
type RateLimitedError struct {
|
||||
RetryAfter time.Duration
|
||||
}
|
||||
@@ -299,7 +297,7 @@ func checkStatusCode(resp *http.Response, d Debug) error {
|
||||
// Slack seems to send an HTML body along with 5xx error codes. Don't parse it.
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logResponse(resp, d)
|
||||
return misc.StatusCodeError{Code: resp.StatusCode, Status: resp.Status}
|
||||
return StatusCodeError{Code: resp.StatusCode, Status: resp.Status}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
+316
@@ -0,0 +1,316 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_REMOTE_FILES_CHANNEL = ""
|
||||
DEFAULT_REMOTE_FILES_TS_FROM = 0
|
||||
DEFAULT_REMOTE_FILES_TS_TO = -1
|
||||
DEFAULT_REMOTE_FILES_COUNT = 100
|
||||
)
|
||||
|
||||
// RemoteFile contains all the information for a remote file
|
||||
// For more details:
|
||||
// https://api.slack.com/messaging/files/remote
|
||||
type RemoteFile struct {
|
||||
ID string `json:"id"`
|
||||
Created JSONTime `json:"created"`
|
||||
Timestamp JSONTime `json:"timestamp"`
|
||||
Name string `json:"name"`
|
||||
Title string `json:"title"`
|
||||
Mimetype string `json:"mimetype"`
|
||||
Filetype string `json:"filetype"`
|
||||
PrettyType string `json:"pretty_type"`
|
||||
User string `json:"user"`
|
||||
Editable bool `json:"editable"`
|
||||
Size int `json:"size"`
|
||||
Mode string `json:"mode"`
|
||||
IsExternal bool `json:"is_external"`
|
||||
ExternalType string `json:"external_type"`
|
||||
IsPublic bool `json:"is_public"`
|
||||
PublicURLShared bool `json:"public_url_shared"`
|
||||
DisplayAsBot bool `json:"display_as_bot"`
|
||||
Username string `json:"username"`
|
||||
URLPrivate string `json:"url_private"`
|
||||
Permalink string `json:"permalink"`
|
||||
CommentsCount int `json:"comments_count"`
|
||||
IsStarred bool `json:"is_starred"`
|
||||
Shares Share `json:"shares"`
|
||||
Channels []string `json:"channels"`
|
||||
Groups []string `json:"groups"`
|
||||
IMs []string `json:"ims"`
|
||||
ExternalID string `json:"external_id"`
|
||||
ExternalURL string `json:"external_url"`
|
||||
HasRichPreview bool `json:"has_rich_preview"`
|
||||
}
|
||||
|
||||
// RemoteFileParameters contains required and optional parameters for a remote file.
|
||||
//
|
||||
// ExternalID is a user defined GUID, ExternalURL is where the remote file can be accessed,
|
||||
// and Title is the name of the file.
|
||||
//
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.add
|
||||
type RemoteFileParameters struct {
|
||||
ExternalID string // required
|
||||
ExternalURL string // required
|
||||
Title string // required
|
||||
Filetype string
|
||||
IndexableFileContents string
|
||||
PreviewImage string
|
||||
PreviewImageReader io.Reader
|
||||
}
|
||||
|
||||
// ListRemoteFilesParameters contains arguments for the ListRemoteFiles method.
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.list
|
||||
type ListRemoteFilesParameters struct {
|
||||
Channel string
|
||||
Cursor string
|
||||
Limit int
|
||||
TimestampFrom JSONTime
|
||||
TimestampTo JSONTime
|
||||
}
|
||||
|
||||
type remoteFileResponseFull struct {
|
||||
RemoteFile `json:"file"`
|
||||
Paging `json:"paging"`
|
||||
Files []RemoteFile `json:"files"`
|
||||
SlackResponse
|
||||
}
|
||||
|
||||
func (api *Client) remoteFileRequest(ctx context.Context, path string, values url.Values) (*remoteFileResponseFull, error) {
|
||||
response := &remoteFileResponseFull{}
|
||||
err := api.postMethod(ctx, path, values, response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return response, response.Err()
|
||||
}
|
||||
|
||||
// AddRemoteFile adds a remote file. Unlike regular files, remote files must be explicitly shared.
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.add
|
||||
func (api *Client) AddRemoteFile(params RemoteFileParameters) (*RemoteFile, error) {
|
||||
return api.AddRemoteFileContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// AddRemoteFileContext adds a remote file and setting a custom context
|
||||
// For more details see the AddRemoteFile documentation.
|
||||
func (api *Client) AddRemoteFileContext(ctx context.Context, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
|
||||
if params.ExternalID == "" || params.ExternalURL == "" || params.Title == "" {
|
||||
return nil, ErrParametersMissing
|
||||
}
|
||||
response := &remoteFileResponseFull{}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"external_id": {params.ExternalID},
|
||||
"external_url": {params.ExternalURL},
|
||||
"title": {params.Title},
|
||||
}
|
||||
if params.Filetype != "" {
|
||||
values.Add("filetype", params.Filetype)
|
||||
}
|
||||
if params.IndexableFileContents != "" {
|
||||
values.Add("indexable_file_contents", params.IndexableFileContents)
|
||||
}
|
||||
if params.PreviewImage != "" {
|
||||
err = postLocalWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.add", params.PreviewImage, "preview_image", api.token, values, response, api)
|
||||
} else if params.PreviewImageReader != nil {
|
||||
err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.add", "preview.png", "preview_image", api.token, values, params.PreviewImageReader, response, api)
|
||||
} else {
|
||||
response, err = api.remoteFileRequest(ctx, "files.remote.add", values)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.RemoteFile, response.Err()
|
||||
}
|
||||
|
||||
// ListRemoteFiles retrieves all remote files according to the parameters given. Uses cursor based pagination.
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.list
|
||||
func (api *Client) ListRemoteFiles(params ListRemoteFilesParameters) ([]RemoteFile, error) {
|
||||
return api.ListRemoteFilesContext(context.Background(), params)
|
||||
}
|
||||
|
||||
// ListRemoteFilesContext retrieves all remote files according to the parameters given with a custom context. Uses cursor based pagination.
|
||||
// For more details see the ListRemoteFiles documentation.
|
||||
func (api *Client) ListRemoteFilesContext(ctx context.Context, params ListRemoteFilesParameters) ([]RemoteFile, error) {
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if params.Channel != DEFAULT_REMOTE_FILES_CHANNEL {
|
||||
values.Add("channel", params.Channel)
|
||||
}
|
||||
if params.TimestampFrom != DEFAULT_REMOTE_FILES_TS_FROM {
|
||||
values.Add("ts_from", strconv.FormatInt(int64(params.TimestampFrom), 10))
|
||||
}
|
||||
if params.TimestampTo != DEFAULT_REMOTE_FILES_TS_TO {
|
||||
values.Add("ts_to", strconv.FormatInt(int64(params.TimestampTo), 10))
|
||||
}
|
||||
if params.Limit != DEFAULT_REMOTE_FILES_COUNT {
|
||||
values.Add("limit", strconv.Itoa(params.Limit))
|
||||
}
|
||||
if params.Cursor != "" {
|
||||
values.Add("cursor", params.Cursor)
|
||||
}
|
||||
|
||||
response, err := api.remoteFileRequest(ctx, "files.remote.list", values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params.Cursor = response.SlackResponse.ResponseMetadata.Cursor
|
||||
|
||||
return response.Files, nil
|
||||
}
|
||||
|
||||
// GetRemoteFileInfo retrieves the complete remote file information.
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.info
|
||||
func (api *Client) GetRemoteFileInfo(externalID, fileID string) (remotefile *RemoteFile, err error) {
|
||||
return api.GetRemoteFileInfoContext(context.Background(), externalID, fileID)
|
||||
}
|
||||
|
||||
// GetRemoteFileInfoContext retrieves the complete remote file information given with a custom context.
|
||||
// For more details see the GetRemoteFileInfo documentation.
|
||||
func (api *Client) GetRemoteFileInfoContext(ctx context.Context, externalID, fileID string) (remotefile *RemoteFile, err error) {
|
||||
if fileID == "" && externalID == "" {
|
||||
return nil, fmt.Errorf("either externalID or fileID is required")
|
||||
}
|
||||
if fileID != "" && externalID != "" {
|
||||
return nil, fmt.Errorf("don't provide both externalID and fileID")
|
||||
}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if fileID != "" {
|
||||
values.Add("file", fileID)
|
||||
}
|
||||
if externalID != "" {
|
||||
values.Add("external_id", externalID)
|
||||
}
|
||||
response, err := api.remoteFileRequest(ctx, "files.remote.info", values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.RemoteFile, err
|
||||
}
|
||||
|
||||
// ShareRemoteFile shares a remote file to channels
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.share
|
||||
func (api *Client) ShareRemoteFile(channels []string, externalID, fileID string) (file *RemoteFile, err error) {
|
||||
return api.ShareRemoteFileContext(context.Background(), channels, externalID, fileID)
|
||||
}
|
||||
|
||||
// ShareRemoteFileContext shares a remote file to channels with a custom context.
|
||||
// For more details see the ShareRemoteFile documentation.
|
||||
func (api *Client) ShareRemoteFileContext(ctx context.Context, channels []string, externalID, fileID string) (file *RemoteFile, err error) {
|
||||
if channels == nil || len(channels) == 0 {
|
||||
return nil, ErrParametersMissing
|
||||
}
|
||||
if fileID == "" && externalID == "" {
|
||||
return nil, fmt.Errorf("either externalID or fileID is required")
|
||||
}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"channels": {strings.Join(channels, ",")},
|
||||
}
|
||||
if fileID != "" {
|
||||
values.Add("file", fileID)
|
||||
}
|
||||
if externalID != "" {
|
||||
values.Add("external_id", externalID)
|
||||
}
|
||||
response, err := api.remoteFileRequest(ctx, "files.remote.share", values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &response.RemoteFile, err
|
||||
}
|
||||
|
||||
// UpdateRemoteFile updates a remote file
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.update
|
||||
func (api *Client) UpdateRemoteFile(fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
|
||||
return api.UpdateRemoteFileContext(context.Background(), fileID, params)
|
||||
}
|
||||
|
||||
// UpdateRemoteFileContext updates a remote file with a custom context
|
||||
// For more details see the UpdateRemoteFile documentation.
|
||||
func (api *Client) UpdateRemoteFileContext(ctx context.Context, fileID string, params RemoteFileParameters) (remotefile *RemoteFile, err error) {
|
||||
response := &remoteFileResponseFull{}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if fileID != "" {
|
||||
values.Add("file", fileID)
|
||||
}
|
||||
if params.ExternalID != "" {
|
||||
values.Add("external_id", params.ExternalID)
|
||||
}
|
||||
if params.ExternalURL != "" {
|
||||
values.Add("external_url", params.ExternalURL)
|
||||
}
|
||||
if params.Title != "" {
|
||||
values.Add("title", params.Title)
|
||||
}
|
||||
if params.Filetype != "" {
|
||||
values.Add("filetype", params.Filetype)
|
||||
}
|
||||
if params.IndexableFileContents != "" {
|
||||
values.Add("indexable_file_contents", params.IndexableFileContents)
|
||||
}
|
||||
if params.PreviewImageReader != nil {
|
||||
err = postWithMultipartResponse(ctx, api.httpclient, api.endpoint+"files.remote.update", "preview.png", "preview_image", api.token, values, params.PreviewImageReader, response, api)
|
||||
} else {
|
||||
response, err = api.remoteFileRequest(ctx, "files.remote.update", values)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &response.RemoteFile, response.Err()
|
||||
}
|
||||
|
||||
// RemoveRemoteFile removes a remote file.
|
||||
// For more details:
|
||||
// https://api.slack.com/methods/files.remote.remove
|
||||
func (api *Client) RemoveRemoteFile(externalID, fileID string) (err error) {
|
||||
return api.RemoveRemoteFileContext(context.Background(), externalID, fileID)
|
||||
}
|
||||
|
||||
// RemoveRemoteFileContext removes a remote file with a custom context
|
||||
// For more information see the RemoveRemoteFiles documentation.
|
||||
func (api *Client) RemoveRemoteFileContext(ctx context.Context, externalID, fileID string) (err error) {
|
||||
if fileID == "" && externalID == "" {
|
||||
return fmt.Errorf("either externalID or fileID is required")
|
||||
}
|
||||
if fileID != "" && externalID != "" {
|
||||
return fmt.Errorf("don't provide both externalID and fileID")
|
||||
}
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
}
|
||||
if fileID != "" {
|
||||
values.Add("file", fileID)
|
||||
}
|
||||
if externalID != "" {
|
||||
values.Add("external_id", externalID)
|
||||
}
|
||||
_, err = api.remoteFileRequest(ctx, "files.remote.remove", values)
|
||||
return err
|
||||
}
|
||||
+10
-9
@@ -130,17 +130,18 @@ func (api *Client) ListStarsContext(ctx context.Context, params StarsParameters)
|
||||
// GetStarred returns a list of StarredItem items.
|
||||
//
|
||||
// The user then has to iterate over them and figure out what they should
|
||||
// be looking at according to what is in the Type.
|
||||
// for _, item := range items {
|
||||
// switch c.Type {
|
||||
// case "file_comment":
|
||||
// log.Println(c.Comment)
|
||||
// case "file":
|
||||
// ...
|
||||
// be looking at according to what is in the Type:
|
||||
//
|
||||
// for _, item := range items {
|
||||
// switch c.Type {
|
||||
// case "file_comment":
|
||||
// log.Println(c.Comment)
|
||||
// case "file":
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// This function still exists to maintain backwards compatibility.
|
||||
// I exposed it as returning []StarredItem, so it shall stay as StarredItem
|
||||
// I exposed it as returning []StarredItem, so it shall stay as StarredItem.
|
||||
func (api *Client) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) {
|
||||
return api.GetStarredContext(context.Background(), params)
|
||||
}
|
||||
|
||||
Generated
Vendored
+1
-1
@@ -1,4 +1,4 @@
|
||||
package misc
|
||||
package slack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+57
-12
@@ -183,32 +183,77 @@ func (api *Client) GetUserGroupsContext(ctx context.Context, options ...GetUserG
|
||||
return response.UserGroups, nil
|
||||
}
|
||||
|
||||
// UpdateUserGroupsOption options for the UpdateUserGroup method call.
|
||||
type UpdateUserGroupsOption func(*UpdateUserGroupsParams)
|
||||
|
||||
// UpdateUserGroupsOptionName change the name of the User Group (default: empty, so it's no-op)
|
||||
func UpdateUserGroupsOptionName(name string) UpdateUserGroupsOption {
|
||||
return func(params *UpdateUserGroupsParams) {
|
||||
params.Name = name
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserGroupsOptionHandle change the handle of the User Group (default: empty, so it's no-op)
|
||||
func UpdateUserGroupsOptionHandle(handle string) UpdateUserGroupsOption {
|
||||
return func(params *UpdateUserGroupsParams) {
|
||||
params.Handle = handle
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserGroupsOptionDescription change the description of the User Group. (default: nil, so it's no-op)
|
||||
func UpdateUserGroupsOptionDescription(description *string) UpdateUserGroupsOption {
|
||||
return func(params *UpdateUserGroupsParams) {
|
||||
params.Description = description
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserGroupsOptionChannels change the default channels of the User Group. (default: unspecified, so it's no-op)
|
||||
func UpdateUserGroupsOptionChannels(channels []string) UpdateUserGroupsOption {
|
||||
return func(params *UpdateUserGroupsParams) {
|
||||
params.Channels = &channels
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateUserGroupsParams contains arguments for UpdateUserGroup method call
|
||||
type UpdateUserGroupsParams struct {
|
||||
Name string
|
||||
Handle string
|
||||
Description *string
|
||||
Channels *[]string
|
||||
}
|
||||
|
||||
// UpdateUserGroup will update an existing user group
|
||||
func (api *Client) UpdateUserGroup(userGroup UserGroup) (UserGroup, error) {
|
||||
return api.UpdateUserGroupContext(context.Background(), userGroup)
|
||||
func (api *Client) UpdateUserGroup(userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) {
|
||||
return api.UpdateUserGroupContext(context.Background(), userGroupID, options...)
|
||||
}
|
||||
|
||||
// UpdateUserGroupContext will update an existing user group with a custom context
|
||||
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroup UserGroup) (UserGroup, error) {
|
||||
func (api *Client) UpdateUserGroupContext(ctx context.Context, userGroupID string, options ...UpdateUserGroupsOption) (UserGroup, error) {
|
||||
params := UpdateUserGroupsParams{}
|
||||
|
||||
for _, opt := range options {
|
||||
opt(¶ms)
|
||||
}
|
||||
|
||||
values := url.Values{
|
||||
"token": {api.token},
|
||||
"usergroup": {userGroup.ID},
|
||||
"usergroup": {userGroupID},
|
||||
}
|
||||
|
||||
if userGroup.Name != "" {
|
||||
values["name"] = []string{userGroup.Name}
|
||||
if params.Name != "" {
|
||||
values["name"] = []string{params.Name}
|
||||
}
|
||||
|
||||
if userGroup.Handle != "" {
|
||||
values["handle"] = []string{userGroup.Handle}
|
||||
if params.Handle != "" {
|
||||
values["handle"] = []string{params.Handle}
|
||||
}
|
||||
|
||||
if userGroup.Description != "" {
|
||||
values["description"] = []string{userGroup.Description}
|
||||
if params.Description != nil {
|
||||
values["description"] = []string{*params.Description}
|
||||
}
|
||||
|
||||
if len(userGroup.Prefs.Channels) > 0 {
|
||||
values["channels"] = []string{strings.Join(userGroup.Prefs.Channels, ",")}
|
||||
if params.Channels != nil {
|
||||
values["channels"] = []string{strings.Join(*params.Channels, ",")}
|
||||
}
|
||||
|
||||
response, err := api.userGroupRequest(ctx, "usergroups.update", values)
|
||||
|
||||
+13
-4
@@ -292,6 +292,13 @@ func GetUsersOptionPresence(n bool) GetUsersOption {
|
||||
}
|
||||
}
|
||||
|
||||
// GetUsersOptionTeamID include team Id
|
||||
func GetUsersOptionTeamID(teamId string) GetUsersOption {
|
||||
return func(p *UserPagination) {
|
||||
p.teamId = teamId
|
||||
}
|
||||
}
|
||||
|
||||
func newUserPagination(c *Client, options ...GetUsersOption) (up UserPagination) {
|
||||
up = UserPagination{
|
||||
c: c,
|
||||
@@ -310,6 +317,7 @@ type UserPagination struct {
|
||||
Users []User
|
||||
limit int
|
||||
presence bool
|
||||
teamId string
|
||||
previousResp *ResponseMetadata
|
||||
c *Client
|
||||
}
|
||||
@@ -344,6 +352,7 @@ func (t UserPagination) Next(ctx context.Context) (_ UserPagination, err error)
|
||||
"presence": {strconv.FormatBool(t.presence)},
|
||||
"token": {t.c.token},
|
||||
"cursor": {t.previousResp.Cursor},
|
||||
"team_id": {t.teamId},
|
||||
"include_locale": {strconv.FormatBool(true)},
|
||||
}
|
||||
|
||||
@@ -364,13 +373,13 @@ func (api *Client) GetUsersPaginated(options ...GetUsersOption) UserPagination {
|
||||
}
|
||||
|
||||
// GetUsers returns the list of users (with their detailed information)
|
||||
func (api *Client) GetUsers() ([]User, error) {
|
||||
return api.GetUsersContext(context.Background())
|
||||
func (api *Client) GetUsers(options ...GetUsersOption) ([]User, error) {
|
||||
return api.GetUsersContext(context.Background(), options...)
|
||||
}
|
||||
|
||||
// GetUsersContext returns the list of users (with their detailed information) with a custom context
|
||||
func (api *Client) GetUsersContext(ctx context.Context) (results []User, err error) {
|
||||
p := api.GetUsersPaginated()
|
||||
func (api *Client) GetUsersContext(ctx context.Context, options ...GetUsersOption) (results []User, err error) {
|
||||
p := api.GetUsersPaginated(options...)
|
||||
for err == nil {
|
||||
p, err = p.Next(ctx)
|
||||
if err == nil {
|
||||
|
||||
+19
-18
@@ -18,24 +18,25 @@ type ViewState struct {
|
||||
|
||||
type View struct {
|
||||
SlackResponse
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"team_id"`
|
||||
Type ViewType `json:"type"`
|
||||
Title *TextBlockObject `json:"title"`
|
||||
Close *TextBlockObject `json:"close"`
|
||||
Submit *TextBlockObject `json:"submit"`
|
||||
Blocks Blocks `json:"blocks"`
|
||||
PrivateMetadata string `json:"private_metadata"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
State *ViewState `json:"state"`
|
||||
Hash string `json:"hash"`
|
||||
ClearOnClose bool `json:"clear_on_close"`
|
||||
NotifyOnClose bool `json:"notify_on_close"`
|
||||
RootViewID string `json:"root_view_id"`
|
||||
PreviousViewID string `json:"previous_view_id"`
|
||||
AppID string `json:"app_id"`
|
||||
ExternalID string `json:"external_id"`
|
||||
BotID string `json:"bot_id"`
|
||||
ID string `json:"id"`
|
||||
TeamID string `json:"team_id"`
|
||||
Type ViewType `json:"type"`
|
||||
Title *TextBlockObject `json:"title"`
|
||||
Close *TextBlockObject `json:"close"`
|
||||
Submit *TextBlockObject `json:"submit"`
|
||||
Blocks Blocks `json:"blocks"`
|
||||
PrivateMetadata string `json:"private_metadata"`
|
||||
CallbackID string `json:"callback_id"`
|
||||
State *ViewState `json:"state"`
|
||||
Hash string `json:"hash"`
|
||||
ClearOnClose bool `json:"clear_on_close"`
|
||||
NotifyOnClose bool `json:"notify_on_close"`
|
||||
RootViewID string `json:"root_view_id"`
|
||||
PreviousViewID string `json:"previous_view_id"`
|
||||
AppID string `json:"app_id"`
|
||||
ExternalID string `json:"external_id"`
|
||||
BotID string `json:"bot_id"`
|
||||
AppInstalledTeamID string `json:"app_installed_team_id"`
|
||||
}
|
||||
|
||||
type ViewSubmissionCallbackResponseURL struct {
|
||||
|
||||
+7
-1
@@ -5,6 +5,8 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
@@ -21,6 +23,7 @@ type WebhookMessage struct {
|
||||
ResponseType string `json:"response_type,omitempty"`
|
||||
ReplaceOriginal bool `json:"replace_original,omitempty"`
|
||||
DeleteOriginal bool `json:"delete_original,omitempty"`
|
||||
ReplyBroadcast bool `json:"reply_broadcast,omitempty"`
|
||||
}
|
||||
|
||||
func PostWebhook(url string, msg *WebhookMessage) error {
|
||||
@@ -51,7 +54,10 @@ func PostWebhookCustomHTTPContext(ctx context.Context, url string, httpClient *h
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to post webhook: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
io.Copy(ioutil.Discard, resp.Body)
|
||||
resp.Body.Close()
|
||||
}()
|
||||
|
||||
return checkStatusCode(resp, discard{})
|
||||
}
|
||||
|
||||
+28
-4
@@ -13,10 +13,34 @@ import (
|
||||
|
||||
"github.com/slack-go/slack/internal/backoff"
|
||||
"github.com/slack-go/slack/internal/errorsx"
|
||||
"github.com/slack-go/slack/internal/misc"
|
||||
"github.com/slack-go/slack/internal/timex"
|
||||
)
|
||||
|
||||
// UnmappedError represents error occurred when there is no mapping between given event name
|
||||
// and corresponding Go struct.
|
||||
type UnmappedError struct {
|
||||
// EventType returns event type name.
|
||||
EventType string
|
||||
// RawEvent returns raw event body.
|
||||
RawEvent json.RawMessage
|
||||
|
||||
ctxMsg string
|
||||
}
|
||||
|
||||
// NewUnmappedError returns new UnmappedError instance.
|
||||
func NewUnmappedError(ctxMsg, eventType string, raw json.RawMessage) *UnmappedError {
|
||||
return &UnmappedError{
|
||||
ctxMsg: ctxMsg,
|
||||
EventType: eventType,
|
||||
RawEvent: raw,
|
||||
}
|
||||
}
|
||||
|
||||
// Error returns human-readable error message.
|
||||
func (u UnmappedError) Error() string {
|
||||
return fmt.Sprintf("%s: Received unmapped event %q", u.ctxMsg, u.EventType)
|
||||
}
|
||||
|
||||
// ManageConnection can be called on a Slack RTM instance returned by the
|
||||
// NewRTM method. It will connect to the slack RTM API and handle all incoming
|
||||
// and outgoing events. If a connection fails then it will attempt to reconnect
|
||||
@@ -127,7 +151,7 @@ func (rtm *RTM) connect(connectionCount int, useRTMStart bool) (*Info, *websocke
|
||||
}
|
||||
|
||||
switch actual := err.(type) {
|
||||
case misc.StatusCodeError:
|
||||
case StatusCodeError:
|
||||
if actual.Code == http.StatusNotFound {
|
||||
rtm.Debugf("invalid auth when connecting with RTM: %s", err)
|
||||
rtm.IncomingEvents <- RTMEvent{"invalid_auth", &InvalidAuthEvent{}}
|
||||
@@ -475,7 +499,7 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) {
|
||||
v, exists := EventMapping[typeStr]
|
||||
if !exists {
|
||||
rtm.Debugf("RTM Error - received unmapped event %q: %s\n", typeStr, string(event))
|
||||
err := fmt.Errorf("RTM Error: Received unmapped event %q: %s", typeStr, string(event))
|
||||
err := NewUnmappedError("RTM Error", typeStr, event)
|
||||
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||
return
|
||||
}
|
||||
@@ -484,7 +508,7 @@ func (rtm *RTM) handleEvent(typeStr string, event json.RawMessage) {
|
||||
err := json.Unmarshal(event, recvEvent)
|
||||
if err != nil {
|
||||
rtm.Debugf("RTM Error, could not unmarshall event %q: %s\n", typeStr, string(event))
|
||||
err := fmt.Errorf("RTM Error: Could not unmarshall event %q: %s", typeStr, string(event))
|
||||
err := fmt.Errorf("RTM Error: Could not unmarshall event %q", typeStr)
|
||||
rtm.IncomingEvents <- RTMEvent{"unmarshalling_error", &UnmarshallingErrorEvent{err}}
|
||||
return
|
||||
}
|
||||
|
||||
+2
-2
@@ -14,9 +14,9 @@ type SubteamMembersChangedEvent struct {
|
||||
DatePreviousUpdate JSONTime `json:"date_previous_update"`
|
||||
DateUpdate JSONTime `json:"date_update"`
|
||||
AddedUsers []string `json:"added_users"`
|
||||
AddedUsersCount string `json:"added_users_count"`
|
||||
AddedUsersCount int `json:"added_users_count"`
|
||||
RemovedUsers []string `json:"removed_users"`
|
||||
RemovedUsersCount string `json:"removed_users_count"`
|
||||
RemovedUsersCount int `json:"removed_users_count"`
|
||||
}
|
||||
|
||||
// SubteamSelfAddedEvent represents an event of you have been added to a User Group
|
||||
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
package slack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type (
|
||||
WorkflowStepCompletedRequest struct {
|
||||
WorkflowStepExecuteID string `json:"workflow_step_execute_id"`
|
||||
Outputs map[string]string `json:"outputs"`
|
||||
}
|
||||
|
||||
WorkflowStepFailedRequest struct {
|
||||
WorkflowStepExecuteID string `json:"workflow_step_execute_id"`
|
||||
Error struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error"`
|
||||
}
|
||||
)
|
||||
|
||||
type WorkflowStepCompletedRequestOption func(opt WorkflowStepCompletedRequest) error
|
||||
|
||||
func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption {
|
||||
return func(opt WorkflowStepCompletedRequest) error {
|
||||
if len(outputs) > 0 {
|
||||
opt.Outputs = outputs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WorkflowStepCompleted indicates step is completed
|
||||
func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error {
|
||||
// More information: https://api.slack.com/methods/workflows.stepCompleted
|
||||
r := WorkflowStepCompletedRequest{
|
||||
WorkflowStepExecuteID: workflowStepExecuteID,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(r)
|
||||
}
|
||||
|
||||
endpoint := api.endpoint + "workflows.stepCompleted"
|
||||
jsonData, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WorkflowStepFailed indicates step is failed
|
||||
func (api *Client) WorkflowStepFailed(workflowStepExecuteID string, errorMessage string) error {
|
||||
// More information: https://api.slack.com/methods/workflows.stepFailed
|
||||
r := WorkflowStepFailedRequest{
|
||||
WorkflowStepExecuteID: workflowStepExecuteID,
|
||||
}
|
||||
r.Error.Message = errorMessage
|
||||
|
||||
endpoint := api.endpoint + "workflows.stepFailed"
|
||||
jsonData, err := json.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response := &SlackResponse{}
|
||||
if err := postJSON(context.Background(), api.httpclient, endpoint, api.token, jsonData, response, api); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !response.Ok {
|
||||
return response.Err()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+22
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/bsonrw"
|
||||
"go.mongodb.org/mongo-driver/bson/bsontype"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -118,11 +119,32 @@ type EncodeContext struct {
|
||||
type DecodeContext struct {
|
||||
*Registry
|
||||
Truncate bool
|
||||
|
||||
// Ancestor is the type of a containing document. This is mainly used to determine what type
|
||||
// should be used when decoding an embedded document into an empty interface. For example, if
|
||||
// Ancestor is a bson.M, BSON embedded document values being decoded into an empty interface
|
||||
// will be decoded into a bson.M.
|
||||
//
|
||||
// Deprecated: Use DefaultDocumentM or DefaultDocumentD instead.
|
||||
Ancestor reflect.Type
|
||||
|
||||
// defaultDocumentType specifies the Go type to decode top-level and nested BSON documents into. In particular, the
|
||||
// usage for this field is restricted to data typed as "interface{}" or "map[string]interface{}". If DocumentType is
|
||||
// set to a type that a BSON document cannot be unmarshaled into (e.g. "string"), unmarshalling will result in an
|
||||
// error. DocumentType overrides the Ancestor field.
|
||||
defaultDocumentType reflect.Type
|
||||
}
|
||||
|
||||
// DefaultDocumentM will decode empty documents using the primitive.M type. This behavior is restricted to data typed as
|
||||
// "interface{}" or "map[string]interface{}".
|
||||
func (dc *DecodeContext) DefaultDocumentM() {
|
||||
dc.defaultDocumentType = reflect.TypeOf(primitive.M{})
|
||||
}
|
||||
|
||||
// DefaultDocumentD will decode empty documents using the primitive.D type. This behavior is restricted to data typed as
|
||||
// "interface{}" or "map[string]interface{}".
|
||||
func (dc *DecodeContext) DefaultDocumentD() {
|
||||
dc.defaultDocumentType = reflect.TypeOf(primitive.D{})
|
||||
}
|
||||
|
||||
// ValueCodec is the interface that groups the methods to encode and decode
|
||||
|
||||
Generated
Vendored
+8
-8
@@ -1463,7 +1463,7 @@ func (dvd DefaultValueDecoders) ValueUnmarshalerDecodeValue(dc DecodeContext, vr
|
||||
if !val.CanAddr() {
|
||||
return ValueDecoderError{Name: "ValueUnmarshalerDecodeValue", Types: []reflect.Type{tValueUnmarshaler}, Received: val}
|
||||
}
|
||||
val = val.Addr() // If they type doesn't implement the interface, a pointer to it must.
|
||||
val = val.Addr() // If the type doesn't implement the interface, a pointer to it must.
|
||||
}
|
||||
|
||||
t, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
|
||||
@@ -1492,13 +1492,6 @@ func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(dc DecodeContext, vr bson
|
||||
val.Set(reflect.New(val.Type().Elem()))
|
||||
}
|
||||
|
||||
if !val.Type().Implements(tUnmarshaler) {
|
||||
if !val.CanAddr() {
|
||||
return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
|
||||
}
|
||||
val = val.Addr() // If they type doesn't implement the interface, a pointer to it must.
|
||||
}
|
||||
|
||||
_, src, err := bsonrw.Copier{}.CopyValueToBytes(vr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1516,6 +1509,13 @@ func (dvd DefaultValueDecoders) UnmarshalerDecodeValue(dc DecodeContext, vr bson
|
||||
return nil
|
||||
}
|
||||
|
||||
if !val.Type().Implements(tUnmarshaler) {
|
||||
if !val.CanAddr() {
|
||||
return ValueDecoderError{Name: "UnmarshalerDecodeValue", Types: []reflect.Type{tUnmarshaler}, Received: val}
|
||||
}
|
||||
val = val.Addr() // If the type doesn't implement the interface, a pointer to it must.
|
||||
}
|
||||
|
||||
fn := val.Convert(tUnmarshaler).MethodByName("UnmarshalBSON")
|
||||
errVal := fn.Call([]reflect.Value{reflect.ValueOf(src)})[0]
|
||||
if !errVal.IsNil() {
|
||||
|
||||
+6
@@ -1,3 +1,9 @@
|
||||
// Copyright (C) MongoDB, Inc. 2022-present.
|
||||
//
|
||||
// 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
|
||||
|
||||
// Package bsoncodec provides a system for encoding values to BSON representations and decoding
|
||||
// values from BSON representations. This package considers both binary BSON and ExtendedJSON as
|
||||
// BSON representations. The types in this package enable a flexible system for handling this
|
||||
|
||||
Generated
Vendored
+12
-5
@@ -57,11 +57,18 @@ func (eic EmptyInterfaceCodec) EncodeValue(ec EncodeContext, vw bsonrw.ValueWrit
|
||||
|
||||
func (eic EmptyInterfaceCodec) getEmptyInterfaceDecodeType(dc DecodeContext, valueType bsontype.Type) (reflect.Type, error) {
|
||||
isDocument := valueType == bsontype.Type(0) || valueType == bsontype.EmbeddedDocument
|
||||
if isDocument && dc.Ancestor != nil {
|
||||
// Using ancestor information rather than looking up the type map entry forces consistent decoding.
|
||||
// If we're decoding into a bson.D, subdocuments should also be decoded as bson.D, even if a type map entry
|
||||
// has been registered.
|
||||
return dc.Ancestor, nil
|
||||
if isDocument {
|
||||
if dc.defaultDocumentType != nil {
|
||||
// If the bsontype is an embedded document and the DocumentType is set on the DecodeContext, then return
|
||||
// that type.
|
||||
return dc.defaultDocumentType, nil
|
||||
}
|
||||
if dc.Ancestor != nil {
|
||||
// Using ancestor information rather than looking up the type map entry forces consistent decoding.
|
||||
// If we're decoding into a bson.D, subdocuments should also be decoded as bson.D, even if a type map entry
|
||||
// has been registered.
|
||||
return dc.Ancestor, nil
|
||||
}
|
||||
}
|
||||
|
||||
rtype, err := dc.LookupTypeMapEntry(valueType)
|
||||
|
||||
+21
@@ -7,6 +7,7 @@
|
||||
package bsoncodec
|
||||
|
||||
import (
|
||||
"encoding"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
@@ -230,6 +231,19 @@ func (mc *MapCodec) encodeKey(val reflect.Value) (string, error) {
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
// keys implement encoding.TextMarshaler are marshaled.
|
||||
if km, ok := val.Interface().(encoding.TextMarshaler); ok {
|
||||
if val.Kind() == reflect.Ptr && val.IsNil() {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
buf, err := km.MarshalText()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
switch val.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
@@ -241,6 +255,7 @@ func (mc *MapCodec) encodeKey(val reflect.Value) (string, error) {
|
||||
}
|
||||
|
||||
var keyUnmarshalerType = reflect.TypeOf((*KeyUnmarshaler)(nil)).Elem()
|
||||
var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||
|
||||
func (mc *MapCodec) decodeKey(key string, keyType reflect.Type) (reflect.Value, error) {
|
||||
keyVal := reflect.ValueOf(key)
|
||||
@@ -252,6 +267,12 @@ func (mc *MapCodec) decodeKey(key string, keyType reflect.Type) (reflect.Value,
|
||||
v := keyVal.Interface().(KeyUnmarshaler)
|
||||
err = v.UnmarshalKey(key)
|
||||
keyVal = keyVal.Elem()
|
||||
// Try to decode encoding.TextUnmarshalers.
|
||||
case reflect.PtrTo(keyType).Implements(textUnmarshalerType):
|
||||
keyVal = reflect.New(keyType)
|
||||
v := keyVal.Interface().(encoding.TextUnmarshaler)
|
||||
err = v.UnmarshalText([]byte(key))
|
||||
keyVal = keyVal.Elem()
|
||||
// Otherwise, go to type specific behavior
|
||||
default:
|
||||
switch keyType.Kind() {
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Copyright (C) MongoDB, Inc. 2022-present.
|
||||
//
|
||||
// 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
|
||||
|
||||
// Package bsonoptions defines the optional configurations for the BSON codecs.
|
||||
package bsonoptions
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user