Update direct dependencies where possible
This commit is contained in:
parent
f716b8fc0f
commit
09875fe160
|
@ -72,7 +72,7 @@ Used by at least 3 projects. Feel free to make a PR to add your project to this
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
Accounts to one of the supported bridges
|
Accounts to one of the supported bridges
|
||||||
* [Mattermost](https://github.com/mattermost/platform/) 3.8.x - 3.10.x, 4.x, 5.x
|
* [Mattermost](https://github.com/mattermost/mattermost-server/) 3.8.x - 3.10.x, 4.x, 5.x
|
||||||
* [IRC](http://www.mirc.com/servers.html)
|
* [IRC](http://www.mirc.com/servers.html)
|
||||||
* [XMPP](https://jabber.org)
|
* [XMPP](https://jabber.org)
|
||||||
* [Gitter](https://gitter.im)
|
* [Gitter](https://gitter.im)
|
||||||
|
@ -237,7 +237,7 @@ Matterbridge wouldn't exist without these libraries:
|
||||||
* gops - https://github.com/google/gops
|
* gops - https://github.com/google/gops
|
||||||
* gozulipbot - https://github.com/ifo/gozulipbot
|
* gozulipbot - https://github.com/ifo/gozulipbot
|
||||||
* irc - https://github.com/lrstanley/girc
|
* irc - https://github.com/lrstanley/girc
|
||||||
* mattermost - https://github.com/mattermost/platform
|
* mattermost - https://github.com/mattermost/mattermost-server
|
||||||
* matrix - https://github.com/matrix-org/gomatrix
|
* matrix - https://github.com/matrix-org/gomatrix
|
||||||
* slack - https://github.com/nlopes/slack
|
* slack - https://github.com/nlopes/slack
|
||||||
* steam - https://github.com/Philipp15b/go-steam
|
* steam - https://github.com/Philipp15b/go-steam
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"github.com/42wim/matterbridge/bridge/helper"
|
"github.com/42wim/matterbridge/bridge/helper"
|
||||||
"github.com/42wim/matterbridge/matterclient"
|
"github.com/42wim/matterbridge/matterclient"
|
||||||
"github.com/42wim/matterbridge/matterhook"
|
"github.com/42wim/matterbridge/matterhook"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
47
go.mod
47
go.mod
|
@ -3,40 +3,37 @@ module github.com/42wim/matterbridge
|
||||||
require (
|
require (
|
||||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557
|
||||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
|
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 // indirect
|
||||||
github.com/Philipp15b/go-steam v0.0.0-20161020161927-e0f3bb9566e3
|
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329
|
||||||
github.com/alecthomas/log4go v0.0.0-20160307011253-e5dc62318d9b // indirect
|
|
||||||
github.com/bwmarrin/discordgo v0.19.0
|
github.com/bwmarrin/discordgo v0.19.0
|
||||||
github.com/dfordsoft/golib v0.0.0-20180313113957-2ea3495aee1d
|
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec
|
||||||
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a // indirect
|
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180428185002-212b1541150c
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
|
||||||
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc // indirect
|
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc // indirect
|
||||||
github.com/google/gops v0.0.0-20170319002943-62f833fc9f6c
|
github.com/google/gops v0.3.5
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
|
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f // indirect
|
||||||
github.com/gorilla/schema v0.0.0-20170317173100-f3c80893412c
|
github.com/gorilla/schema v1.0.2
|
||||||
github.com/gorilla/websocket v1.4.0
|
github.com/gorilla/websocket v1.4.0
|
||||||
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad
|
github.com/hashicorp/golang-lru v0.5.0
|
||||||
github.com/hashicorp/hcl v0.0.0-20171017181929-23c074d0eceb // indirect
|
|
||||||
github.com/hpcloud/tail v1.0.0 // indirect
|
github.com/hpcloud/tail v1.0.0 // indirect
|
||||||
github.com/jpillora/backoff v0.0.0-20170222002228-06c7a16c845d
|
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7
|
||||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||||
github.com/kardianos/osext v0.0.0-20170207191655-9b883c5eb462 // indirect
|
github.com/kardianos/osext v0.0.0-20170207191655-9b883c5eb462 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/labstack/echo v0.0.0-20180219162101-7eec915044a1
|
github.com/labstack/echo v3.3.5+incompatible
|
||||||
github.com/labstack/gommon v0.2.1 // indirect
|
github.com/labstack/gommon v0.2.1 // indirect
|
||||||
github.com/lrstanley/girc v0.0.0-20180913221000-0fb5b684054e
|
github.com/lrstanley/girc v0.0.0-20181114171214-3aee8c249519
|
||||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 // indirect
|
||||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 // indirect
|
||||||
github.com/magiconair/properties v0.0.0-20180217134545-2c9e95027885 // indirect
|
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91
|
||||||
github.com/matterbridge/gomatrix v0.0.0-20171224233421-78ac6a1a0f5f
|
github.com/matterbridge/gomatrix v0.0.0-20171224233421-78ac6a1a0f5f
|
||||||
github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544
|
github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544
|
||||||
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
|
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61
|
||||||
github.com/mattermost/platform v4.6.2+incompatible
|
github.com/mattermost/mattermost-server v5.5.0+incompatible
|
||||||
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597 // indirect
|
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc // indirect
|
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 // indirect
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
|
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 // indirect
|
||||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff // indirect
|
||||||
github.com/nicksnyder/go-i18n v1.4.0 // indirect
|
github.com/nicksnyder/go-i18n v1.4.0 // indirect
|
||||||
|
@ -45,36 +42,34 @@ require (
|
||||||
github.com/onsi/gomega v1.4.1 // indirect
|
github.com/onsi/gomega v1.4.1 // indirect
|
||||||
github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83
|
github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83
|
||||||
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 // indirect
|
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 // indirect
|
||||||
github.com/pelletier/go-toml v0.0.0-20180228233631-05bcc0fb0d3e // indirect
|
github.com/peterhellberg/emojilib v0.0.0-20180820090156-eeb3823dab9a
|
||||||
github.com/peterhellberg/emojilib v0.0.0-20170616163716-41920917e271
|
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a
|
github.com/rs/xid v1.2.1
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible
|
github.com/russross/blackfriday v2.0.0+incompatible
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca
|
||||||
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 // indirect
|
github.com/shazow/ssh-chat v0.0.0-20181028152505-f36d7eb9ccc6
|
||||||
github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||||
github.com/sirupsen/logrus v1.2.0
|
github.com/sirupsen/logrus v1.2.0
|
||||||
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
|
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 // indirect
|
||||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
|
||||||
github.com/spf13/afero v0.0.0-20180211162714-bbf41cb36dff // indirect
|
github.com/spf13/cast v1.3.0 // indirect
|
||||||
github.com/spf13/cast v1.2.0 // indirect
|
github.com/spf13/pflag v1.0.3 // indirect
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec // indirect
|
github.com/spf13/viper v1.2.1
|
||||||
github.com/spf13/pflag v0.0.0-20180220143236-ee5fd03fd6ac // indirect
|
|
||||||
github.com/spf13/viper v0.0.0-20171227194143-aafc9e6bc7b7
|
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.2.2
|
||||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||||
github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a // indirect
|
github.com/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a // indirect
|
||||||
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
|
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 // indirect
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2 // indirect
|
||||||
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6
|
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6
|
||||||
|
go.uber.org/atomic v1.3.2 // indirect
|
||||||
|
go.uber.org/multierr v1.1.0 // indirect
|
||||||
|
go.uber.org/zap v1.9.1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect
|
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 // indirect
|
||||||
golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37 // indirect
|
golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37 // indirect
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
|
||||||
golang.org/x/sys v0.0.0-20181116161606-93218def8b18 // indirect
|
golang.org/x/sys v0.0.0-20181116161606-93218def8b18 // indirect
|
||||||
golang.org/x/text v0.0.0-20180511172408-5c1cf69b5978 // indirect
|
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
gopkg.in/yaml.v2 v2.0.0-20160301204022-a83829b6f129 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
114
go.sum
114
go.sum
|
@ -2,40 +2,42 @@ github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557 h1:IZtuWGfzQnKnCSu
|
||||||
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557/go.mod h1:jL0YSXMs/txjtGJ4PWrmETOk6KUHMDPMshgQZlTeB3Y=
|
github.com/42wim/go-gitter v0.0.0-20170828205020-017310c2d557/go.mod h1:jL0YSXMs/txjtGJ4PWrmETOk6KUHMDPMshgQZlTeB3Y=
|
||||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 h1:v/zr4ns/4sSahF9KBm4Uc933bLsEEv7LuT63CJ019yo=
|
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14 h1:v/zr4ns/4sSahF9KBm4Uc933bLsEEv7LuT63CJ019yo=
|
||||||
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.0.0-20170318202913-d94612f9fc14/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/Philipp15b/go-steam v0.0.0-20161020161927-e0f3bb9566e3 h1:V4+1E1SRYUySqwOoI3ZphFADtabbF568zTHa5ix/zU0=
|
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329 h1:xZBoq249G9MSt+XuY7sVQzcfONJ6IQuwpCK+KAaOpnY=
|
||||||
github.com/Philipp15b/go-steam v0.0.0-20161020161927-e0f3bb9566e3/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
|
github.com/Philipp15b/go-steam v1.0.1-0.20180818081528-681bd9573329/go.mod h1:HuVM+sZFzumUdKPWiz+IlCMb4RdsKdT3T+nQBKL+sYg=
|
||||||
github.com/alecthomas/log4go v0.0.0-20160307011253-e5dc62318d9b h1:1OpGXps6UOY5HtQaQcLowsV1qMWCNBzhFvK7q4fgXtc=
|
github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58 h1:MkpmYfld/S8kXqTYI68DfL8/hHXjHogL120Dy00TIxc=
|
||||||
github.com/alecthomas/log4go v0.0.0-20160307011253-e5dc62318d9b/go.mod h1:iCVmQ9g4TfaRX5m5jq5sXY7RXYWPv9/PynM/GocbG3w=
|
github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58/go.mod h1:YNfsMyWSs+h+PaYkxGeMVmVCX75Zj/pqdjbu12ciCYE=
|
||||||
github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY=
|
github.com/bwmarrin/discordgo v0.19.0 h1:kMED/DB0NR1QhRcalb85w0Cu3Ep2OrGAqZH1R5awQiY=
|
||||||
github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
github.com/bwmarrin/discordgo v0.19.0/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dfordsoft/golib v0.0.0-20180313113957-2ea3495aee1d h1:rONNnZDE5CYuaSFQk+gP4GEQTXEUcyQ5p6p/dgxIHas=
|
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec h1:JEUiu7P9smN7zgX87a2zVnnbPPickIM9Gf9OIhsIgWQ=
|
||||||
github.com/dfordsoft/golib v0.0.0-20180313113957-2ea3495aee1d/go.mod h1:UGa5M2Sz/Uh13AMse4+RELKCDw7kqgqlTjeGae+7vUY=
|
github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec/go.mod h1:UGa5M2Sz/Uh13AMse4+RELKCDw7kqgqlTjeGae+7vUY=
|
||||||
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a h1:MuHMeSsXbNEeUyxjB7T9P8s1+5k8OLTC/M27qsVwixM=
|
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a h1:MuHMeSsXbNEeUyxjB7T9P8s1+5k8OLTC/M27qsVwixM=
|
||||||
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v0.0.0-20170508165458-6c8dedd55f8a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180428185002-212b1541150c h1:3gMh737vMGqAkkkSfNbwjO8VRHOSaCjYRG4y9xVMEIQ=
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
|
||||||
github.com/go-telegram-bot-api/telegram-bot-api v0.0.0-20180428185002-212b1541150c/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
|
||||||
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc h1:wdhDSKrkYy24mcfzuA3oYm58h0QkyXjwERCkzJDP5kA=
|
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc h1:wdhDSKrkYy24mcfzuA3oYm58h0QkyXjwERCkzJDP5kA=
|
||||||
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v0.0.0-20170613224224-e325f446bebc/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/google/gops v0.0.0-20170319002943-62f833fc9f6c h1:MrMA1vhRTNidtgENqmsmLOIUS6ixMBOU/g10rm7IUe8=
|
github.com/google/gops v0.3.5 h1:SIWvPLiYvy5vMwjxB3rVFTE4QBhUFj2KKWr3Xm7CKhw=
|
||||||
github.com/google/gops v0.0.0-20170319002943-62f833fc9f6c/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
|
github.com/google/gops v0.3.5/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
|
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f h1:FDM3EtwZLyhW48YRiyqjivNlNZjAObv4xt4NnJaU+NQ=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20180628210949-0892b62f0d9f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/schema v0.0.0-20170317173100-f3c80893412c h1:mORYpib1aLu3M2Oi50Z1pNTXuDJEHcoLb6oo6VdOutk=
|
github.com/gorilla/schema v1.0.2 h1:sAgNfOcNYvdDSrzGHVy9nzCQahG+qmsg+nE8dK85QRA=
|
||||||
github.com/gorilla/schema v0.0.0-20170317173100-f3c80893412c/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
github.com/gorilla/schema v1.0.2/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
|
||||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad h1:eMxs9EL0PvIGS9TTtxg4R+JxuPGav82J8rA+GFnY7po=
|
github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo=
|
||||||
github.com/hashicorp/golang-lru v0.0.0-20160813221303-0a025b7e63ad/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v0.0.0-20171017181929-23c074d0eceb h1:1OvvPvZkn/yCQ3xBcM8y4020wdkMXPHLB4+NfoGWh4U=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
github.com/hashicorp/hcl v0.0.0-20171017181929-23c074d0eceb/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/howeyc/gopass v0.0.0-20170109162249-bf9dde6d0d2c/go.mod h1:lADxMC39cJJqL93Duh1xhAs4I2Zs8mKS89XWXFGp9cs=
|
||||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||||
github.com/jpillora/backoff v0.0.0-20170222002228-06c7a16c845d h1:ETeT81zgLgSNc4BWdDO2Fg9ekVItYErbNtE8mKD2pJA=
|
github.com/jessevdk/go-flags v1.3.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/jpillora/backoff v0.0.0-20170222002228-06c7a16c845d/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
|
||||||
|
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
github.com/kardianos/osext v0.0.0-20170207191655-9b883c5eb462 h1:oSOOTPHkCzMeu1vJ0nHxg5+XZBdMMjNa+6NPnm8arok=
|
github.com/kardianos/osext v0.0.0-20170207191655-9b883c5eb462 h1:oSOOTPHkCzMeu1vJ0nHxg5+XZBdMMjNa+6NPnm8arok=
|
||||||
|
@ -47,18 +49,18 @@ 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/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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labstack/echo v0.0.0-20180219162101-7eec915044a1 h1:cOIt0LZKdfeirAfTP4VtIJuWbjVTGtd1suuPXp/J+dE=
|
github.com/labstack/echo v3.3.5+incompatible h1:9PfxPUmasKzeJor9uQTaXLT6WUG/r+vSTmvXxvv3JO4=
|
||||||
github.com/labstack/echo v0.0.0-20180219162101-7eec915044a1/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
github.com/labstack/echo v3.3.5+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
github.com/labstack/gommon v0.2.1 h1:C+I4NYknueQncqKYZQ34kHsLZJVeB5KwPUhnO0nmbpU=
|
github.com/labstack/gommon v0.2.1 h1:C+I4NYknueQncqKYZQ34kHsLZJVeB5KwPUhnO0nmbpU=
|
||||||
github.com/labstack/gommon v0.2.1/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
github.com/labstack/gommon v0.2.1/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||||
github.com/lrstanley/girc v0.0.0-20180913221000-0fb5b684054e h1:RpktB2igr6nS1EN7bCvjldAEfngrM5GyAbmOa4/cafU=
|
github.com/lrstanley/girc v0.0.0-20181114171214-3aee8c249519 h1:o7duXxs4nxplgWrFRJoyGrPAS+U9Sk5eQyc2mflk6/Q=
|
||||||
github.com/lrstanley/girc v0.0.0-20180913221000-0fb5b684054e/go.mod h1:7cRs1SIBfKQ7e3Tam6GKTILSNHzR862JD0JpINaZoJk=
|
github.com/lrstanley/girc v0.0.0-20181114171214-3aee8c249519/go.mod h1:7cRs1SIBfKQ7e3Tam6GKTILSNHzR862JD0JpINaZoJk=
|
||||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU=
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5 h1:AsEBgzv3DhuYHI/GiQh2HxvTP71HCCE9E/tzGUzGdtU=
|
||||||
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
|
github.com/lusis/go-slackbot v0.0.0-20180109053408-401027ccfef5/go.mod h1:c2mYKRyMb1BPkO5St0c/ps62L4S0W2NAkaTXj9qEI+0=
|
||||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns=
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6 h1:iOAVXzZyXtW408TMYejlUPo6BIn92HmOacWtIfNyYns=
|
||||||
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
github.com/lusis/slack-test v0.0.0-20180109053238-3c758769bfa6/go.mod h1:sFlOUpQL1YcjhFVXhg1CG8ZASEs/Mf1oVb6H75JL/zg=
|
||||||
github.com/magiconair/properties v0.0.0-20180217134545-2c9e95027885 h1:HWxJJvF+QceKcql4r9PC93NtMEgEBfBxlQrZPvbcQvs=
|
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||||
github.com/magiconair/properties v0.0.0-20180217134545-2c9e95027885/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
|
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 h1:KzDEcy8eDbTx881giW8a6llsAck3e2bJvMyKvh1IK+k=
|
||||||
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
|
github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91/go.mod h1:ECDRehsR9TYTKCAsRS8/wLeOk6UUqDydw47ln7wG41Q=
|
||||||
github.com/matterbridge/gomatrix v0.0.0-20171224233421-78ac6a1a0f5f h1:2eKh6Qi/sJ8bXvYMoyVfQxHgR8UcCDWjOmhV1oCstMU=
|
github.com/matterbridge/gomatrix v0.0.0-20171224233421-78ac6a1a0f5f h1:2eKh6Qi/sJ8bXvYMoyVfQxHgR8UcCDWjOmhV1oCstMU=
|
||||||
|
@ -67,16 +69,17 @@ github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544 h1:A8lLG3D
|
||||||
github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544/go.mod h1:yAjnZ34DuDyPHMPHHjOsTk/FefW4JJjoMMCGt/8uuQA=
|
github.com/matterbridge/gozulipbot v0.0.0-20180507190239-b6bb12d33544/go.mod h1:yAjnZ34DuDyPHMPHHjOsTk/FefW4JJjoMMCGt/8uuQA=
|
||||||
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61 h1:R/MgM/eUyRBQx2FiH6JVmXck8PaAuKfe2M1tWIzW7nE=
|
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61 h1:R/MgM/eUyRBQx2FiH6JVmXck8PaAuKfe2M1tWIzW7nE=
|
||||||
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU=
|
github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU=
|
||||||
github.com/mattermost/platform v4.6.2+incompatible h1:9WqKNuJFIp6SDYn5wl1RF5urdhEw8d7o5tAOwT1MW0A=
|
github.com/mattermost/mattermost-server v5.5.0+incompatible h1:0wcLGgYtd+YImtLDPf2AOfpBHxbU4suATx+6XKw1XbU=
|
||||||
github.com/mattermost/platform v4.6.2+incompatible/go.mod h1:HjGKtkQNu3HXTOykPMQckMnH11WHvNvQqDBNnVXVbfM=
|
github.com/mattermost/mattermost-server v5.5.0+incompatible/go.mod h1:5L6MjAec+XXQwMIt791Ganu45GKsSiM+I0tLR9wUj8Y=
|
||||||
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597 h1:hGizH4aMDFFt1iOA4HNKC13lqIBoCyxIjWcAnWIy7aU=
|
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597 h1:hGizH4aMDFFt1iOA4HNKC13lqIBoCyxIjWcAnWIy7aU=
|
||||||
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.0-20170210172801-5411d3eea597/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc h1:pK7tzC30erKOTfEDCYGvPZQCkmM9X5iSmmAR5m9x3Yc=
|
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc h1:pK7tzC30erKOTfEDCYGvPZQCkmM9X5iSmmAR5m9x3Yc=
|
||||||
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.0-20170216235908-dda3de49cbfc/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
|
||||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 h1:+MZW2uvHgN8kYvksEN3f7eFL2wpzk0GxmlFsMybWc7E=
|
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 h1:oKIteTqeSpenyTrOVj5zkiyCaflLa8B+CD0324otT+o=
|
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474 h1:oKIteTqeSpenyTrOVj5zkiyCaflLa8B+CD0324otT+o=
|
||||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||||
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
|
github.com/mrexodia/wray v0.0.0-20160318003008-78a2c1f284ff h1:HLGD5/9UxxfEuO9DtP8gnTmNtMxbPyhYltfxsITel8g=
|
||||||
|
@ -93,24 +96,24 @@ github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83 h1:XQonH5Iv
|
||||||
github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83/go.mod h1:YnNlZP7l4MhyGQ4CBRwv6ohZTPrUJJZtEv4ZgADkbs4=
|
github.com/paulrosania/go-charset v0.0.0-20151028000031-621bb39fcc83/go.mod h1:YnNlZP7l4MhyGQ4CBRwv6ohZTPrUJJZtEv4ZgADkbs4=
|
||||||
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 h1:/CPgDYrfeK2LMK6xcUhvI17yO9SlpAdDIJGkhDEgO8A=
|
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606 h1:/CPgDYrfeK2LMK6xcUhvI17yO9SlpAdDIJGkhDEgO8A=
|
||||||
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
|
github.com/pborman/uuid v0.0.0-20160216163710-c55201b03606/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34=
|
||||||
github.com/pelletier/go-toml v0.0.0-20180228233631-05bcc0fb0d3e h1:ZW8599OjioQsmBbkGpyruHUlRVQceYFWnJsGr4NCkiA=
|
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||||
github.com/pelletier/go-toml v0.0.0-20180228233631-05bcc0fb0d3e/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/peterhellberg/emojilib v0.0.0-20170616163716-41920917e271 h1:wQ9lVx75za6AT2kI0S9QID0uWuwTWnvcTfN+uw1F8vg=
|
github.com/peterhellberg/emojilib v0.0.0-20180820090156-eeb3823dab9a h1:zAss6STq7oejKWTMEUYDUKYZhqXe0xALo8pJhJ3JJAs=
|
||||||
github.com/peterhellberg/emojilib v0.0.0-20170616163716-41920917e271/go.mod h1:G7LufuPajuIvdt9OitkNt2qh0mmvD4bfRgRM7bhDIOA=
|
github.com/peterhellberg/emojilib v0.0.0-20180820090156-eeb3823dab9a/go.mod h1:G7LufuPajuIvdt9OitkNt2qh0mmvD4bfRgRM7bhDIOA=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a h1:UWKek6MK3K6/TpbsFcv+8rrO6rSc6KKSp2FbMOHWsq4=
|
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
|
||||||
github.com/rs/xid v0.0.0-20180525034800-088c5cf1423a/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
github.com/russross/blackfriday v2.0.0+incompatible h1:cBXrhZNUf9C+La9/YpS+UHpUT8YD6Td9ZMSU9APFcsk=
|
||||||
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v2.0.0+incompatible/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||||
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 h1:Lx3BlDGFElJt4u/zKc9A3BuGYbQAGlEFyPuUA3jeMD0=
|
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1 h1:Lx3BlDGFElJt4u/zKc9A3BuGYbQAGlEFyPuUA3jeMD0=
|
||||||
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
|
github.com/shazow/rateio v0.0.0-20150116013248-e8e00881e5c1/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
|
||||||
github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991 h1:PQiUTDzUC5EUh0vNurK7KQS22zlKqLLOFn+K9nJXDQQ=
|
github.com/shazow/ssh-chat v0.0.0-20181028152505-f36d7eb9ccc6 h1:qNoZx1RWPGKiqfs8ZZAYsYtw3ejo3HIF7iECaeaJhFk=
|
||||||
github.com/shazow/ssh-chat v0.0.0-20171012174035-2078e1381991/go.mod h1:KwtnpMClmrXsHCKTbRui5xBUNt17n1GGrGhdiw2KcoY=
|
github.com/shazow/ssh-chat v0.0.0-20181028152505-f36d7eb9ccc6/go.mod h1:SA/9+Wy3zV0UvPjttpGgs90FS9ZZ5D/LTffnVqdIBE8=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY=
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
|
||||||
|
@ -119,16 +122,19 @@ github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9 h1:lXQ+j+
|
||||||
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180803164922-886ec427f6b9/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||||
github.com/spf13/afero v0.0.0-20180211162714-bbf41cb36dff h1:HLvGWId7M56TfuxTeZ6aoiTAcrWO5Mnq/ArwVRgV62I=
|
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||||
github.com/spf13/afero v0.0.0-20180211162714-bbf41cb36dff/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig=
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/pflag v0.0.0-20180220143236-ee5fd03fd6ac h1:+uzyQ0TQ3aKorQxsOjcDDgE7CuUXwpkKnK19LULQALQ=
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
github.com/spf13/pflag v0.0.0-20180220143236-ee5fd03fd6ac/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/viper v0.0.0-20171227194143-aafc9e6bc7b7 h1:Wj4cg2M6Um7j1N7yD/mxsdy1/wrsdjzVha2eWdOhti8=
|
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/viper v0.0.0-20171227194143-aafc9e6bc7b7/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||||
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M=
|
||||||
|
github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
||||||
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
|
@ -143,6 +149,13 @@ github.com/x-cray/logrus-prefixed-formatter v0.5.2 h1:00txxvfBM9muc0jiLIEAkAcIMJ
|
||||||
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
github.com/x-cray/logrus-prefixed-formatter v0.5.2/go.mod h1:2duySbKsL6M18s5GU7VPsoEPHyzalCE06qoARUCeBBE=
|
||||||
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6 h1:/WULP+6asFz569UbOwg87f3iDT7T+GF5/vjLmL51Pdk=
|
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6 h1:/WULP+6asFz569UbOwg87f3iDT7T+GF5/vjLmL51Pdk=
|
||||||
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6/go.mod h1:0MsIttMJIF/8Y7x0XjonJP7K99t3sR6bjj4m5S4JmqU=
|
github.com/zfjagann/golang-ring v0.0.0-20141111230621-17637388c9f6/go.mod h1:0MsIttMJIF/8Y7x0XjonJP7K99t3sR6bjj4m5S4JmqU=
|
||||||
|
go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4=
|
||||||
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
|
go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI=
|
||||||
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
|
go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o=
|
||||||
|
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
|
golang.org/x/crypto v0.0.0-20180119074636-ee41a25c63fb/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
|
||||||
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
@ -152,16 +165,21 @@ golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37 h1:BkNcmLtAVeWe9h5k0jt24CQga
|
||||||
golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180108090419-434ec0c7fe37/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116161606-93218def8b18 h1:Wh+XCfg3kNpjhdq2LXrsiOProjtQZKme5XUx7VcxwAw=
|
golang.org/x/sys v0.0.0-20181116161606-93218def8b18 h1:Wh+XCfg3kNpjhdq2LXrsiOProjtQZKme5XUx7VcxwAw=
|
||||||
golang.org/x/sys v0.0.0-20181116161606-93218def8b18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116161606-93218def8b18/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/text v0.0.0-20180511172408-5c1cf69b5978 h1:WNm0tmiuBMW4FJRuXKWOqaQfmKptHs0n8nTCyG0ayjc=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
golang.org/x/text v0.0.0-20180511172408-5c1cf69b5978/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20160301204022-a83829b6f129 h1:RBgb9aPUbZ9nu66ecQNIBNsA7j3mB5h8PNDIfhPjaJg=
|
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||||
gopkg.in/yaml.v2 v2.0.0-20160301204022-a83829b6f129/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
@ -27,7 +27,7 @@ func main() {
|
||||||
flagGops := flag.Bool("gops", false, "enable gops agent")
|
flagGops := flag.Bool("gops", false, "enable gops agent")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
if *flagGops {
|
if *flagGops {
|
||||||
agent.Listen(&agent.Options{})
|
agent.Listen(agent.Options{})
|
||||||
defer agent.Close()
|
defer agent.Close()
|
||||||
}
|
}
|
||||||
if *flagVersion {
|
if *flagVersion {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
"github.com/hashicorp/golang-lru"
|
"github.com/hashicorp/golang-lru"
|
||||||
"github.com/jpillora/backoff"
|
"github.com/jpillora/backoff"
|
||||||
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
prefixed "github.com/matterbridge/logrus-prefixed-formatter"
|
||||||
"github.com/mattermost/platform/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,14 @@ package steam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
. "github.com/Philipp15b/go-steam/protocol"
|
. "github.com/Philipp15b/go-steam/protocol"
|
||||||
. "github.com/Philipp15b/go-steam/protocol/protobuf"
|
. "github.com/Philipp15b/go-steam/protocol/protobuf"
|
||||||
. "github.com/Philipp15b/go-steam/protocol/steamlang"
|
. "github.com/Philipp15b/go-steam/protocol/steamlang"
|
||||||
. "github.com/Philipp15b/go-steam/steamid"
|
. "github.com/Philipp15b/go-steam/steamid"
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
|
@ -19,23 +20,41 @@ type Auth struct {
|
||||||
type SentryHash []byte
|
type SentryHash []byte
|
||||||
|
|
||||||
type LogOnDetails struct {
|
type LogOnDetails struct {
|
||||||
Username string
|
Username string
|
||||||
Password string
|
|
||||||
AuthCode string
|
// If logging into an account without a login key, the account's password.
|
||||||
|
Password string
|
||||||
|
|
||||||
|
// If you have a Steam Guard email code, you can provide it here.
|
||||||
|
AuthCode string
|
||||||
|
|
||||||
|
// If you have a Steam Guard mobile two-factor authentication code, you can provide it here.
|
||||||
TwoFactorCode string
|
TwoFactorCode string
|
||||||
SentryFileHash SentryHash
|
SentryFileHash SentryHash
|
||||||
|
LoginKey string
|
||||||
|
|
||||||
|
// true if you want to get a login key which can be used in lieu of
|
||||||
|
// a password for subsequent logins. false or omitted otherwise.
|
||||||
|
ShouldRememberPassword bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log on with the given details. You must always specify username and
|
// Log on with the given details. You must always specify username and
|
||||||
// password. For the first login, don't set an authcode or a hash and you'll receive an error
|
// password OR username and loginkey. For the first login, don't set an authcode or a hash and you'll
|
||||||
|
// receive an error (EResult_AccountLogonDenied)
|
||||||
// and Steam will send you an authcode. Then you have to login again, this time with the authcode.
|
// and Steam will send you an authcode. Then you have to login again, this time with the authcode.
|
||||||
// Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows
|
// Shortly after logging in, you'll receive a MachineAuthUpdateEvent with a hash which allows
|
||||||
// you to login without using an authcode in the future.
|
// you to login without using an authcode in the future.
|
||||||
//
|
//
|
||||||
// If you don't use Steam Guard, username and password are enough.
|
// If you don't use Steam Guard, username and password are enough.
|
||||||
|
//
|
||||||
|
// After the event EMsg_ClientNewLoginKey is received you can use the LoginKey
|
||||||
|
// to login instead of using the password.
|
||||||
func (a *Auth) LogOn(details *LogOnDetails) {
|
func (a *Auth) LogOn(details *LogOnDetails) {
|
||||||
if len(details.Username) == 0 || len(details.Password) == 0 {
|
if details.Username == "" {
|
||||||
panic("Username and password must be set!")
|
panic("Username must be set!")
|
||||||
|
}
|
||||||
|
if details.Password == "" && details.LoginKey == "" {
|
||||||
|
panic("Password or LoginKey must be set!")
|
||||||
}
|
}
|
||||||
|
|
||||||
logon := new(CMsgClientLogon)
|
logon := new(CMsgClientLogon)
|
||||||
|
@ -50,6 +69,12 @@ func (a *Auth) LogOn(details *LogOnDetails) {
|
||||||
logon.ClientLanguage = proto.String("english")
|
logon.ClientLanguage = proto.String("english")
|
||||||
logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol)
|
logon.ProtocolVersion = proto.Uint32(MsgClientLogon_CurrentProtocol)
|
||||||
logon.ShaSentryfile = details.SentryFileHash
|
logon.ShaSentryfile = details.SentryFileHash
|
||||||
|
if details.LoginKey != "" {
|
||||||
|
logon.LoginKey = proto.String(details.LoginKey)
|
||||||
|
}
|
||||||
|
if details.ShouldRememberPassword {
|
||||||
|
logon.ShouldRememberPassword = proto.Bool(details.ShouldRememberPassword)
|
||||||
|
}
|
||||||
|
|
||||||
atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual))))
|
atomic.StoreUint64(&a.client.steamId, uint64(NewIdAdv(0, 1, int32(EUniverse_Public), int32(EAccountType_Individual))))
|
||||||
|
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
*.sw[op]
|
|
||||||
.DS_Store
|
|
|
@ -1,13 +0,0 @@
|
||||||
Copyright (c) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
||||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
@ -1,14 +0,0 @@
|
||||||
# This is an unmaintained fork, left only so it doesn't break imports.
|
|
||||||
|
|
||||||
Please see http://log4go.googlecode.com/
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
- Run `goinstall log4go.googlecode.com/hg`
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
- Add the following import:
|
|
||||||
import l4g "log4go.googlecode.com/hg"
|
|
||||||
|
|
||||||
Acknowledgements:
|
|
||||||
- pomack
|
|
||||||
For providing awesome patches to bring log4go up to the latest Go spec
|
|
|
@ -1,288 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type xmlProperty struct {
|
|
||||||
Name string `xml:"name,attr"`
|
|
||||||
Value string `xml:",chardata"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type xmlFilter struct {
|
|
||||||
Enabled string `xml:"enabled,attr"`
|
|
||||||
Tag string `xml:"tag"`
|
|
||||||
Level string `xml:"level"`
|
|
||||||
Type string `xml:"type"`
|
|
||||||
Property []xmlProperty `xml:"property"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type xmlLoggerConfig struct {
|
|
||||||
Filter []xmlFilter `xml:"filter"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load XML configuration; see examples/example.xml for documentation
|
|
||||||
func (log Logger) LoadConfiguration(filename string) {
|
|
||||||
log.Close()
|
|
||||||
|
|
||||||
// Open the configuration file
|
|
||||||
fd, err := os.Open(filename)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not open %q for reading: %s\n", filename, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
contents, err := ioutil.ReadAll(fd)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not read %q: %s\n", filename, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
xc := new(xmlLoggerConfig)
|
|
||||||
if err := xml.Unmarshal(contents, xc); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not parse XML configuration in %q: %s\n", filename, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, xmlfilt := range xc.Filter {
|
|
||||||
var filt LogWriter
|
|
||||||
var lvl Level
|
|
||||||
bad, good, enabled := false, true, false
|
|
||||||
|
|
||||||
// Check required children
|
|
||||||
if len(xmlfilt.Enabled) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required attribute %s for filter missing in %s\n", "enabled", filename)
|
|
||||||
bad = true
|
|
||||||
} else {
|
|
||||||
enabled = xmlfilt.Enabled != "false"
|
|
||||||
}
|
|
||||||
if len(xmlfilt.Tag) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "tag", filename)
|
|
||||||
bad = true
|
|
||||||
}
|
|
||||||
if len(xmlfilt.Type) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "type", filename)
|
|
||||||
bad = true
|
|
||||||
}
|
|
||||||
if len(xmlfilt.Level) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter missing in %s\n", "level", filename)
|
|
||||||
bad = true
|
|
||||||
}
|
|
||||||
|
|
||||||
switch xmlfilt.Level {
|
|
||||||
case "FINEST":
|
|
||||||
lvl = FINEST
|
|
||||||
case "FINE":
|
|
||||||
lvl = FINE
|
|
||||||
case "DEBUG":
|
|
||||||
lvl = DEBUG
|
|
||||||
case "TRACE":
|
|
||||||
lvl = TRACE
|
|
||||||
case "INFO":
|
|
||||||
lvl = INFO
|
|
||||||
case "WARNING":
|
|
||||||
lvl = WARNING
|
|
||||||
case "ERROR":
|
|
||||||
lvl = ERROR
|
|
||||||
case "CRITICAL":
|
|
||||||
lvl = CRITICAL
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required child <%s> for filter has unknown value in %s: %s\n", "level", filename, xmlfilt.Level)
|
|
||||||
bad = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just so all of the required attributes are errored at the same time if missing
|
|
||||||
if bad {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch xmlfilt.Type {
|
|
||||||
case "console":
|
|
||||||
filt, good = xmlToConsoleLogWriter(filename, xmlfilt.Property, enabled)
|
|
||||||
case "file":
|
|
||||||
filt, good = xmlToFileLogWriter(filename, xmlfilt.Property, enabled)
|
|
||||||
case "xml":
|
|
||||||
filt, good = xmlToXMLLogWriter(filename, xmlfilt.Property, enabled)
|
|
||||||
case "socket":
|
|
||||||
filt, good = xmlToSocketLogWriter(filename, xmlfilt.Property, enabled)
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Could not load XML configuration in %s: unknown filter type \"%s\"\n", filename, xmlfilt.Type)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Just so all of the required params are errored at the same time if wrong
|
|
||||||
if !good {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're disabled (syntax and correctness checks only), don't add to logger
|
|
||||||
if !enabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log[xmlfilt.Tag] = &Filter{lvl, filt}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func xmlToConsoleLogWriter(filename string, props []xmlProperty, enabled bool) (*ConsoleLogWriter, bool) {
|
|
||||||
// Parse properties
|
|
||||||
for _, prop := range props {
|
|
||||||
switch prop.Name {
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for console filter in %s\n", prop.Name, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's disabled, we're just checking syntax
|
|
||||||
if !enabled {
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewConsoleLogWriter(), true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse a number with K/M/G suffixes based on thousands (1000) or 2^10 (1024)
|
|
||||||
func strToNumSuffix(str string, mult int) int {
|
|
||||||
num := 1
|
|
||||||
if len(str) > 1 {
|
|
||||||
switch str[len(str)-1] {
|
|
||||||
case 'G', 'g':
|
|
||||||
num *= mult
|
|
||||||
fallthrough
|
|
||||||
case 'M', 'm':
|
|
||||||
num *= mult
|
|
||||||
fallthrough
|
|
||||||
case 'K', 'k':
|
|
||||||
num *= mult
|
|
||||||
str = str[0 : len(str)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parsed, _ := strconv.Atoi(str)
|
|
||||||
return parsed * num
|
|
||||||
}
|
|
||||||
func xmlToFileLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
|
|
||||||
file := ""
|
|
||||||
format := "[%D %T] [%L] (%S) %M"
|
|
||||||
maxlines := 0
|
|
||||||
maxsize := 0
|
|
||||||
daily := false
|
|
||||||
rotate := false
|
|
||||||
|
|
||||||
// Parse properties
|
|
||||||
for _, prop := range props {
|
|
||||||
switch prop.Name {
|
|
||||||
case "filename":
|
|
||||||
file = strings.Trim(prop.Value, " \r\n")
|
|
||||||
case "format":
|
|
||||||
format = strings.Trim(prop.Value, " \r\n")
|
|
||||||
case "maxlines":
|
|
||||||
maxlines = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
|
|
||||||
case "maxsize":
|
|
||||||
maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
|
|
||||||
case "daily":
|
|
||||||
daily = strings.Trim(prop.Value, " \r\n") != "false"
|
|
||||||
case "rotate":
|
|
||||||
rotate = strings.Trim(prop.Value, " \r\n") != "false"
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check properties
|
|
||||||
if len(file) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "filename", filename)
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's disabled, we're just checking syntax
|
|
||||||
if !enabled {
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
flw := NewFileLogWriter(file, rotate)
|
|
||||||
flw.SetFormat(format)
|
|
||||||
flw.SetRotateLines(maxlines)
|
|
||||||
flw.SetRotateSize(maxsize)
|
|
||||||
flw.SetRotateDaily(daily)
|
|
||||||
return flw, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func xmlToXMLLogWriter(filename string, props []xmlProperty, enabled bool) (*FileLogWriter, bool) {
|
|
||||||
file := ""
|
|
||||||
maxrecords := 0
|
|
||||||
maxsize := 0
|
|
||||||
daily := false
|
|
||||||
rotate := false
|
|
||||||
|
|
||||||
// Parse properties
|
|
||||||
for _, prop := range props {
|
|
||||||
switch prop.Name {
|
|
||||||
case "filename":
|
|
||||||
file = strings.Trim(prop.Value, " \r\n")
|
|
||||||
case "maxrecords":
|
|
||||||
maxrecords = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1000)
|
|
||||||
case "maxsize":
|
|
||||||
maxsize = strToNumSuffix(strings.Trim(prop.Value, " \r\n"), 1024)
|
|
||||||
case "daily":
|
|
||||||
daily = strings.Trim(prop.Value, " \r\n") != "false"
|
|
||||||
case "rotate":
|
|
||||||
rotate = strings.Trim(prop.Value, " \r\n") != "false"
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for xml filter in %s\n", prop.Name, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check properties
|
|
||||||
if len(file) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for xml filter missing in %s\n", "filename", filename)
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's disabled, we're just checking syntax
|
|
||||||
if !enabled {
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
xlw := NewXMLLogWriter(file, rotate)
|
|
||||||
xlw.SetRotateLines(maxrecords)
|
|
||||||
xlw.SetRotateSize(maxsize)
|
|
||||||
xlw.SetRotateDaily(daily)
|
|
||||||
return xlw, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func xmlToSocketLogWriter(filename string, props []xmlProperty, enabled bool) (SocketLogWriter, bool) {
|
|
||||||
endpoint := ""
|
|
||||||
protocol := "udp"
|
|
||||||
|
|
||||||
// Parse properties
|
|
||||||
for _, prop := range props {
|
|
||||||
switch prop.Name {
|
|
||||||
case "endpoint":
|
|
||||||
endpoint = strings.Trim(prop.Value, " \r\n")
|
|
||||||
case "protocol":
|
|
||||||
protocol = strings.Trim(prop.Value, " \r\n")
|
|
||||||
default:
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Warning: Unknown property \"%s\" for file filter in %s\n", prop.Name, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check properties
|
|
||||||
if len(endpoint) == 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "LoadConfiguration: Error: Required property \"%s\" for file filter missing in %s\n", "endpoint", filename)
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's disabled, we're just checking syntax
|
|
||||||
if !enabled {
|
|
||||||
return nil, true
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewSocketLogWriter(protocol, endpoint), true
|
|
||||||
}
|
|
|
@ -1,264 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This log writer sends output to a file
|
|
||||||
type FileLogWriter struct {
|
|
||||||
rec chan *LogRecord
|
|
||||||
rot chan bool
|
|
||||||
|
|
||||||
// The opened file
|
|
||||||
filename string
|
|
||||||
file *os.File
|
|
||||||
|
|
||||||
// The logging format
|
|
||||||
format string
|
|
||||||
|
|
||||||
// File header/trailer
|
|
||||||
header, trailer string
|
|
||||||
|
|
||||||
// Rotate at linecount
|
|
||||||
maxlines int
|
|
||||||
maxlines_curlines int
|
|
||||||
|
|
||||||
// Rotate at size
|
|
||||||
maxsize int
|
|
||||||
maxsize_cursize int
|
|
||||||
|
|
||||||
// Rotate daily
|
|
||||||
daily bool
|
|
||||||
daily_opendate int
|
|
||||||
|
|
||||||
// Keep old logfiles (.001, .002, etc)
|
|
||||||
rotate bool
|
|
||||||
maxbackup int
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the FileLogWriter's output method
|
|
||||||
func (w *FileLogWriter) LogWrite(rec *LogRecord) {
|
|
||||||
w.rec <- rec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *FileLogWriter) Close() {
|
|
||||||
close(w.rec)
|
|
||||||
w.file.Sync()
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFileLogWriter creates a new LogWriter which writes to the given file and
|
|
||||||
// has rotation enabled if rotate is true.
|
|
||||||
//
|
|
||||||
// If rotate is true, any time a new log file is opened, the old one is renamed
|
|
||||||
// with a .### extension to preserve it. The various Set* methods can be used
|
|
||||||
// to configure log rotation based on lines, size, and daily.
|
|
||||||
//
|
|
||||||
// The standard log-line format is:
|
|
||||||
// [%D %T] [%L] (%S) %M
|
|
||||||
func NewFileLogWriter(fname string, rotate bool) *FileLogWriter {
|
|
||||||
w := &FileLogWriter{
|
|
||||||
rec: make(chan *LogRecord, LogBufferLength),
|
|
||||||
rot: make(chan bool),
|
|
||||||
filename: fname,
|
|
||||||
format: "[%D %T] [%L] (%S) %M",
|
|
||||||
rotate: rotate,
|
|
||||||
maxbackup: 999,
|
|
||||||
}
|
|
||||||
|
|
||||||
// open the file for the first time
|
|
||||||
if err := w.intRotate(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if w.file != nil {
|
|
||||||
fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
|
|
||||||
w.file.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-w.rot:
|
|
||||||
if err := w.intRotate(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case rec, ok := <-w.rec:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
if (w.maxlines > 0 && w.maxlines_curlines >= w.maxlines) ||
|
|
||||||
(w.maxsize > 0 && w.maxsize_cursize >= w.maxsize) ||
|
|
||||||
(w.daily && now.Day() != w.daily_opendate) {
|
|
||||||
if err := w.intRotate(); err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform the write
|
|
||||||
n, err := fmt.Fprint(w.file, FormatLogRecord(w.format, rec))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "FileLogWriter(%q): %s\n", w.filename, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the counts
|
|
||||||
w.maxlines_curlines++
|
|
||||||
w.maxsize_cursize += n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Request that the logs rotate
|
|
||||||
func (w *FileLogWriter) Rotate() {
|
|
||||||
w.rot <- true
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is called in a threaded context, it MUST be synchronized
|
|
||||||
func (w *FileLogWriter) intRotate() error {
|
|
||||||
// Close any log file that may be open
|
|
||||||
if w.file != nil {
|
|
||||||
fmt.Fprint(w.file, FormatLogRecord(w.trailer, &LogRecord{Created: time.Now()}))
|
|
||||||
w.file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we are keeping log files, move it to the next available number
|
|
||||||
if w.rotate {
|
|
||||||
_, err := os.Lstat(w.filename)
|
|
||||||
if err == nil { // file exists
|
|
||||||
// Find the next available number
|
|
||||||
num := 1
|
|
||||||
fname := ""
|
|
||||||
if w.daily && time.Now().Day() != w.daily_opendate {
|
|
||||||
yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
|
||||||
|
|
||||||
for ; err == nil && num <= 999; num++ {
|
|
||||||
fname = w.filename + fmt.Sprintf(".%s.%03d", yesterday, num)
|
|
||||||
_, err = os.Lstat(fname)
|
|
||||||
}
|
|
||||||
// return error if the last file checked still existed
|
|
||||||
if err == nil {
|
|
||||||
return fmt.Errorf("Rotate: Cannot find free log number to rename %s\n", w.filename)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
num = w.maxbackup - 1
|
|
||||||
for ; num >= 1; num-- {
|
|
||||||
fname = w.filename + fmt.Sprintf(".%d", num)
|
|
||||||
nfname := w.filename + fmt.Sprintf(".%d", num+1)
|
|
||||||
_, err = os.Lstat(fname)
|
|
||||||
if err == nil {
|
|
||||||
os.Rename(fname, nfname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.file.Close()
|
|
||||||
// Rename the file to its newfound home
|
|
||||||
err = os.Rename(w.filename, fname)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("Rotate: %s\n", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the log file
|
|
||||||
fd, err := os.OpenFile(w.filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0660)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.file = fd
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: now}))
|
|
||||||
|
|
||||||
// Set the daily open date to the current date
|
|
||||||
w.daily_opendate = now.Day()
|
|
||||||
|
|
||||||
// initialize rotation values
|
|
||||||
w.maxlines_curlines = 0
|
|
||||||
w.maxsize_cursize = 0
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the logging format (chainable). Must be called before the first log
|
|
||||||
// message is written.
|
|
||||||
func (w *FileLogWriter) SetFormat(format string) *FileLogWriter {
|
|
||||||
w.format = format
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the logfile header and footer (chainable). Must be called before the first log
|
|
||||||
// message is written. These are formatted similar to the FormatLogRecord (e.g.
|
|
||||||
// you can use %D and %T in your header/footer for date and time).
|
|
||||||
func (w *FileLogWriter) SetHeadFoot(head, foot string) *FileLogWriter {
|
|
||||||
w.header, w.trailer = head, foot
|
|
||||||
if w.maxlines_curlines == 0 {
|
|
||||||
fmt.Fprint(w.file, FormatLogRecord(w.header, &LogRecord{Created: time.Now()}))
|
|
||||||
}
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set rotate at linecount (chainable). Must be called before the first log
|
|
||||||
// message is written.
|
|
||||||
func (w *FileLogWriter) SetRotateLines(maxlines int) *FileLogWriter {
|
|
||||||
//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateLines: %v\n", maxlines)
|
|
||||||
w.maxlines = maxlines
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set rotate at size (chainable). Must be called before the first log message
|
|
||||||
// is written.
|
|
||||||
func (w *FileLogWriter) SetRotateSize(maxsize int) *FileLogWriter {
|
|
||||||
//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateSize: %v\n", maxsize)
|
|
||||||
w.maxsize = maxsize
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set rotate daily (chainable). Must be called before the first log message is
|
|
||||||
// written.
|
|
||||||
func (w *FileLogWriter) SetRotateDaily(daily bool) *FileLogWriter {
|
|
||||||
//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotateDaily: %v\n", daily)
|
|
||||||
w.daily = daily
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set max backup files. Must be called before the first log message
|
|
||||||
// is written.
|
|
||||||
func (w *FileLogWriter) SetRotateMaxBackup(maxbackup int) *FileLogWriter {
|
|
||||||
w.maxbackup = maxbackup
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRotate changes whether or not the old logs are kept. (chainable) Must be
|
|
||||||
// called before the first log message is written. If rotate is false, the
|
|
||||||
// files are overwritten; otherwise, they are rotated to another file before the
|
|
||||||
// new log is opened.
|
|
||||||
func (w *FileLogWriter) SetRotate(rotate bool) *FileLogWriter {
|
|
||||||
//fmt.Fprintf(os.Stderr, "FileLogWriter.SetRotate: %v\n", rotate)
|
|
||||||
w.rotate = rotate
|
|
||||||
return w
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewXMLLogWriter is a utility method for creating a FileLogWriter set up to
|
|
||||||
// output XML record log messages instead of line-based ones.
|
|
||||||
func NewXMLLogWriter(fname string, rotate bool) *FileLogWriter {
|
|
||||||
return NewFileLogWriter(fname, rotate).SetFormat(
|
|
||||||
` <record level="%L">
|
|
||||||
<timestamp>%D %T</timestamp>
|
|
||||||
<source>%S</source>
|
|
||||||
<message>%M</message>
|
|
||||||
</record>`).SetHeadFoot("<log created=\"%D %T\">", "</log>")
|
|
||||||
}
|
|
|
@ -1,484 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
// Package log4go provides level-based and highly configurable logging.
|
|
||||||
//
|
|
||||||
// Enhanced Logging
|
|
||||||
//
|
|
||||||
// This is inspired by the logging functionality in Java. Essentially, you create a Logger
|
|
||||||
// object and create output filters for it. You can send whatever you want to the Logger,
|
|
||||||
// and it will filter that based on your settings and send it to the outputs. This way, you
|
|
||||||
// can put as much debug code in your program as you want, and when you're done you can filter
|
|
||||||
// out the mundane messages so only the important ones show up.
|
|
||||||
//
|
|
||||||
// Utility functions are provided to make life easier. Here is some example code to get started:
|
|
||||||
//
|
|
||||||
// log := log4go.NewLogger()
|
|
||||||
// log.AddFilter("stdout", log4go.DEBUG, log4go.NewConsoleLogWriter())
|
|
||||||
// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
|
|
||||||
// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
|
|
||||||
//
|
|
||||||
// The first two lines can be combined with the utility NewDefaultLogger:
|
|
||||||
//
|
|
||||||
// log := log4go.NewDefaultLogger(log4go.DEBUG)
|
|
||||||
// log.AddFilter("log", log4go.FINE, log4go.NewFileLogWriter("example.log", true))
|
|
||||||
// log.Info("The time is now: %s", time.LocalTime().Format("15:04:05 MST 2006/01/02"))
|
|
||||||
//
|
|
||||||
// Usage notes:
|
|
||||||
// - The ConsoleLogWriter does not display the source of the message to standard
|
|
||||||
// output, but the FileLogWriter does.
|
|
||||||
// - The utility functions (Info, Debug, Warn, etc) derive their source from the
|
|
||||||
// calling function, and this incurs extra overhead.
|
|
||||||
//
|
|
||||||
// Changes from 2.0:
|
|
||||||
// - The external interface has remained mostly stable, but a lot of the
|
|
||||||
// internals have been changed, so if you depended on any of this or created
|
|
||||||
// your own LogWriter, then you will probably have to update your code. In
|
|
||||||
// particular, Logger is now a map and ConsoleLogWriter is now a channel
|
|
||||||
// behind-the-scenes, and the LogWrite method no longer has return values.
|
|
||||||
//
|
|
||||||
// Future work: (please let me know if you think I should work on any of these particularly)
|
|
||||||
// - Log file rotation
|
|
||||||
// - Logging configuration files ala log4j
|
|
||||||
// - Have the ability to remove filters?
|
|
||||||
// - Have GetInfoChannel, GetDebugChannel, etc return a chan string that allows
|
|
||||||
// for another method of logging
|
|
||||||
// - Add an XML filter type
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Version information
|
|
||||||
const (
|
|
||||||
L4G_VERSION = "log4go-v3.0.1"
|
|
||||||
L4G_MAJOR = 3
|
|
||||||
L4G_MINOR = 0
|
|
||||||
L4G_BUILD = 1
|
|
||||||
)
|
|
||||||
|
|
||||||
/****** Constants ******/
|
|
||||||
|
|
||||||
// These are the integer logging levels used by the logger
|
|
||||||
type Level int
|
|
||||||
|
|
||||||
const (
|
|
||||||
FINEST Level = iota
|
|
||||||
FINE
|
|
||||||
DEBUG
|
|
||||||
TRACE
|
|
||||||
INFO
|
|
||||||
WARNING
|
|
||||||
ERROR
|
|
||||||
CRITICAL
|
|
||||||
)
|
|
||||||
|
|
||||||
// Logging level strings
|
|
||||||
var (
|
|
||||||
levelStrings = [...]string{"FNST", "FINE", "DEBG", "TRAC", "INFO", "WARN", "EROR", "CRIT"}
|
|
||||||
)
|
|
||||||
|
|
||||||
func (l Level) String() string {
|
|
||||||
if l < 0 || int(l) > len(levelStrings) {
|
|
||||||
return "UNKNOWN"
|
|
||||||
}
|
|
||||||
return levelStrings[int(l)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/****** Variables ******/
|
|
||||||
var (
|
|
||||||
// LogBufferLength specifies how many log messages a particular log4go
|
|
||||||
// logger can buffer at a time before writing them.
|
|
||||||
LogBufferLength = 32
|
|
||||||
)
|
|
||||||
|
|
||||||
/****** LogRecord ******/
|
|
||||||
|
|
||||||
// A LogRecord contains all of the pertinent information for each message
|
|
||||||
type LogRecord struct {
|
|
||||||
Level Level // The log level
|
|
||||||
Created time.Time // The time at which the log message was created (nanoseconds)
|
|
||||||
Source string // The message source
|
|
||||||
Message string // The log message
|
|
||||||
}
|
|
||||||
|
|
||||||
/****** LogWriter ******/
|
|
||||||
|
|
||||||
// This is an interface for anything that should be able to write logs
|
|
||||||
type LogWriter interface {
|
|
||||||
// This will be called to log a LogRecord message.
|
|
||||||
LogWrite(rec *LogRecord)
|
|
||||||
|
|
||||||
// This should clean up anything lingering about the LogWriter, as it is called before
|
|
||||||
// the LogWriter is removed. LogWrite should not be called after Close.
|
|
||||||
Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
/****** Logger ******/
|
|
||||||
|
|
||||||
// A Filter represents the log level below which no log records are written to
|
|
||||||
// the associated LogWriter.
|
|
||||||
type Filter struct {
|
|
||||||
Level Level
|
|
||||||
LogWriter
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Logger represents a collection of Filters through which log messages are
|
|
||||||
// written.
|
|
||||||
type Logger map[string]*Filter
|
|
||||||
|
|
||||||
// Create a new logger.
|
|
||||||
//
|
|
||||||
// DEPRECATED: Use make(Logger) instead.
|
|
||||||
func NewLogger() Logger {
|
|
||||||
os.Stderr.WriteString("warning: use of deprecated NewLogger\n")
|
|
||||||
return make(Logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new logger with a "stdout" filter configured to send log messages at
|
|
||||||
// or above lvl to standard output.
|
|
||||||
//
|
|
||||||
// DEPRECATED: use NewDefaultLogger instead.
|
|
||||||
func NewConsoleLogger(lvl Level) Logger {
|
|
||||||
os.Stderr.WriteString("warning: use of deprecated NewConsoleLogger\n")
|
|
||||||
return Logger{
|
|
||||||
"stdout": &Filter{lvl, NewConsoleLogWriter()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new logger with a "stdout" filter configured to send log messages at
|
|
||||||
// or above lvl to standard output.
|
|
||||||
func NewDefaultLogger(lvl Level) Logger {
|
|
||||||
return Logger{
|
|
||||||
"stdout": &Filter{lvl, NewConsoleLogWriter()},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closes all log writers in preparation for exiting the program or a
|
|
||||||
// reconfiguration of logging. Calling this is not really imperative, unless
|
|
||||||
// you want to guarantee that all log messages are written. Close removes
|
|
||||||
// all filters (and thus all LogWriters) from the logger.
|
|
||||||
func (log Logger) Close() {
|
|
||||||
// Close all open loggers
|
|
||||||
for name, filt := range log {
|
|
||||||
filt.Close()
|
|
||||||
delete(log, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new LogWriter to the Logger which will only log messages at lvl or
|
|
||||||
// higher. This function should not be called from multiple goroutines.
|
|
||||||
// Returns the logger for chaining.
|
|
||||||
func (log Logger) AddFilter(name string, lvl Level, writer LogWriter) Logger {
|
|
||||||
log[name] = &Filter{lvl, writer}
|
|
||||||
return log
|
|
||||||
}
|
|
||||||
|
|
||||||
/******* Logging *******/
|
|
||||||
// Send a formatted log message internally
|
|
||||||
func (log Logger) intLogf(lvl Level, format string, args ...interface{}) {
|
|
||||||
skip := true
|
|
||||||
|
|
||||||
// Determine if any logging will be done
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl >= filt.Level {
|
|
||||||
skip = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if skip {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine caller func
|
|
||||||
pc, _, lineno, ok := runtime.Caller(2)
|
|
||||||
src := ""
|
|
||||||
if ok {
|
|
||||||
src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := format
|
|
||||||
if len(args) > 0 {
|
|
||||||
msg = fmt.Sprintf(format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the log record
|
|
||||||
rec := &LogRecord{
|
|
||||||
Level: lvl,
|
|
||||||
Created: time.Now(),
|
|
||||||
Source: src,
|
|
||||||
Message: msg,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the logs
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl < filt.Level {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filt.LogWrite(rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a closure log message internally
|
|
||||||
func (log Logger) intLogc(lvl Level, closure func() string) {
|
|
||||||
skip := true
|
|
||||||
|
|
||||||
// Determine if any logging will be done
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl >= filt.Level {
|
|
||||||
skip = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if skip {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine caller func
|
|
||||||
pc, _, lineno, ok := runtime.Caller(2)
|
|
||||||
src := ""
|
|
||||||
if ok {
|
|
||||||
src = fmt.Sprintf("%s:%d", runtime.FuncForPC(pc).Name(), lineno)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the log record
|
|
||||||
rec := &LogRecord{
|
|
||||||
Level: lvl,
|
|
||||||
Created: time.Now(),
|
|
||||||
Source: src,
|
|
||||||
Message: closure(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the logs
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl < filt.Level {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filt.LogWrite(rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a log message with manual level, source, and message.
|
|
||||||
func (log Logger) Log(lvl Level, source, message string) {
|
|
||||||
skip := true
|
|
||||||
|
|
||||||
// Determine if any logging will be done
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl >= filt.Level {
|
|
||||||
skip = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if skip {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the log record
|
|
||||||
rec := &LogRecord{
|
|
||||||
Level: lvl,
|
|
||||||
Created: time.Now(),
|
|
||||||
Source: source,
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch the logs
|
|
||||||
for _, filt := range log {
|
|
||||||
if lvl < filt.Level {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filt.LogWrite(rec)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logf logs a formatted log message at the given log level, using the caller as
|
|
||||||
// its source.
|
|
||||||
func (log Logger) Logf(lvl Level, format string, args ...interface{}) {
|
|
||||||
log.intLogf(lvl, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logc logs a string returned by the closure at the given log level, using the caller as
|
|
||||||
// its source. If no log message would be written, the closure is never called.
|
|
||||||
func (log Logger) Logc(lvl Level, closure func() string) {
|
|
||||||
log.intLogc(lvl, closure)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finest logs a message at the finest log level.
|
|
||||||
// See Debug for an explanation of the arguments.
|
|
||||||
func (log Logger) Finest(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = FINEST
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
log.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
log.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fine logs a message at the fine log level.
|
|
||||||
// See Debug for an explanation of the arguments.
|
|
||||||
func (log Logger) Fine(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = FINE
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
log.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
log.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Debug is a utility method for debug log messages.
|
|
||||||
// The behavior of Debug depends on the first argument:
|
|
||||||
// - arg0 is a string
|
|
||||||
// When given a string as the first argument, this behaves like Logf but with
|
|
||||||
// the DEBUG log level: the first argument is interpreted as a format for the
|
|
||||||
// latter arguments.
|
|
||||||
// - arg0 is a func()string
|
|
||||||
// When given a closure of type func()string, this logs the string returned by
|
|
||||||
// the closure iff it will be logged. The closure runs at most one time.
|
|
||||||
// - arg0 is interface{}
|
|
||||||
// When given anything else, the log message will be each of the arguments
|
|
||||||
// formatted with %v and separated by spaces (ala Sprint).
|
|
||||||
func (log Logger) Debug(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = DEBUG
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
log.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
log.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Trace logs a message at the trace log level.
|
|
||||||
// See Debug for an explanation of the arguments.
|
|
||||||
func (log Logger) Trace(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = TRACE
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
log.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
log.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Info logs a message at the info log level.
|
|
||||||
// See Debug for an explanation of the arguments.
|
|
||||||
func (log Logger) Info(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = INFO
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
log.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
log.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
log.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn logs a message at the warning log level and returns the formatted error.
|
|
||||||
// At the warning level and higher, there is no performance benefit if the
|
|
||||||
// message is not actually logged, because all formats are processed and all
|
|
||||||
// closures are executed to format the error message.
|
|
||||||
// See Debug for further explanation of the arguments.
|
|
||||||
func (log Logger) Warn(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = WARNING
|
|
||||||
)
|
|
||||||
var msg string
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
msg = fmt.Sprintf(first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
msg = first()
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
log.intLogf(lvl, msg)
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error logs a message at the error log level and returns the formatted error,
|
|
||||||
// See Warn for an explanation of the performance and Debug for an explanation
|
|
||||||
// of the parameters.
|
|
||||||
func (log Logger) Error(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = ERROR
|
|
||||||
)
|
|
||||||
var msg string
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
msg = fmt.Sprintf(first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
msg = first()
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
log.intLogf(lvl, msg)
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Critical logs a message at the critical log level and returns the formatted error,
|
|
||||||
// See Warn for an explanation of the performance and Debug for an explanation
|
|
||||||
// of the parameters.
|
|
||||||
func (log Logger) Critical(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = CRITICAL
|
|
||||||
)
|
|
||||||
var msg string
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
msg = fmt.Sprintf(first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
msg = first()
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
msg = fmt.Sprintf(fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
log.intLogf(lvl, msg)
|
|
||||||
return errors.New(msg)
|
|
||||||
}
|
|
|
@ -1,126 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
FORMAT_DEFAULT = "[%D %T] [%L] (%S) %M"
|
|
||||||
FORMAT_SHORT = "[%t %d] [%L] %M"
|
|
||||||
FORMAT_ABBREV = "[%L] %M"
|
|
||||||
)
|
|
||||||
|
|
||||||
type formatCacheType struct {
|
|
||||||
LastUpdateSeconds int64
|
|
||||||
shortTime, shortDate string
|
|
||||||
longTime, longDate string
|
|
||||||
}
|
|
||||||
|
|
||||||
var formatCache = &formatCacheType{}
|
|
||||||
|
|
||||||
// Known format codes:
|
|
||||||
// %T - Time (15:04:05 MST)
|
|
||||||
// %t - Time (15:04)
|
|
||||||
// %D - Date (2006/01/02)
|
|
||||||
// %d - Date (01/02/06)
|
|
||||||
// %L - Level (FNST, FINE, DEBG, TRAC, WARN, EROR, CRIT)
|
|
||||||
// %S - Source
|
|
||||||
// %M - Message
|
|
||||||
// Ignores unknown formats
|
|
||||||
// Recommended: "[%D %T] [%L] (%S) %M"
|
|
||||||
func FormatLogRecord(format string, rec *LogRecord) string {
|
|
||||||
if rec == nil {
|
|
||||||
return "<nil>"
|
|
||||||
}
|
|
||||||
if len(format) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
out := bytes.NewBuffer(make([]byte, 0, 64))
|
|
||||||
secs := rec.Created.UnixNano() / 1e9
|
|
||||||
|
|
||||||
cache := *formatCache
|
|
||||||
if cache.LastUpdateSeconds != secs {
|
|
||||||
month, day, year := rec.Created.Month(), rec.Created.Day(), rec.Created.Year()
|
|
||||||
hour, minute, second := rec.Created.Hour(), rec.Created.Minute(), rec.Created.Second()
|
|
||||||
zone, _ := rec.Created.Zone()
|
|
||||||
updated := &formatCacheType{
|
|
||||||
LastUpdateSeconds: secs,
|
|
||||||
shortTime: fmt.Sprintf("%02d:%02d", hour, minute),
|
|
||||||
shortDate: fmt.Sprintf("%02d/%02d/%02d", day, month, year%100),
|
|
||||||
longTime: fmt.Sprintf("%02d:%02d:%02d %s", hour, minute, second, zone),
|
|
||||||
longDate: fmt.Sprintf("%04d/%02d/%02d", year, month, day),
|
|
||||||
}
|
|
||||||
cache = *updated
|
|
||||||
formatCache = updated
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the string into pieces by % signs
|
|
||||||
pieces := bytes.Split([]byte(format), []byte{'%'})
|
|
||||||
|
|
||||||
// Iterate over the pieces, replacing known formats
|
|
||||||
for i, piece := range pieces {
|
|
||||||
if i > 0 && len(piece) > 0 {
|
|
||||||
switch piece[0] {
|
|
||||||
case 'T':
|
|
||||||
out.WriteString(cache.longTime)
|
|
||||||
case 't':
|
|
||||||
out.WriteString(cache.shortTime)
|
|
||||||
case 'D':
|
|
||||||
out.WriteString(cache.longDate)
|
|
||||||
case 'd':
|
|
||||||
out.WriteString(cache.shortDate)
|
|
||||||
case 'L':
|
|
||||||
out.WriteString(levelStrings[rec.Level])
|
|
||||||
case 'S':
|
|
||||||
out.WriteString(rec.Source)
|
|
||||||
case 's':
|
|
||||||
slice := strings.Split(rec.Source, "/")
|
|
||||||
out.WriteString(slice[len(slice)-1])
|
|
||||||
case 'M':
|
|
||||||
out.WriteString(rec.Message)
|
|
||||||
}
|
|
||||||
if len(piece) > 1 {
|
|
||||||
out.Write(piece[1:])
|
|
||||||
}
|
|
||||||
} else if len(piece) > 0 {
|
|
||||||
out.Write(piece)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out.WriteByte('\n')
|
|
||||||
|
|
||||||
return out.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the standard writer that prints to standard output.
|
|
||||||
type FormatLogWriter chan *LogRecord
|
|
||||||
|
|
||||||
// This creates a new FormatLogWriter
|
|
||||||
func NewFormatLogWriter(out io.Writer, format string) FormatLogWriter {
|
|
||||||
records := make(FormatLogWriter, LogBufferLength)
|
|
||||||
go records.run(out, format)
|
|
||||||
return records
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w FormatLogWriter) run(out io.Writer, format string) {
|
|
||||||
for rec := range w {
|
|
||||||
fmt.Fprint(out, FormatLogRecord(format, rec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the FormatLogWriter's output method. This will block if the output
|
|
||||||
// buffer is full.
|
|
||||||
func (w FormatLogWriter) LogWrite(rec *LogRecord) {
|
|
||||||
w <- rec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops the logger from sending messages to standard output. Attempts to
|
|
||||||
// send log messages to this logger after a Close have undefined behavior.
|
|
||||||
func (w FormatLogWriter) Close() {
|
|
||||||
close(w)
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This log writer sends output to a socket
|
|
||||||
type SocketLogWriter chan *LogRecord
|
|
||||||
|
|
||||||
// This is the SocketLogWriter's output method
|
|
||||||
func (w SocketLogWriter) LogWrite(rec *LogRecord) {
|
|
||||||
w <- rec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w SocketLogWriter) Close() {
|
|
||||||
close(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSocketLogWriter(proto, hostport string) SocketLogWriter {
|
|
||||||
sock, err := net.Dial(proto, hostport)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "NewSocketLogWriter(%q): %s\n", hostport, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w := SocketLogWriter(make(chan *LogRecord, LogBufferLength))
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if sock != nil && proto == "tcp" {
|
|
||||||
sock.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for rec := range w {
|
|
||||||
// Marshall into JSON
|
|
||||||
js, err := json.Marshal(rec)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = sock.Write(js)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprint(os.Stderr, "SocketLogWriter(%q): %s", hostport, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
return w
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var stdout io.Writer = os.Stdout
|
|
||||||
|
|
||||||
// This is the standard writer that prints to standard output.
|
|
||||||
type ConsoleLogWriter struct {
|
|
||||||
format string
|
|
||||||
w chan *LogRecord
|
|
||||||
}
|
|
||||||
|
|
||||||
// This creates a new ConsoleLogWriter
|
|
||||||
func NewConsoleLogWriter() *ConsoleLogWriter {
|
|
||||||
consoleWriter := &ConsoleLogWriter{
|
|
||||||
format: "[%T %D] [%L] (%S) %M",
|
|
||||||
w: make(chan *LogRecord, LogBufferLength),
|
|
||||||
}
|
|
||||||
go consoleWriter.run(stdout)
|
|
||||||
return consoleWriter
|
|
||||||
}
|
|
||||||
func (c *ConsoleLogWriter) SetFormat(format string) {
|
|
||||||
c.format = format
|
|
||||||
}
|
|
||||||
func (c *ConsoleLogWriter) run(out io.Writer) {
|
|
||||||
for rec := range c.w {
|
|
||||||
fmt.Fprint(out, FormatLogRecord(c.format, rec))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the ConsoleLogWriter's output method. This will block if the output
|
|
||||||
// buffer is full.
|
|
||||||
func (c *ConsoleLogWriter) LogWrite(rec *LogRecord) {
|
|
||||||
c.w <- rec
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close stops the logger from sending messages to standard output. Attempts to
|
|
||||||
// send log messages to this logger after a Close have undefined behavior.
|
|
||||||
func (c *ConsoleLogWriter) Close() {
|
|
||||||
close(c.w)
|
|
||||||
time.Sleep(50 * time.Millisecond) // Try to give console I/O time to complete
|
|
||||||
}
|
|
|
@ -1,278 +0,0 @@
|
||||||
// Copyright (C) 2010, Kyle Lemons <kyle@kylelemons.net>. All rights reserved.
|
|
||||||
|
|
||||||
package log4go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
Global Logger
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
Global = NewDefaultLogger(DEBUG)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper for (*Logger).LoadConfiguration
|
|
||||||
func LoadConfiguration(filename string) {
|
|
||||||
Global.LoadConfiguration(filename)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper for (*Logger).AddFilter
|
|
||||||
func AddFilter(name string, lvl Level, writer LogWriter) {
|
|
||||||
Global.AddFilter(name, lvl, writer)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrapper for (*Logger).Close (closes and removes all logwriters)
|
|
||||||
func Close() {
|
|
||||||
Global.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Crash(args ...interface{}) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
Global.intLogf(CRITICAL, strings.Repeat(" %v", len(args))[1:], args...)
|
|
||||||
}
|
|
||||||
panic(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logs the given message and crashes the program
|
|
||||||
func Crashf(format string, args ...interface{}) {
|
|
||||||
Global.intLogf(CRITICAL, format, args...)
|
|
||||||
Global.Close() // so that hopefully the messages get logged
|
|
||||||
panic(fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Exit(args ...interface{}) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...)
|
|
||||||
}
|
|
||||||
Global.Close() // so that hopefully the messages get logged
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Exitf(format string, args ...interface{}) {
|
|
||||||
Global.intLogf(ERROR, format, args...)
|
|
||||||
Global.Close() // so that hopefully the messages get logged
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Stderr(args ...interface{}) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
Global.intLogf(ERROR, strings.Repeat(" %v", len(args))[1:], args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Stderrf(format string, args ...interface{}) {
|
|
||||||
Global.intLogf(ERROR, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Stdout(args ...interface{}) {
|
|
||||||
if len(args) > 0 {
|
|
||||||
Global.intLogf(INFO, strings.Repeat(" %v", len(args))[1:], args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compatibility with `log`
|
|
||||||
func Stdoutf(format string, args ...interface{}) {
|
|
||||||
Global.intLogf(INFO, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a log message manually
|
|
||||||
// Wrapper for (*Logger).Log
|
|
||||||
func Log(lvl Level, source, message string) {
|
|
||||||
Global.Log(lvl, source, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a formatted log message easily
|
|
||||||
// Wrapper for (*Logger).Logf
|
|
||||||
func Logf(lvl Level, format string, args ...interface{}) {
|
|
||||||
Global.intLogf(lvl, format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send a closure log message
|
|
||||||
// Wrapper for (*Logger).Logc
|
|
||||||
func Logc(lvl Level, closure func() string) {
|
|
||||||
Global.intLogc(lvl, closure)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for finest log messages (see Debug() for parameter explanation)
|
|
||||||
// Wrapper for (*Logger).Finest
|
|
||||||
func Finest(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = FINEST
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
Global.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for fine log messages (see Debug() for parameter explanation)
|
|
||||||
// Wrapper for (*Logger).Fine
|
|
||||||
func Fine(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = FINE
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
Global.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for debug log messages
|
|
||||||
// When given a string as the first argument, this behaves like Logf but with the DEBUG log level (e.g. the first argument is interpreted as a format for the latter arguments)
|
|
||||||
// When given a closure of type func()string, this logs the string returned by the closure iff it will be logged. The closure runs at most one time.
|
|
||||||
// When given anything else, the log message will be each of the arguments formatted with %v and separated by spaces (ala Sprint).
|
|
||||||
// Wrapper for (*Logger).Debug
|
|
||||||
func Debug(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = DEBUG
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
Global.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for trace log messages (see Debug() for parameter explanation)
|
|
||||||
// Wrapper for (*Logger).Trace
|
|
||||||
func Trace(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = TRACE
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
Global.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for info log messages (see Debug() for parameter explanation)
|
|
||||||
// Wrapper for (*Logger).Info
|
|
||||||
func Info(arg0 interface{}, args ...interface{}) {
|
|
||||||
const (
|
|
||||||
lvl = INFO
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
Global.intLogc(lvl, first)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(arg0)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for warn log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
|
|
||||||
// These functions will execute a closure exactly once, to build the error message for the return
|
|
||||||
// Wrapper for (*Logger).Warn
|
|
||||||
func Warn(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = WARNING
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
return errors.New(fmt.Sprintf(first, args...))
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
str := first()
|
|
||||||
Global.intLogf(lvl, "%s", str)
|
|
||||||
return errors.New(str)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for error log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
|
|
||||||
// These functions will execute a closure exactly once, to build the error message for the return
|
|
||||||
// Wrapper for (*Logger).Error
|
|
||||||
func Error(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = ERROR
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
return errors.New(fmt.Sprintf(first, args...))
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
str := first()
|
|
||||||
Global.intLogf(lvl, "%s", str)
|
|
||||||
return errors.New(str)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility for critical log messages (returns an error for easy function returns) (see Debug() for parameter explanation)
|
|
||||||
// These functions will execute a closure exactly once, to build the error message for the return
|
|
||||||
// Wrapper for (*Logger).Critical
|
|
||||||
func Critical(arg0 interface{}, args ...interface{}) error {
|
|
||||||
const (
|
|
||||||
lvl = CRITICAL
|
|
||||||
)
|
|
||||||
switch first := arg0.(type) {
|
|
||||||
case string:
|
|
||||||
// Use the string as a format string
|
|
||||||
Global.intLogf(lvl, first, args...)
|
|
||||||
return errors.New(fmt.Sprintf(first, args...))
|
|
||||||
case func() string:
|
|
||||||
// Log the closure (no other arguments used)
|
|
||||||
str := first()
|
|
||||||
Global.intLogf(lvl, "%s", str)
|
|
||||||
return errors.New(str)
|
|
||||||
default:
|
|
||||||
// Build a format string so that it will be similar to Sprint
|
|
||||||
Global.intLogf(lvl, fmt.Sprint(first)+strings.Repeat(" %v", len(args)), args...)
|
|
||||||
return errors.New(fmt.Sprint(first) + fmt.Sprintf(strings.Repeat(" %v", len(args)), args...))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
|
@ -1,2 +1,3 @@
|
||||||
.idea/
|
.idea/
|
||||||
coverage.out
|
coverage.out
|
||||||
|
tmp/
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.4
|
- '1.10'
|
||||||
- tip
|
- '1.11'
|
||||||
|
- tip
|
||||||
|
|
|
@ -3,10 +3,6 @@
|
||||||
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
|
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
|
||||||
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api)
|
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api)
|
||||||
|
|
||||||
All methods have been added, and all features should be available.
|
|
||||||
If you want a feature that hasn't been added yet or something is broken,
|
|
||||||
open an issue and I'll see what I can do.
|
|
||||||
|
|
||||||
All methods are fairly self explanatory, and reading the godoc page should
|
All methods are fairly self explanatory, and reading the godoc page should
|
||||||
explain everything. If something isn't clear, open an issue or submit
|
explain everything. If something isn't clear, open an issue or submit
|
||||||
a pull request.
|
a pull request.
|
||||||
|
@ -16,14 +12,14 @@ without any additional features. There are other projects for creating
|
||||||
something with plugins and command handlers without having to design
|
something with plugins and command handlers without having to design
|
||||||
all that yourself.
|
all that yourself.
|
||||||
|
|
||||||
Use `github.com/go-telegram-bot-api/telegram-bot-api` for the latest
|
|
||||||
version, or use `gopkg.in/telegram-bot-api.v4` for the stable build.
|
|
||||||
|
|
||||||
Join [the development group](https://telegram.me/go_telegram_bot_api) if
|
Join [the development group](https://telegram.me/go_telegram_bot_api) if
|
||||||
you want to ask questions or discuss development.
|
you want to ask questions or discuss development.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
First, ensure the library is installed and up to date by running
|
||||||
|
`go get -u github.com/go-telegram-bot-api/telegram-bot-api`.
|
||||||
|
|
||||||
This is a very simple bot that just displays any gotten updates,
|
This is a very simple bot that just displays any gotten updates,
|
||||||
then replies it to that chat.
|
then replies it to that chat.
|
||||||
|
|
||||||
|
@ -32,7 +28,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"gopkg.in/telegram-bot-api.v4"
|
|
||||||
|
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -51,7 +48,7 @@ func main() {
|
||||||
updates, err := bot.GetUpdatesChan(u)
|
updates, err := bot.GetUpdatesChan(u)
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
if update.Message == nil {
|
if update.Message == nil { // ignore any non-Message Updates
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +62,11 @@ func main() {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki)
|
||||||
|
with detailed information on how to do many differen kinds of things.
|
||||||
|
It's a great place to get started on using keyboards, commands, or other
|
||||||
|
kinds of reply markup.
|
||||||
|
|
||||||
If you need to use webhooks (if you wish to run on Google App Engine),
|
If you need to use webhooks (if you wish to run on Google App Engine),
|
||||||
you may use a slightly different method.
|
you may use a slightly different method.
|
||||||
|
|
||||||
|
@ -72,9 +74,10 @@ you may use a slightly different method.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/telegram-bot-api.v4"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -96,7 +99,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if info.LastErrorDate != 0 {
|
if info.LastErrorDate != 0 {
|
||||||
log.Printf("[Telegram callback failed]%s", info.LastErrorMessage)
|
log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
|
||||||
}
|
}
|
||||||
updates := bot.ListenForWebhook("/" + bot.Token)
|
updates := bot.ListenForWebhook("/" + bot.Token)
|
||||||
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
|
go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
|
||||||
|
@ -114,5 +117,5 @@ properly signed.
|
||||||
|
|
||||||
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes
|
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes
|
||||||
|
|
||||||
Now that [Let's Encrypt](https://letsencrypt.org) has entered public beta,
|
Now that [Let's Encrypt](https://letsencrypt.org) is available,
|
||||||
you may wish to generate your free TLS certificate there.
|
you may wish to generate your free TLS certificate there.
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -28,6 +27,7 @@ type BotAPI struct {
|
||||||
|
|
||||||
Self User `json:"-"`
|
Self User `json:"-"`
|
||||||
Client *http.Client `json:"-"`
|
Client *http.Client `json:"-"`
|
||||||
|
shutdownChannel chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBotAPI creates a new BotAPI instance.
|
// NewBotAPI creates a new BotAPI instance.
|
||||||
|
@ -46,6 +46,7 @@ func NewBotAPIWithClient(token string, client *http.Client) (*BotAPI, error) {
|
||||||
Token: token,
|
Token: token,
|
||||||
Client: client,
|
Client: client,
|
||||||
Buffer: 100,
|
Buffer: 100,
|
||||||
|
shutdownChannel: make(chan interface{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
self, err := bot.GetMe()
|
self, err := bot.GetMe()
|
||||||
|
@ -484,6 +485,12 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
|
select {
|
||||||
|
case <-bot.shutdownChannel:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
updates, err := bot.GetUpdates(config)
|
updates, err := bot.GetUpdates(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
|
@ -505,6 +512,14 @@ func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) (UpdatesChannel, error) {
|
||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StopReceivingUpdates stops the go routine which receives updates
|
||||||
|
func (bot *BotAPI) StopReceivingUpdates() {
|
||||||
|
if bot.Debug {
|
||||||
|
log.Println("Stopping the update receiver routine...")
|
||||||
|
}
|
||||||
|
close(bot.shutdownChannel)
|
||||||
|
}
|
||||||
|
|
||||||
// ListenForWebhook registers a http handler for a webhook.
|
// ListenForWebhook registers a http handler for a webhook.
|
||||||
func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
|
func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
|
||||||
ch := make(chan Update, bot.Buffer)
|
ch := make(chan Update, bot.Buffer)
|
||||||
|
|
|
@ -243,7 +243,8 @@ func (config ForwardConfig) method() string {
|
||||||
// PhotoConfig contains information about a SendPhoto request.
|
// PhotoConfig contains information about a SendPhoto request.
|
||||||
type PhotoConfig struct {
|
type PhotoConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
Caption string
|
Caption string
|
||||||
|
ParseMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params returns a map[string]string representation of PhotoConfig.
|
// Params returns a map[string]string representation of PhotoConfig.
|
||||||
|
@ -252,6 +253,9 @@ func (config PhotoConfig) params() (map[string]string, error) {
|
||||||
|
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
params["caption"] = config.Caption
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -267,7 +271,11 @@ func (config PhotoConfig) values() (url.Values, error) {
|
||||||
v.Add(config.name(), config.FileID)
|
v.Add(config.name(), config.FileID)
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,6 +293,7 @@ func (config PhotoConfig) method() string {
|
||||||
type AudioConfig struct {
|
type AudioConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
Caption string
|
Caption string
|
||||||
|
ParseMode string
|
||||||
Duration int
|
Duration int
|
||||||
Performer string
|
Performer string
|
||||||
Title string
|
Title string
|
||||||
|
@ -310,6 +319,9 @@ func (config AudioConfig) values() (url.Values, error) {
|
||||||
}
|
}
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -331,6 +343,9 @@ func (config AudioConfig) params() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
params["caption"] = config.Caption
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -349,7 +364,8 @@ func (config AudioConfig) method() string {
|
||||||
// DocumentConfig contains information about a SendDocument request.
|
// DocumentConfig contains information about a SendDocument request.
|
||||||
type DocumentConfig struct {
|
type DocumentConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
Caption string
|
Caption string
|
||||||
|
ParseMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
// values returns a url.Values representation of DocumentConfig.
|
// values returns a url.Values representation of DocumentConfig.
|
||||||
|
@ -362,6 +378,9 @@ func (config DocumentConfig) values() (url.Values, error) {
|
||||||
v.Add(config.name(), config.FileID)
|
v.Add(config.name(), config.FileID)
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -373,6 +392,9 @@ func (config DocumentConfig) params() (map[string]string, error) {
|
||||||
|
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
params["caption"] = config.Caption
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -425,8 +447,9 @@ func (config StickerConfig) method() string {
|
||||||
// VideoConfig contains information about a SendVideo request.
|
// VideoConfig contains information about a SendVideo request.
|
||||||
type VideoConfig struct {
|
type VideoConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
Duration int
|
Duration int
|
||||||
Caption string
|
Caption string
|
||||||
|
ParseMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
// values returns a url.Values representation of VideoConfig.
|
// values returns a url.Values representation of VideoConfig.
|
||||||
|
@ -442,6 +465,9 @@ func (config VideoConfig) values() (url.Values, error) {
|
||||||
}
|
}
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -453,6 +479,9 @@ func (config VideoConfig) params() (map[string]string, error) {
|
||||||
|
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
params["caption"] = config.Caption
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -468,6 +497,59 @@ func (config VideoConfig) method() string {
|
||||||
return "sendVideo"
|
return "sendVideo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnimationConfig contains information about a SendAnimation request.
|
||||||
|
type AnimationConfig struct {
|
||||||
|
BaseFile
|
||||||
|
Duration int
|
||||||
|
Caption string
|
||||||
|
ParseMode string
|
||||||
|
}
|
||||||
|
|
||||||
|
// values returns a url.Values representation of AnimationConfig.
|
||||||
|
func (config AnimationConfig) values() (url.Values, error) {
|
||||||
|
v, err := config.BaseChat.values()
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Add(config.name(), config.FileID)
|
||||||
|
if config.Duration != 0 {
|
||||||
|
v.Add("duration", strconv.Itoa(config.Duration))
|
||||||
|
}
|
||||||
|
if config.Caption != "" {
|
||||||
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// params returns a map[string]string representation of AnimationConfig.
|
||||||
|
func (config AnimationConfig) params() (map[string]string, error) {
|
||||||
|
params, _ := config.BaseFile.params()
|
||||||
|
|
||||||
|
if config.Caption != "" {
|
||||||
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// name returns the field name for the Animation.
|
||||||
|
func (config AnimationConfig) name() string {
|
||||||
|
return "animation"
|
||||||
|
}
|
||||||
|
|
||||||
|
// method returns Telegram API method name for sending Animation.
|
||||||
|
func (config AnimationConfig) method() string {
|
||||||
|
return "sendAnimation"
|
||||||
|
}
|
||||||
|
|
||||||
// VideoNoteConfig contains information about a SendVideoNote request.
|
// VideoNoteConfig contains information about a SendVideoNote request.
|
||||||
type VideoNoteConfig struct {
|
type VideoNoteConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
|
@ -522,8 +604,9 @@ func (config VideoNoteConfig) method() string {
|
||||||
// VoiceConfig contains information about a SendVoice request.
|
// VoiceConfig contains information about a SendVoice request.
|
||||||
type VoiceConfig struct {
|
type VoiceConfig struct {
|
||||||
BaseFile
|
BaseFile
|
||||||
Caption string
|
Caption string
|
||||||
Duration int
|
ParseMode string
|
||||||
|
Duration int
|
||||||
}
|
}
|
||||||
|
|
||||||
// values returns a url.Values representation of VoiceConfig.
|
// values returns a url.Values representation of VoiceConfig.
|
||||||
|
@ -539,6 +622,9 @@ func (config VoiceConfig) values() (url.Values, error) {
|
||||||
}
|
}
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
|
@ -553,6 +639,9 @@ func (config VoiceConfig) params() (map[string]string, error) {
|
||||||
}
|
}
|
||||||
if config.Caption != "" {
|
if config.Caption != "" {
|
||||||
params["caption"] = config.Caption
|
params["caption"] = config.Caption
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
params["parse_mode"] = config.ParseMode
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return params, nil
|
return params, nil
|
||||||
|
@ -568,6 +657,32 @@ func (config VoiceConfig) method() string {
|
||||||
return "sendVoice"
|
return "sendVoice"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MediaGroupConfig contains information about a sendMediaGroup request.
|
||||||
|
type MediaGroupConfig struct {
|
||||||
|
BaseChat
|
||||||
|
InputMedia []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config MediaGroupConfig) values() (url.Values, error) {
|
||||||
|
v, err := config.BaseChat.values()
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(config.InputMedia)
|
||||||
|
if err != nil {
|
||||||
|
return v, err
|
||||||
|
}
|
||||||
|
|
||||||
|
v.Add("media", string(data))
|
||||||
|
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (config MediaGroupConfig) method() string {
|
||||||
|
return "sendMediaGroup"
|
||||||
|
}
|
||||||
|
|
||||||
// LocationConfig contains information about a SendLocation request.
|
// LocationConfig contains information about a SendLocation request.
|
||||||
type LocationConfig struct {
|
type LocationConfig struct {
|
||||||
BaseChat
|
BaseChat
|
||||||
|
@ -786,13 +901,17 @@ func (config EditMessageTextConfig) method() string {
|
||||||
// EditMessageCaptionConfig allows you to modify the caption of a message.
|
// EditMessageCaptionConfig allows you to modify the caption of a message.
|
||||||
type EditMessageCaptionConfig struct {
|
type EditMessageCaptionConfig struct {
|
||||||
BaseEdit
|
BaseEdit
|
||||||
Caption string
|
Caption string
|
||||||
|
ParseMode string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (config EditMessageCaptionConfig) values() (url.Values, error) {
|
func (config EditMessageCaptionConfig) values() (url.Values, error) {
|
||||||
v, _ := config.BaseEdit.values()
|
v, _ := config.BaseEdit.values()
|
||||||
|
|
||||||
v.Add("caption", config.Caption)
|
v.Add("caption", config.Caption)
|
||||||
|
if config.ParseMode != "" {
|
||||||
|
v.Add("parse_mode", config.ParseMode)
|
||||||
|
}
|
||||||
|
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package tgbotapi
|
package tgbotapi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,11 +13,12 @@ func NewMessage(chatID int64, text string) MessageConfig {
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
ReplyToMessageID: 0,
|
ReplyToMessageID: 0,
|
||||||
},
|
},
|
||||||
Text: text,
|
Text: text,
|
||||||
DisableWebPagePreview: false,
|
DisableWebPagePreview: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewDeleteMessage creates a request to delete a message.
|
||||||
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
|
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
|
||||||
return DeleteMessageConfig{
|
return DeleteMessageConfig{
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
|
@ -201,6 +201,35 @@ func NewVideoShare(chatID int64, fileID string) VideoConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewAnimationUpload creates a new animation uploader.
|
||||||
|
//
|
||||||
|
// chatID is where to send it, file is a string path to the file,
|
||||||
|
// FileReader, or FileBytes.
|
||||||
|
func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig {
|
||||||
|
return AnimationConfig{
|
||||||
|
BaseFile: BaseFile{
|
||||||
|
BaseChat: BaseChat{ChatID: chatID},
|
||||||
|
File: file,
|
||||||
|
UseExisting: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAnimationShare shares an existing animation.
|
||||||
|
// You may use this to reshare an existing animation without reuploading it.
|
||||||
|
//
|
||||||
|
// chatID is where to send it, fileID is the ID of the animation
|
||||||
|
// already uploaded.
|
||||||
|
func NewAnimationShare(chatID int64, fileID string) AnimationConfig {
|
||||||
|
return AnimationConfig{
|
||||||
|
BaseFile: BaseFile{
|
||||||
|
BaseChat: BaseChat{ChatID: chatID},
|
||||||
|
FileID: fileID,
|
||||||
|
UseExisting: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewVideoNoteUpload creates a new video note uploader.
|
// NewVideoNoteUpload creates a new video note uploader.
|
||||||
//
|
//
|
||||||
// chatID is where to send it, file is a string path to the file,
|
// chatID is where to send it, file is a string path to the file,
|
||||||
|
@ -261,6 +290,33 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMediaGroup creates a new media group. Files should be an array of
|
||||||
|
// two to ten InputMediaPhoto or InputMediaVideo.
|
||||||
|
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
|
||||||
|
return MediaGroupConfig{
|
||||||
|
BaseChat: BaseChat{
|
||||||
|
ChatID: chatID,
|
||||||
|
},
|
||||||
|
InputMedia: files,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInputMediaPhoto creates a new InputMediaPhoto.
|
||||||
|
func NewInputMediaPhoto(media string) InputMediaPhoto {
|
||||||
|
return InputMediaPhoto{
|
||||||
|
Type: "photo",
|
||||||
|
Media: media,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInputMediaVideo creates a new InputMediaVideo.
|
||||||
|
func NewInputMediaVideo(media string) InputMediaVideo {
|
||||||
|
return InputMediaVideo{
|
||||||
|
Type: "video",
|
||||||
|
Media: media,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NewContact allows you to send a shared contact.
|
// NewContact allows you to send a shared contact.
|
||||||
func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig {
|
func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig {
|
||||||
return ContactConfig{
|
return ContactConfig{
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
stdlog "log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BotLogger is an interface that represents the required methods to log data.
|
||||||
|
//
|
||||||
|
// Instead of requiring the standard logger, we can just specify the methods we
|
||||||
|
// use and allow users to pass anything that implements these.
|
||||||
|
type BotLogger interface {
|
||||||
|
Println(v ...interface{})
|
||||||
|
Printf(format string, v ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var log BotLogger = stdlog.New(os.Stderr, "", stdlog.LstdFlags)
|
||||||
|
|
||||||
|
// SetLogger specifies the logger that the package should use.
|
||||||
|
func SetLogger(logger BotLogger) error {
|
||||||
|
if logger == nil {
|
||||||
|
return errors.New("logger is nil")
|
||||||
|
}
|
||||||
|
log = logger
|
||||||
|
return nil
|
||||||
|
}
|
315
vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go
generated
vendored
Normal file
315
vendor/github.com/go-telegram-bot-api/telegram-bot-api/passport.go
generated
vendored
Normal file
|
@ -0,0 +1,315 @@
|
||||||
|
package tgbotapi
|
||||||
|
|
||||||
|
// PassportRequestInfoConfig allows you to request passport info
|
||||||
|
type PassportRequestInfoConfig struct {
|
||||||
|
BotID int `json:"bot_id"`
|
||||||
|
Scope *PassportScope `json:"scope"`
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
PublicKey string `json:"public_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportScopeElement supports using one or one of several elements.
|
||||||
|
type PassportScopeElement interface {
|
||||||
|
ScopeType() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportScope is the requested scopes of data.
|
||||||
|
type PassportScope struct {
|
||||||
|
V int `json:"v"`
|
||||||
|
Data []PassportScopeElement `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportScopeElementOneOfSeveral allows you to request any one of the
|
||||||
|
// requested documents.
|
||||||
|
type PassportScopeElementOneOfSeveral struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopeType is the scope type.
|
||||||
|
func (eo *PassportScopeElementOneOfSeveral) ScopeType() string {
|
||||||
|
return "one_of"
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportScopeElementOne requires the specified element be provided.
|
||||||
|
type PassportScopeElementOne struct {
|
||||||
|
Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email”
|
||||||
|
Selfie bool `json:"selfie"`
|
||||||
|
Translation bool `json:"translation"`
|
||||||
|
NativeNames bool `json:"native_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScopeType is the scope type.
|
||||||
|
func (eo *PassportScopeElementOne) ScopeType() string {
|
||||||
|
return "one"
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// PassportData contains information about Telegram Passport data shared with
|
||||||
|
// the bot by the user.
|
||||||
|
PassportData struct {
|
||||||
|
// Array with information about documents and other Telegram Passport
|
||||||
|
// elements that was shared with the bot
|
||||||
|
Data []EncryptedPassportElement `json:"data"`
|
||||||
|
|
||||||
|
// Encrypted credentials required to decrypt the data
|
||||||
|
Credentials *EncryptedCredentials `json:"credentials"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportFile represents a file uploaded to Telegram Passport. Currently all
|
||||||
|
// Telegram Passport files are in JPEG format when decrypted and don't exceed
|
||||||
|
// 10MB.
|
||||||
|
PassportFile struct {
|
||||||
|
// Unique identifier for this file
|
||||||
|
FileID string `json:"file_id"`
|
||||||
|
|
||||||
|
// File size
|
||||||
|
FileSize int `json:"file_size"`
|
||||||
|
|
||||||
|
// Unix time when the file was uploaded
|
||||||
|
FileDate int64 `json:"file_date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedPassportElement contains information about documents or other
|
||||||
|
// Telegram Passport elements shared with the bot by the user.
|
||||||
|
EncryptedPassportElement struct {
|
||||||
|
// Element type.
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Base64-encoded encrypted Telegram Passport element data provided by
|
||||||
|
// the user, available for "personal_details", "passport",
|
||||||
|
// "driver_license", "identity_card", "identity_passport" and "address"
|
||||||
|
// types. Can be decrypted and verified using the accompanying
|
||||||
|
// EncryptedCredentials.
|
||||||
|
Data string `json:"data,omitempty"`
|
||||||
|
|
||||||
|
// User's verified phone number, available only for "phone_number" type
|
||||||
|
PhoneNumber string `json:"phone_number,omitempty"`
|
||||||
|
|
||||||
|
// User's verified email address, available only for "email" type
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
|
||||||
|
// Array of encrypted files with documents provided by the user,
|
||||||
|
// available for "utility_bill", "bank_statement", "rental_agreement",
|
||||||
|
// "passport_registration" and "temporary_registration" types. Files can
|
||||||
|
// be decrypted and verified using the accompanying EncryptedCredentials.
|
||||||
|
Files []PassportFile `json:"files,omitempty"`
|
||||||
|
|
||||||
|
// Encrypted file with the front side of the document, provided by the
|
||||||
|
// user. Available for "passport", "driver_license", "identity_card" and
|
||||||
|
// "internal_passport". The file can be decrypted and verified using the
|
||||||
|
// accompanying EncryptedCredentials.
|
||||||
|
FrontSide *PassportFile `json:"front_side,omitempty"`
|
||||||
|
|
||||||
|
// Encrypted file with the reverse side of the document, provided by the
|
||||||
|
// user. Available for "driver_license" and "identity_card". The file can
|
||||||
|
// be decrypted and verified using the accompanying EncryptedCredentials.
|
||||||
|
ReverseSide *PassportFile `json:"reverse_side,omitempty"`
|
||||||
|
|
||||||
|
// Encrypted file with the selfie of the user holding a document,
|
||||||
|
// provided by the user; available for "passport", "driver_license",
|
||||||
|
// "identity_card" and "internal_passport". The file can be decrypted
|
||||||
|
// and verified using the accompanying EncryptedCredentials.
|
||||||
|
Selfie *PassportFile `json:"selfie,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncryptedCredentials contains data required for decrypting and
|
||||||
|
// authenticating EncryptedPassportElement. See the Telegram Passport
|
||||||
|
// Documentation for a complete description of the data decryption and
|
||||||
|
// authentication processes.
|
||||||
|
EncryptedCredentials struct {
|
||||||
|
// Base64-encoded encrypted JSON-serialized data with unique user's
|
||||||
|
// payload, data hashes and secrets required for EncryptedPassportElement
|
||||||
|
// decryption and authentication
|
||||||
|
Data string `json:"data"`
|
||||||
|
|
||||||
|
// Base64-encoded data hash for data authentication
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
|
||||||
|
// Base64-encoded secret, encrypted with the bot's public RSA key,
|
||||||
|
// required for data decryption
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementError represents an error in the Telegram Passport element
|
||||||
|
// which was submitted that should be resolved by the user.
|
||||||
|
PassportElementError interface{}
|
||||||
|
|
||||||
|
// PassportElementErrorDataField represents an issue in one of the data
|
||||||
|
// fields that was provided by the user. The error is considered resolved
|
||||||
|
// when the field's value changes.
|
||||||
|
PassportElementErrorDataField struct {
|
||||||
|
// Error source, must be data
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the error, one
|
||||||
|
// of "personal_details", "passport", "driver_license", "identity_card",
|
||||||
|
// "internal_passport", "address"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Name of the data field which has the error
|
||||||
|
FieldName string `json:"field_name"`
|
||||||
|
|
||||||
|
// Base64-encoded data hash
|
||||||
|
DataHash string `json:"data_hash"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorFrontSide represents an issue with the front side of
|
||||||
|
// a document. The error is considered resolved when the file with the front
|
||||||
|
// side of the document changes.
|
||||||
|
PassportElementErrorFrontSide struct {
|
||||||
|
// Error source, must be front_side
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the issue, one
|
||||||
|
// of "passport", "driver_license", "identity_card", "internal_passport"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Base64-encoded hash of the file with the front side of the document
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorReverseSide represents an issue with the reverse side
|
||||||
|
// of a document. The error is considered resolved when the file with reverse
|
||||||
|
// side of the document changes.
|
||||||
|
PassportElementErrorReverseSide struct {
|
||||||
|
// Error source, must be reverse_side
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the issue, one
|
||||||
|
// of "driver_license", "identity_card"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Base64-encoded hash of the file with the reverse side of the document
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorSelfie represents an issue with the selfie with a
|
||||||
|
// document. The error is considered resolved when the file with the selfie
|
||||||
|
// changes.
|
||||||
|
PassportElementErrorSelfie struct {
|
||||||
|
// Error source, must be selfie
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the issue, one
|
||||||
|
// of "passport", "driver_license", "identity_card", "internal_passport"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Base64-encoded hash of the file with the selfie
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorFile represents an issue with a document scan. The
|
||||||
|
// error is considered resolved when the file with the document scan changes.
|
||||||
|
PassportElementErrorFile struct {
|
||||||
|
// Error source, must be file
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the issue, one
|
||||||
|
// of "utility_bill", "bank_statement", "rental_agreement",
|
||||||
|
// "passport_registration", "temporary_registration"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// Base64-encoded file hash
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassportElementErrorFiles represents an issue with a list of scans. The
|
||||||
|
// error is considered resolved when the list of files containing the scans
|
||||||
|
// changes.
|
||||||
|
PassportElementErrorFiles struct {
|
||||||
|
// Error source, must be files
|
||||||
|
Source string `json:"source"`
|
||||||
|
|
||||||
|
// The section of the user's Telegram Passport which has the issue, one
|
||||||
|
// of "utility_bill", "bank_statement", "rental_agreement",
|
||||||
|
// "passport_registration", "temporary_registration"
|
||||||
|
Type string `json:"type"`
|
||||||
|
|
||||||
|
// List of base64-encoded file hashes
|
||||||
|
FileHashes []string `json:"file_hashes"`
|
||||||
|
|
||||||
|
// Error message
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credentials contains encrypted data.
|
||||||
|
Credentials struct {
|
||||||
|
Data SecureData `json:"secure_data"`
|
||||||
|
// Nonce the same nonce given in the request
|
||||||
|
Nonce string `json:"nonce"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureData is a map of the fields and their encrypted values.
|
||||||
|
SecureData map[string]*SecureValue
|
||||||
|
// PersonalDetails *SecureValue `json:"personal_details"`
|
||||||
|
// Passport *SecureValue `json:"passport"`
|
||||||
|
// InternalPassport *SecureValue `json:"internal_passport"`
|
||||||
|
// DriverLicense *SecureValue `json:"driver_license"`
|
||||||
|
// IdentityCard *SecureValue `json:"identity_card"`
|
||||||
|
// Address *SecureValue `json:"address"`
|
||||||
|
// UtilityBill *SecureValue `json:"utility_bill"`
|
||||||
|
// BankStatement *SecureValue `json:"bank_statement"`
|
||||||
|
// RentalAgreement *SecureValue `json:"rental_agreement"`
|
||||||
|
// PassportRegistration *SecureValue `json:"passport_registration"`
|
||||||
|
// TemporaryRegistration *SecureValue `json:"temporary_registration"`
|
||||||
|
|
||||||
|
// SecureValue contains encrypted values for a SecureData item.
|
||||||
|
SecureValue struct {
|
||||||
|
Data *DataCredentials `json:"data"`
|
||||||
|
FrontSide *FileCredentials `json:"front_side"`
|
||||||
|
ReverseSide *FileCredentials `json:"reverse_side"`
|
||||||
|
Selfie *FileCredentials `json:"selfie"`
|
||||||
|
Translation []*FileCredentials `json:"translation"`
|
||||||
|
Files []*FileCredentials `json:"files"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DataCredentials contains information required to decrypt data.
|
||||||
|
DataCredentials struct {
|
||||||
|
// DataHash checksum of encrypted data
|
||||||
|
DataHash string `json:"data_hash"`
|
||||||
|
// Secret of encrypted data
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileCredentials contains information required to decrypt files.
|
||||||
|
FileCredentials struct {
|
||||||
|
// FileHash checksum of encrypted data
|
||||||
|
FileHash string `json:"file_hash"`
|
||||||
|
// Secret of encrypted data
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersonalDetails https://core.telegram.org/passport#personaldetails
|
||||||
|
PersonalDetails struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
MiddleName string `json:"middle_name"`
|
||||||
|
BirthDate string `json:"birth_date"`
|
||||||
|
Gender string `json:"gender"`
|
||||||
|
CountryCode string `json:"country_code"`
|
||||||
|
ResidenceCountryCode string `json:"residence_country_code"`
|
||||||
|
FirstNameNative string `json:"first_name_native"`
|
||||||
|
LastNameNative string `json:"last_name_native"`
|
||||||
|
MiddleNameNative string `json:"middle_name_native"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDDocumentData https://core.telegram.org/passport#iddocumentdata
|
||||||
|
IDDocumentData struct {
|
||||||
|
DocumentNumber string `json:"document_no"`
|
||||||
|
ExpiryDate string `json:"expiry_date"`
|
||||||
|
}
|
||||||
|
)
|
|
@ -144,6 +144,7 @@ type Message struct {
|
||||||
Entities *[]MessageEntity `json:"entities"` // optional
|
Entities *[]MessageEntity `json:"entities"` // optional
|
||||||
Audio *Audio `json:"audio"` // optional
|
Audio *Audio `json:"audio"` // optional
|
||||||
Document *Document `json:"document"` // optional
|
Document *Document `json:"document"` // optional
|
||||||
|
Animation *ChatAnimation `json:"animation"` // optional
|
||||||
Game *Game `json:"game"` // optional
|
Game *Game `json:"game"` // optional
|
||||||
Photo *[]PhotoSize `json:"photo"` // optional
|
Photo *[]PhotoSize `json:"photo"` // optional
|
||||||
Sticker *Sticker `json:"sticker"` // optional
|
Sticker *Sticker `json:"sticker"` // optional
|
||||||
|
@ -167,6 +168,7 @@ type Message struct {
|
||||||
PinnedMessage *Message `json:"pinned_message"` // optional
|
PinnedMessage *Message `json:"pinned_message"` // optional
|
||||||
Invoice *Invoice `json:"invoice"` // optional
|
Invoice *Invoice `json:"invoice"` // optional
|
||||||
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
|
SuccessfulPayment *SuccessfulPayment `json:"successful_payment"` // optional
|
||||||
|
PassportData *PassportData `json:"passport_data,omitempty"` // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
// Time converts the message timestamp into a Time.
|
// Time converts the message timestamp into a Time.
|
||||||
|
@ -293,6 +295,18 @@ type Sticker struct {
|
||||||
SetName string `json:"set_name"` // optional
|
SetName string `json:"set_name"` // optional
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ChatAnimation contains information about an animation.
|
||||||
|
type ChatAnimation struct {
|
||||||
|
FileID string `json:"file_id"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
Thumbnail *PhotoSize `json:"thumb"` // optional
|
||||||
|
FileName string `json:"file_name"` // optional
|
||||||
|
MimeType string `json:"mime_type"` // optional
|
||||||
|
FileSize int `json:"file_size"` // optional
|
||||||
|
}
|
||||||
|
|
||||||
// Video contains information about a video.
|
// Video contains information about a video.
|
||||||
type Video struct {
|
type Video struct {
|
||||||
FileID string `json:"file_id"`
|
FileID string `json:"file_id"`
|
||||||
|
@ -511,6 +525,27 @@ func (info WebhookInfo) IsSet() bool {
|
||||||
return info.URL != ""
|
return info.URL != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InputMediaPhoto contains a photo for displaying as part of a media group.
|
||||||
|
type InputMediaPhoto struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Media string `json:"media"`
|
||||||
|
Caption string `json:"caption"`
|
||||||
|
ParseMode string `json:"parse_mode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputMediaVideo contains a video for displaying as part of a media group.
|
||||||
|
type InputMediaVideo struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Media string `json:"media"`
|
||||||
|
// thumb intentionally missing as it is not currently compatible
|
||||||
|
Caption string `json:"caption"`
|
||||||
|
ParseMode string `json:"parse_mode"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Duration int `json:"duration"`
|
||||||
|
SupportsStreaming bool `json:"supports_streaming"`
|
||||||
|
}
|
||||||
|
|
||||||
// InlineQuery is a Query from Telegram for an inline request.
|
// InlineQuery is a Query from Telegram for an inline request.
|
||||||
type InlineQuery struct {
|
type InlineQuery struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -14,14 +16,13 @@ import (
|
||||||
"os"
|
"os"
|
||||||
gosignal "os/signal"
|
gosignal "os/signal"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"runtime/debug"
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
"runtime/trace"
|
"runtime/trace"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"bufio"
|
|
||||||
|
|
||||||
"github.com/google/gops/internal"
|
"github.com/google/gops/internal"
|
||||||
"github.com/google/gops/signal"
|
"github.com/google/gops/signal"
|
||||||
"github.com/kardianos/osext"
|
"github.com/kardianos/osext"
|
||||||
|
@ -43,10 +44,16 @@ type Options struct {
|
||||||
// Optional.
|
// Optional.
|
||||||
Addr string
|
Addr string
|
||||||
|
|
||||||
// NoShutdownCleanup tells the agent not to automatically cleanup
|
// ConfigDir is the directory to store the configuration file,
|
||||||
// resources if the running process receives an interrupt.
|
// PID of the gops process, filename, port as well as content.
|
||||||
// Optional.
|
// Optional.
|
||||||
NoShutdownCleanup bool
|
ConfigDir string
|
||||||
|
|
||||||
|
// ShutdownCleanup automatically cleans up resources if the
|
||||||
|
// running process receives an interrupt. Otherwise, users
|
||||||
|
// can call Close before shutting down.
|
||||||
|
// Optional.
|
||||||
|
ShutdownCleanup bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen starts the gops agent on a host process. Once agent started, users
|
// Listen starts the gops agent on a host process. Once agent started, users
|
||||||
|
@ -58,26 +65,29 @@ type Options struct {
|
||||||
// Note: The agent exposes an endpoint via a TCP connection that can be used by
|
// Note: The agent exposes an endpoint via a TCP connection that can be used by
|
||||||
// any program on the system. Review your security requirements before starting
|
// any program on the system. Review your security requirements before starting
|
||||||
// the agent.
|
// the agent.
|
||||||
func Listen(opts *Options) error {
|
func Listen(opts Options) error {
|
||||||
mu.Lock()
|
mu.Lock()
|
||||||
defer mu.Unlock()
|
defer mu.Unlock()
|
||||||
|
|
||||||
if opts == nil {
|
|
||||||
opts = &Options{}
|
|
||||||
}
|
|
||||||
if portfile != "" {
|
if portfile != "" {
|
||||||
return fmt.Errorf("gops: agent already listening at: %v", listener.Addr())
|
return fmt.Errorf("gops: agent already listening at: %v", listener.Addr())
|
||||||
}
|
}
|
||||||
|
|
||||||
gopsdir, err := internal.ConfigDir()
|
// new
|
||||||
|
gopsdir := opts.ConfigDir
|
||||||
|
if gopsdir == "" {
|
||||||
|
cfgDir, err := internal.ConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gopsdir = cfgDir
|
||||||
|
}
|
||||||
|
|
||||||
|
err := os.MkdirAll(gopsdir, os.ModePerm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(gopsdir, os.ModePerm)
|
if opts.ShutdownCleanup {
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !opts.NoShutdownCleanup {
|
|
||||||
gracefulShutdown()
|
gracefulShutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,7 +175,7 @@ func formatBytes(val uint64) string {
|
||||||
return fmt.Sprintf("%d bytes", val)
|
return fmt.Sprintf("%d bytes", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handle(conn io.Writer, msg []byte) error {
|
func handle(conn io.ReadWriter, msg []byte) error {
|
||||||
switch msg[0] {
|
switch msg[0] {
|
||||||
case signal.StackTrace:
|
case signal.StackTrace:
|
||||||
return pprof.Lookup("goroutine").WriteTo(conn, 2)
|
return pprof.Lookup("goroutine").WriteTo(conn, 2)
|
||||||
|
@ -190,13 +200,20 @@ func handle(conn io.Writer, msg []byte) error {
|
||||||
fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects)
|
fmt.Fprintf(conn, "heap-objects: %v\n", s.HeapObjects)
|
||||||
fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse))
|
fmt.Fprintf(conn, "stack-in-use: %v\n", formatBytes(s.StackInuse))
|
||||||
fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys))
|
fmt.Fprintf(conn, "stack-sys: %v\n", formatBytes(s.StackSys))
|
||||||
|
fmt.Fprintf(conn, "stack-mspan-inuse: %v\n", formatBytes(s.MSpanInuse))
|
||||||
|
fmt.Fprintf(conn, "stack-mspan-sys: %v\n", formatBytes(s.MSpanSys))
|
||||||
|
fmt.Fprintf(conn, "stack-mcache-inuse: %v\n", formatBytes(s.MCacheInuse))
|
||||||
|
fmt.Fprintf(conn, "stack-mcache-sys: %v\n", formatBytes(s.MCacheSys))
|
||||||
|
fmt.Fprintf(conn, "other-sys: %v\n", formatBytes(s.OtherSys))
|
||||||
|
fmt.Fprintf(conn, "gc-sys: %v\n", formatBytes(s.GCSys))
|
||||||
fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC))
|
fmt.Fprintf(conn, "next-gc: when heap-alloc >= %v\n", formatBytes(s.NextGC))
|
||||||
lastGC := "-"
|
lastGC := "-"
|
||||||
if s.LastGC != 0 {
|
if s.LastGC != 0 {
|
||||||
lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC)))
|
lastGC = fmt.Sprint(time.Unix(0, int64(s.LastGC)))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(conn, "last-gc: %v\n", lastGC)
|
fmt.Fprintf(conn, "last-gc: %v\n", lastGC)
|
||||||
fmt.Fprintf(conn, "gc-pause: %v\n", time.Duration(s.PauseTotalNs))
|
fmt.Fprintf(conn, "gc-pause-total: %v\n", time.Duration(s.PauseTotalNs))
|
||||||
|
fmt.Fprintf(conn, "gc-pause: %v\n", s.PauseNs[(s.NumGC+255)%256])
|
||||||
fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC)
|
fmt.Fprintf(conn, "num-gc: %v\n", s.NumGC)
|
||||||
fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC)
|
fmt.Fprintf(conn, "enable-gc: %v\n", s.EnableGC)
|
||||||
fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC)
|
fmt.Fprintf(conn, "debug-gc: %v\n", s.DebugGC)
|
||||||
|
@ -232,6 +249,12 @@ func handle(conn io.Writer, msg []byte) error {
|
||||||
trace.Start(conn)
|
trace.Start(conn)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
trace.Stop()
|
trace.Stop()
|
||||||
|
case signal.SetGCPercent:
|
||||||
|
perc, err := binary.ReadVarint(bufio.NewReader(conn))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(conn, "New GC percent set to %v. Previous value was %v.\n", perc, debug.SetGCPercent(int(perc)))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// Copyright 2017 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,7 +15,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const gopsConfigDirEnvKey = "GOPS_CONFIG_DIR"
|
||||||
|
|
||||||
func ConfigDir() (string, error) {
|
func ConfigDir() (string, error) {
|
||||||
|
if configDir := os.Getenv(gopsConfigDirEnvKey); configDir != "" {
|
||||||
|
return configDir, nil
|
||||||
|
}
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
return filepath.Join(os.Getenv("APPDATA"), "gops"), nil
|
return filepath.Join(os.Getenv("APPDATA"), "gops"), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,4 +32,7 @@ const (
|
||||||
|
|
||||||
// BinaryDump returns running binary file.
|
// BinaryDump returns running binary file.
|
||||||
BinaryDump = byte(0x9)
|
BinaryDump = byte(0x9)
|
||||||
|
|
||||||
|
// SetGCPercent sets the garbage collection target percentage.
|
||||||
|
SetGCPercent = byte(0x10)
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,13 +18,9 @@ var invalidPath = errors.New("schema: invalid path")
|
||||||
func newCache() *cache {
|
func newCache() *cache {
|
||||||
c := cache{
|
c := cache{
|
||||||
m: make(map[reflect.Type]*structInfo),
|
m: make(map[reflect.Type]*structInfo),
|
||||||
conv: make(map[reflect.Kind]Converter),
|
|
||||||
regconv: make(map[reflect.Type]Converter),
|
regconv: make(map[reflect.Type]Converter),
|
||||||
tag: "schema",
|
tag: "schema",
|
||||||
}
|
}
|
||||||
for k, v := range converters {
|
|
||||||
c.conv[k] = v
|
|
||||||
}
|
|
||||||
return &c
|
return &c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,11 +28,15 @@ func newCache() *cache {
|
||||||
type cache struct {
|
type cache struct {
|
||||||
l sync.RWMutex
|
l sync.RWMutex
|
||||||
m map[reflect.Type]*structInfo
|
m map[reflect.Type]*structInfo
|
||||||
conv map[reflect.Kind]Converter
|
|
||||||
regconv map[reflect.Type]Converter
|
regconv map[reflect.Type]Converter
|
||||||
tag string
|
tag string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// registerConverter registers a converter function for a custom type.
|
||||||
|
func (c *cache) registerConverter(value interface{}, converterFunc Converter) {
|
||||||
|
c.regconv[reflect.TypeOf(value)] = converterFunc
|
||||||
|
}
|
||||||
|
|
||||||
// parsePath parses a path in dotted notation verifying that it is a valid
|
// parsePath parses a path in dotted notation verifying that it is a valid
|
||||||
// path to a struct field.
|
// path to a struct field.
|
||||||
//
|
//
|
||||||
|
@ -63,7 +63,7 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
|
||||||
}
|
}
|
||||||
// Valid field. Append index.
|
// Valid field. Append index.
|
||||||
path = append(path, field.name)
|
path = append(path, field.name)
|
||||||
if field.ss {
|
if field.isSliceOfStructs && (!field.unmarshalerInfo.IsValid || (field.unmarshalerInfo.IsValid && field.unmarshalerInfo.IsSliceElement)) {
|
||||||
// Parse a special case: slices of structs.
|
// Parse a special case: slices of structs.
|
||||||
// i+1 must be the slice index.
|
// i+1 must be the slice index.
|
||||||
//
|
//
|
||||||
|
@ -142,7 +142,7 @@ func (c *cache) create(t reflect.Type, info *structInfo) *structInfo {
|
||||||
c.create(ft, info)
|
c.create(ft, info)
|
||||||
for _, fi := range info.fields[bef:len(info.fields)] {
|
for _, fi := range info.fields[bef:len(info.fields)] {
|
||||||
// exclude required check because duplicated to embedded field
|
// exclude required check because duplicated to embedded field
|
||||||
fi.required = false
|
fi.isRequired = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,6 +162,7 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) {
|
||||||
// First let's get the basic type.
|
// First let's get the basic type.
|
||||||
isSlice, isStruct := false, false
|
isSlice, isStruct := false, false
|
||||||
ft := field.Type
|
ft := field.Type
|
||||||
|
m := isTextUnmarshaler(reflect.Zero(ft))
|
||||||
if ft.Kind() == reflect.Ptr {
|
if ft.Kind() == reflect.Ptr {
|
||||||
ft = ft.Elem()
|
ft = ft.Elem()
|
||||||
}
|
}
|
||||||
|
@ -178,29 +179,26 @@ func (c *cache) createField(field reflect.StructField, info *structInfo) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if isStruct = ft.Kind() == reflect.Struct; !isStruct {
|
if isStruct = ft.Kind() == reflect.Struct; !isStruct {
|
||||||
if conv := c.converter(ft); conv == nil {
|
if c.converter(ft) == nil && builtinConverters[ft.Kind()] == nil {
|
||||||
// Type is not supported.
|
// Type is not supported.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.fields = append(info.fields, &fieldInfo{
|
info.fields = append(info.fields, &fieldInfo{
|
||||||
typ: field.Type,
|
typ: field.Type,
|
||||||
name: field.Name,
|
name: field.Name,
|
||||||
ss: isSlice && isStruct,
|
alias: alias,
|
||||||
alias: alias,
|
unmarshalerInfo: m,
|
||||||
anon: field.Anonymous,
|
isSliceOfStructs: isSlice && isStruct,
|
||||||
required: options.Contains("required"),
|
isAnonymous: field.Anonymous,
|
||||||
|
isRequired: options.Contains("required"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// converter returns the converter for a type.
|
// converter returns the converter for a type.
|
||||||
func (c *cache) converter(t reflect.Type) Converter {
|
func (c *cache) converter(t reflect.Type) Converter {
|
||||||
conv := c.regconv[t]
|
return c.regconv[t]
|
||||||
if conv == nil {
|
|
||||||
conv = c.conv[t.Kind()]
|
|
||||||
}
|
|
||||||
return conv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
@ -219,12 +217,18 @@ func (i *structInfo) get(alias string) *fieldInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
type fieldInfo struct {
|
type fieldInfo struct {
|
||||||
typ reflect.Type
|
typ reflect.Type
|
||||||
name string // field name in the struct.
|
// name is the field name in the struct.
|
||||||
ss bool // true if this is a slice of structs.
|
name string
|
||||||
alias string
|
alias string
|
||||||
anon bool // is an embedded field
|
// unmarshalerInfo contains information regarding the
|
||||||
required bool // tag option
|
// encoding.TextUnmarshaler implementation of the field type.
|
||||||
|
unmarshalerInfo unmarshaler
|
||||||
|
// isSliceOfStructs indicates if the field type is a slice of structs.
|
||||||
|
isSliceOfStructs bool
|
||||||
|
// isAnonymous indicates whether the field is embedded in the struct.
|
||||||
|
isAnonymous bool
|
||||||
|
isRequired bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type pathPart struct {
|
type pathPart struct {
|
||||||
|
|
|
@ -30,7 +30,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Default converters for basic types.
|
// Default converters for basic types.
|
||||||
var converters = map[reflect.Kind]Converter{
|
var builtinConverters = map[reflect.Kind]Converter{
|
||||||
boolType: convertBool,
|
boolType: convertBool,
|
||||||
float32Type: convertFloat32,
|
float32Type: convertFloat32,
|
||||||
float64Type: convertFloat64,
|
float64Type: convertFloat64,
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) {
|
||||||
|
|
||||||
// RegisterConverter registers a converter function for a custom type.
|
// RegisterConverter registers a converter function for a custom type.
|
||||||
func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
|
func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
|
||||||
d.cache.regconv[reflect.TypeOf(value)] = converterFunc
|
d.cache.registerConverter(value, converterFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode decodes a map[string][]string to a struct.
|
// Decode decodes a map[string][]string to a struct.
|
||||||
|
@ -90,7 +90,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
|
||||||
return d.checkRequired(t, src, "")
|
return d.checkRequired(t, src, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkRequired checks whether requred field empty
|
// checkRequired checks whether required fields are empty
|
||||||
//
|
//
|
||||||
// check type t recursively if t has struct fields, and prefix is same as parsePath: in dotted notation
|
// check type t recursively if t has struct fields, and prefix is same as parsePath: in dotted notation
|
||||||
//
|
//
|
||||||
|
@ -106,7 +106,7 @@ func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix
|
||||||
if f.typ.Kind() == reflect.Struct {
|
if f.typ.Kind() == reflect.Struct {
|
||||||
err := d.checkRequired(f.typ, src, prefix+f.alias+".")
|
err := d.checkRequired(f.typ, src, prefix+f.alias+".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !f.anon {
|
if !f.isAnonymous {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// check embedded parent field.
|
// check embedded parent field.
|
||||||
|
@ -116,7 +116,7 @@ func (d *Decoder) checkRequired(t reflect.Type, src map[string][]string, prefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if f.required {
|
if f.isRequired {
|
||||||
key := f.alias
|
key := f.alias
|
||||||
if prefix != "" {
|
if prefix != "" {
|
||||||
key = prefix + key
|
key = prefix + key
|
||||||
|
@ -153,7 +153,6 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
}
|
}
|
||||||
v = v.FieldByName(name)
|
v = v.FieldByName(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't even bother for unexported fields.
|
// Don't even bother for unexported fields.
|
||||||
if !v.CanSet() {
|
if !v.CanSet() {
|
||||||
return nil
|
return nil
|
||||||
|
@ -185,7 +184,8 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
|
|
||||||
// Get the converter early in case there is one for a slice type.
|
// Get the converter early in case there is one for a slice type.
|
||||||
conv := d.cache.converter(t)
|
conv := d.cache.converter(t)
|
||||||
if conv == nil && t.Kind() == reflect.Slice {
|
m := isTextUnmarshaler(v)
|
||||||
|
if conv == nil && t.Kind() == reflect.Slice && m.IsSliceElement {
|
||||||
var items []reflect.Value
|
var items []reflect.Value
|
||||||
elemT := t.Elem()
|
elemT := t.Elem()
|
||||||
isPtrElem := elemT.Kind() == reflect.Ptr
|
isPtrElem := elemT.Kind() == reflect.Ptr
|
||||||
|
@ -196,9 +196,12 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
// Try to get a converter for the element type.
|
// Try to get a converter for the element type.
|
||||||
conv := d.cache.converter(elemT)
|
conv := d.cache.converter(elemT)
|
||||||
if conv == nil {
|
if conv == nil {
|
||||||
// As we are not dealing with slice of structs here, we don't need to check if the type
|
conv = builtinConverters[elemT.Kind()]
|
||||||
// implements TextUnmarshaler interface
|
if conv == nil {
|
||||||
return fmt.Errorf("schema: converter not found for %v", elemT)
|
// As we are not dealing with slice of structs here, we don't need to check if the type
|
||||||
|
// implements TextUnmarshaler interface
|
||||||
|
return fmt.Errorf("schema: converter not found for %v", elemT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, value := range values {
|
for key, value := range values {
|
||||||
|
@ -206,6 +209,26 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
if d.zeroEmpty {
|
if d.zeroEmpty {
|
||||||
items = append(items, reflect.Zero(elemT))
|
items = append(items, reflect.Zero(elemT))
|
||||||
}
|
}
|
||||||
|
} else if m.IsValid {
|
||||||
|
u := reflect.New(elemT)
|
||||||
|
if m.IsSliceElementPtr {
|
||||||
|
u = reflect.New(reflect.PtrTo(elemT).Elem())
|
||||||
|
}
|
||||||
|
if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(value)); err != nil {
|
||||||
|
return ConversionError{
|
||||||
|
Key: path,
|
||||||
|
Type: t,
|
||||||
|
Index: key,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.IsSliceElementPtr {
|
||||||
|
items = append(items, u.Elem().Addr())
|
||||||
|
} else if u.Kind() == reflect.Ptr {
|
||||||
|
items = append(items, u.Elem())
|
||||||
|
} else {
|
||||||
|
items = append(items, u)
|
||||||
|
}
|
||||||
} else if item := conv(value); item.IsValid() {
|
} else if item := conv(value); item.IsValid() {
|
||||||
if isPtrElem {
|
if isPtrElem {
|
||||||
ptr := reflect.New(elemT)
|
ptr := reflect.New(elemT)
|
||||||
|
@ -260,11 +283,45 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
val = values[len(values)-1]
|
val = values[len(values)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if val == "" {
|
if conv != nil {
|
||||||
|
if value := conv(val); value.IsValid() {
|
||||||
|
v.Set(value.Convert(t))
|
||||||
|
} else {
|
||||||
|
return ConversionError{
|
||||||
|
Key: path,
|
||||||
|
Type: t,
|
||||||
|
Index: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if m.IsValid {
|
||||||
|
if m.IsPtr {
|
||||||
|
u := reflect.New(v.Type())
|
||||||
|
if err := u.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(val)); err != nil {
|
||||||
|
return ConversionError{
|
||||||
|
Key: path,
|
||||||
|
Type: t,
|
||||||
|
Index: -1,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v.Set(reflect.Indirect(u))
|
||||||
|
} else {
|
||||||
|
// If the value implements the encoding.TextUnmarshaler interface
|
||||||
|
// apply UnmarshalText as the converter
|
||||||
|
if err := m.Unmarshaler.UnmarshalText([]byte(val)); err != nil {
|
||||||
|
return ConversionError{
|
||||||
|
Key: path,
|
||||||
|
Type: t,
|
||||||
|
Index: -1,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if val == "" {
|
||||||
if d.zeroEmpty {
|
if d.zeroEmpty {
|
||||||
v.Set(reflect.Zero(t))
|
v.Set(reflect.Zero(t))
|
||||||
}
|
}
|
||||||
} else if conv != nil {
|
} else if conv := builtinConverters[t.Kind()]; conv != nil {
|
||||||
if value := conv(val); value.IsValid() {
|
if value := conv(val); value.IsValid() {
|
||||||
v.Set(value.Convert(t))
|
v.Set(value.Convert(t))
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,31 +332,71 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// When there's no registered conversion for the custom type, we will check if the type
|
return fmt.Errorf("schema: converter not found for %v", t)
|
||||||
// implements the TextUnmarshaler interface. As the UnmarshalText function should be applied
|
|
||||||
// to the pointer of the type, we convert the value to pointer.
|
|
||||||
if v.CanAddr() {
|
|
||||||
v = v.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
if u, ok := v.Interface().(encoding.TextUnmarshaler); ok {
|
|
||||||
if err := u.UnmarshalText([]byte(val)); err != nil {
|
|
||||||
return ConversionError{
|
|
||||||
Key: path,
|
|
||||||
Type: t,
|
|
||||||
Index: -1,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return fmt.Errorf("schema: converter not found for %v", t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isTextUnmarshaler(v reflect.Value) unmarshaler {
|
||||||
|
// Create a new unmarshaller instance
|
||||||
|
m := unmarshaler{}
|
||||||
|
if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
// As the UnmarshalText function should be applied to the pointer of the
|
||||||
|
// type, we check that type to see if it implements the necessary
|
||||||
|
// method.
|
||||||
|
if m.Unmarshaler, m.IsValid = reflect.New(v.Type()).Interface().(encoding.TextUnmarshaler); m.IsValid {
|
||||||
|
m.IsPtr = true
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// if v is []T or *[]T create new T
|
||||||
|
t := v.Type()
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
if t.Kind() == reflect.Slice {
|
||||||
|
// Check if the slice implements encoding.TextUnmarshaller
|
||||||
|
if m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler); m.IsValid {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
// If t is a pointer slice, check if its elements implement
|
||||||
|
// encoding.TextUnmarshaler
|
||||||
|
m.IsSliceElement = true
|
||||||
|
if t = t.Elem(); t.Kind() == reflect.Ptr {
|
||||||
|
t = reflect.PtrTo(t.Elem())
|
||||||
|
v = reflect.Zero(t)
|
||||||
|
m.IsSliceElementPtr = true
|
||||||
|
m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
v = reflect.New(t)
|
||||||
|
m.Unmarshaler, m.IsValid = v.Interface().(encoding.TextUnmarshaler)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextUnmarshaler helpers ----------------------------------------------------
|
||||||
|
// unmarshaller contains information about a TextUnmarshaler type
|
||||||
|
type unmarshaler struct {
|
||||||
|
Unmarshaler encoding.TextUnmarshaler
|
||||||
|
// IsValid indicates whether the resolved type indicated by the other
|
||||||
|
// flags implements the encoding.TextUnmarshaler interface.
|
||||||
|
IsValid bool
|
||||||
|
// IsPtr indicates that the resolved type is the pointer of the original
|
||||||
|
// type.
|
||||||
|
IsPtr bool
|
||||||
|
// IsSliceElement indicates that the resolved type is a slice element of
|
||||||
|
// the original type.
|
||||||
|
IsSliceElement bool
|
||||||
|
// IsSliceElementPtr indicates that the resolved type is a pointer to a
|
||||||
|
// slice element of the original type.
|
||||||
|
IsSliceElementPtr bool
|
||||||
|
}
|
||||||
|
|
||||||
// Errors ---------------------------------------------------------------------
|
// Errors ---------------------------------------------------------------------
|
||||||
|
|
||||||
// ConversionError stores information about a failed conversion.
|
// ConversionError stores information about a failed conversion.
|
||||||
|
|
|
@ -24,7 +24,7 @@ The basic usage is really simple. Given this struct:
|
||||||
|
|
||||||
This is just a simple example and it doesn't make a lot of sense to create
|
This is just a simple example and it doesn't make a lot of sense to create
|
||||||
the map manually. Typically it will come from a http.Request object and
|
the map manually. Typically it will come from a http.Request object and
|
||||||
will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
|
will be of type url.Values, http.Request.Form, or http.Request.MultipartForm:
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
err := r.ParseForm()
|
err := r.ParseForm()
|
||||||
|
@ -45,7 +45,7 @@ will be of type url.Values: http.Request.Form or http.Request.MultipartForm:
|
||||||
}
|
}
|
||||||
|
|
||||||
Note: it is a good idea to set a Decoder instance as a package global,
|
Note: it is a good idea to set a Decoder instance as a package global,
|
||||||
because it caches meta-data about structs, and a instance can be shared safely:
|
because it caches meta-data about structs, and an instance can be shared safely:
|
||||||
|
|
||||||
var decoder = schema.NewDecoder()
|
var decoder = schema.NewDecoder()
|
||||||
|
|
||||||
|
@ -121,7 +121,7 @@ field, we could not translate multiple values to it if we did not use an
|
||||||
index for the parent struct.
|
index for the parent struct.
|
||||||
|
|
||||||
There's also the possibility to create a custom type that implements the
|
There's also the possibility to create a custom type that implements the
|
||||||
TextUnmarshaler interface, and in this case there's no need to registry
|
TextUnmarshaler interface, and in this case there's no need to register
|
||||||
a converter, like:
|
a converter, like:
|
||||||
|
|
||||||
type Person struct {
|
type Person struct {
|
||||||
|
|
|
@ -40,6 +40,34 @@ func (e *Encoder) SetAliasTag(tag string) {
|
||||||
e.cache.tag = tag
|
e.cache.tag = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// isValidStructPointer test if input value is a valid struct pointer.
|
||||||
|
func isValidStructPointer(v reflect.Value) bool {
|
||||||
|
return v.Type().Kind() == reflect.Ptr && v.Elem().IsValid() && v.Elem().Type().Kind() == reflect.Struct
|
||||||
|
}
|
||||||
|
|
||||||
|
func isZero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Func:
|
||||||
|
case reflect.Map, reflect.Slice:
|
||||||
|
return v.IsNil() || v.Len() == 0
|
||||||
|
case reflect.Array:
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
z = z && isZero(v.Index(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
case reflect.Struct:
|
||||||
|
z := true
|
||||||
|
for i := 0; i < v.NumField(); i++ {
|
||||||
|
z = z && isZero(v.Field(i))
|
||||||
|
}
|
||||||
|
return z
|
||||||
|
}
|
||||||
|
// Compare other types directly:
|
||||||
|
z := reflect.Zero(v.Type())
|
||||||
|
return v.Interface() == z.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
|
func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
|
||||||
if v.Kind() == reflect.Ptr {
|
if v.Kind() == reflect.Ptr {
|
||||||
v = v.Elem()
|
v = v.Elem()
|
||||||
|
@ -57,8 +85,9 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.Field(i).Type().Kind() == reflect.Struct {
|
// Encode struct pointer types if the field is a valid pointer and a struct.
|
||||||
e.encode(v.Field(i), dst)
|
if isValidStructPointer(v.Field(i)) {
|
||||||
|
e.encode(v.Field(i).Elem(), dst)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +96,7 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
|
||||||
// Encode non-slice types and custom implementations immediately.
|
// Encode non-slice types and custom implementations immediately.
|
||||||
if encFunc != nil {
|
if encFunc != nil {
|
||||||
value := encFunc(v.Field(i))
|
value := encFunc(v.Field(i))
|
||||||
if value == "" && opts.Contains("omitempty") {
|
if opts.Contains("omitempty") && isZero(v.Field(i)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +104,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if v.Field(i).Type().Kind() == reflect.Struct {
|
||||||
|
e.encode(v.Field(i), dst)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if v.Field(i).Type().Kind() == reflect.Slice {
|
if v.Field(i).Type().Kind() == reflect.Slice {
|
||||||
encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc)
|
encFunc = typeEncoder(v.Field(i).Type().Elem(), e.regenc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,9 @@ type TwoQueueCache struct {
|
||||||
size int
|
size int
|
||||||
recentSize int
|
recentSize int
|
||||||
|
|
||||||
recent *simplelru.LRU
|
recent simplelru.LRUCache
|
||||||
frequent *simplelru.LRU
|
frequent simplelru.LRUCache
|
||||||
recentEvict *simplelru.LRU
|
recentEvict simplelru.LRUCache
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +84,8 @@ func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCa
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
|
// Get looks up a key's value from the cache.
|
||||||
|
func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
|
@ -105,6 +106,7 @@ func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds a value to the cache.
|
||||||
func (c *TwoQueueCache) Add(key, value interface{}) {
|
func (c *TwoQueueCache) Add(key, value interface{}) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
@ -160,12 +162,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
|
||||||
c.frequent.RemoveOldest()
|
c.frequent.RemoveOldest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Len returns the number of items in the cache.
|
||||||
func (c *TwoQueueCache) Len() int {
|
func (c *TwoQueueCache) Len() int {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
return c.recent.Len() + c.frequent.Len()
|
return c.recent.Len() + c.frequent.Len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keys returns a slice of the keys in the cache.
|
||||||
|
// The frequently used keys are first in the returned slice.
|
||||||
func (c *TwoQueueCache) Keys() []interface{} {
|
func (c *TwoQueueCache) Keys() []interface{} {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
|
@ -174,6 +179,7 @@ func (c *TwoQueueCache) Keys() []interface{} {
|
||||||
return append(k1, k2...)
|
return append(k1, k2...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove removes the provided key from the cache.
|
||||||
func (c *TwoQueueCache) Remove(key interface{}) {
|
func (c *TwoQueueCache) Remove(key interface{}) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
@ -188,6 +194,7 @@ func (c *TwoQueueCache) Remove(key interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Purge is used to completely clear the cache.
|
||||||
func (c *TwoQueueCache) Purge() {
|
func (c *TwoQueueCache) Purge() {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
@ -196,13 +203,17 @@ func (c *TwoQueueCache) Purge() {
|
||||||
c.recentEvict.Purge()
|
c.recentEvict.Purge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Contains is used to check if the cache contains a key
|
||||||
|
// without updating recency or frequency.
|
||||||
func (c *TwoQueueCache) Contains(key interface{}) bool {
|
func (c *TwoQueueCache) Contains(key interface{}) bool {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
return c.frequent.Contains(key) || c.recent.Contains(key)
|
return c.frequent.Contains(key) || c.recent.Contains(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) {
|
// Peek is used to inspect the cache value of a key
|
||||||
|
// without updating recency or frequency.
|
||||||
|
func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
if val, ok := c.frequent.Peek(key); ok {
|
if val, ok := c.frequent.Peek(key); ok {
|
||||||
|
|
|
@ -18,11 +18,11 @@ type ARCCache struct {
|
||||||
size int // Size is the total capacity of the cache
|
size int // Size is the total capacity of the cache
|
||||||
p int // P is the dynamic preference towards T1 or T2
|
p int // P is the dynamic preference towards T1 or T2
|
||||||
|
|
||||||
t1 *simplelru.LRU // T1 is the LRU for recently accessed items
|
t1 simplelru.LRUCache // T1 is the LRU for recently accessed items
|
||||||
b1 *simplelru.LRU // B1 is the LRU for evictions from t1
|
b1 simplelru.LRUCache // B1 is the LRU for evictions from t1
|
||||||
|
|
||||||
t2 *simplelru.LRU // T2 is the LRU for frequently accessed items
|
t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items
|
||||||
b2 *simplelru.LRU // B2 is the LRU for evictions from t2
|
b2 simplelru.LRUCache // B2 is the LRU for evictions from t2
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
@ -60,11 +60,11 @@ func NewARC(size int) (*ARCCache, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get looks up a key's value from the cache.
|
// Get looks up a key's value from the cache.
|
||||||
func (c *ARCCache) Get(key interface{}) (interface{}, bool) {
|
func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
// Ff the value is contained in T1 (recent), then
|
// If the value is contained in T1 (recent), then
|
||||||
// promote it to T2 (frequent)
|
// promote it to T2 (frequent)
|
||||||
if val, ok := c.t1.Peek(key); ok {
|
if val, ok := c.t1.Peek(key); ok {
|
||||||
c.t1.Remove(key)
|
c.t1.Remove(key)
|
||||||
|
@ -153,7 +153,7 @@ func (c *ARCCache) Add(key, value interface{}) {
|
||||||
// Remove from B2
|
// Remove from B2
|
||||||
c.b2.Remove(key)
|
c.b2.Remove(key)
|
||||||
|
|
||||||
// Add the key to the frequntly used list
|
// Add the key to the frequently used list
|
||||||
c.t2.Add(key, value)
|
c.t2.Add(key, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ func (c *ARCCache) Contains(key interface{}) bool {
|
||||||
|
|
||||||
// Peek is used to inspect the cache value of a key
|
// Peek is used to inspect the cache value of a key
|
||||||
// without updating recency or frequency.
|
// without updating recency or frequency.
|
||||||
func (c *ARCCache) Peek(key interface{}) (interface{}, bool) {
|
func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
if val, ok := c.t1.Peek(key); ok {
|
if val, ok := c.t1.Peek(key); ok {
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
// Package lru provides three different LRU caches of varying sophistication.
|
||||||
|
//
|
||||||
|
// Cache is a simple LRU cache. It is based on the
|
||||||
|
// LRU implementation in groupcache:
|
||||||
|
// https://github.com/golang/groupcache/tree/master/lru
|
||||||
|
//
|
||||||
|
// TwoQueueCache tracks frequently used and recently used entries separately.
|
||||||
|
// This avoids a burst of accesses from taking out frequently used entries,
|
||||||
|
// at the cost of about 2x computational overhead and some extra bookkeeping.
|
||||||
|
//
|
||||||
|
// ARCCache is an adaptive replacement cache. It tracks recent evictions as
|
||||||
|
// well as recent usage in both the frequent and recent caches. Its
|
||||||
|
// computational overhead is comparable to TwoQueueCache, but the memory
|
||||||
|
// overhead is linear with the size of the cache.
|
||||||
|
//
|
||||||
|
// ARC has been patented by IBM, so do not use it if that is problematic for
|
||||||
|
// your program.
|
||||||
|
//
|
||||||
|
// All caches in this package take locks while operating, and are therefore
|
||||||
|
// thread-safe for consumers.
|
||||||
|
package lru
|
|
@ -0,0 +1 @@
|
||||||
|
module github.com/hashicorp/golang-lru
|
|
@ -1,6 +1,3 @@
|
||||||
// This package provides a simple LRU cache. It is based on the
|
|
||||||
// LRU implementation in groupcache:
|
|
||||||
// https://github.com/golang/groupcache/tree/master/lru
|
|
||||||
package lru
|
package lru
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -11,11 +8,11 @@ import (
|
||||||
|
|
||||||
// Cache is a thread-safe fixed size LRU cache.
|
// Cache is a thread-safe fixed size LRU cache.
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
lru *simplelru.LRU
|
lru simplelru.LRUCache
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates an LRU of the given size
|
// New creates an LRU of the given size.
|
||||||
func New(size int) (*Cache, error) {
|
func New(size int) (*Cache, error) {
|
||||||
return NewWithEvict(size, nil)
|
return NewWithEvict(size, nil)
|
||||||
}
|
}
|
||||||
|
@ -33,7 +30,7 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{}))
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge is used to completely clear the cache
|
// Purge is used to completely clear the cache.
|
||||||
func (c *Cache) Purge() {
|
func (c *Cache) Purge() {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
c.lru.Purge()
|
c.lru.Purge()
|
||||||
|
@ -41,30 +38,30 @@ func (c *Cache) Purge() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||||
func (c *Cache) Add(key, value interface{}) bool {
|
func (c *Cache) Add(key, value interface{}) (evicted bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
return c.lru.Add(key, value)
|
return c.lru.Add(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get looks up a key's value from the cache.
|
// Get looks up a key's value from the cache.
|
||||||
func (c *Cache) Get(key interface{}) (interface{}, bool) {
|
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
return c.lru.Get(key)
|
return c.lru.Get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a key is in the cache, without updating the recent-ness
|
// Contains checks if a key is in the cache, without updating the
|
||||||
// or deleting it for being stale.
|
// recent-ness or deleting it for being stale.
|
||||||
func (c *Cache) Contains(key interface{}) bool {
|
func (c *Cache) Contains(key interface{}) bool {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
return c.lru.Contains(key)
|
return c.lru.Contains(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the key value (or undefined if not found) without updating
|
// Peek returns the key value (or undefined if not found) without updating
|
||||||
// the "recently used"-ness of the key.
|
// the "recently used"-ness of the key.
|
||||||
func (c *Cache) Peek(key interface{}) (interface{}, bool) {
|
func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
|
||||||
c.lock.RLock()
|
c.lock.RLock()
|
||||||
defer c.lock.RUnlock()
|
defer c.lock.RUnlock()
|
||||||
return c.lru.Peek(key)
|
return c.lru.Peek(key)
|
||||||
|
@ -73,16 +70,15 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) {
|
||||||
// ContainsOrAdd checks if a key is in the cache without updating the
|
// ContainsOrAdd checks if a key is in the cache without updating the
|
||||||
// recent-ness or deleting it for being stale, and if not, adds the value.
|
// recent-ness or deleting it for being stale, and if not, adds the value.
|
||||||
// Returns whether found and whether an eviction occurred.
|
// Returns whether found and whether an eviction occurred.
|
||||||
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) {
|
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
defer c.lock.Unlock()
|
||||||
|
|
||||||
if c.lru.Contains(key) {
|
if c.lru.Contains(key) {
|
||||||
return true, false
|
return true, false
|
||||||
} else {
|
|
||||||
evict := c.lru.Add(key, value)
|
|
||||||
return false, evict
|
|
||||||
}
|
}
|
||||||
|
evicted = c.lru.Add(key, value)
|
||||||
|
return false, evicted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes the provided key from the cache.
|
// Remove removes the provided key from the cache.
|
||||||
|
|
|
@ -36,7 +36,7 @@ func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Purge is used to completely clear the cache
|
// Purge is used to completely clear the cache.
|
||||||
func (c *LRU) Purge() {
|
func (c *LRU) Purge() {
|
||||||
for k, v := range c.items {
|
for k, v := range c.items {
|
||||||
if c.onEvict != nil {
|
if c.onEvict != nil {
|
||||||
|
@ -48,7 +48,7 @@ func (c *LRU) Purge() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a value to the cache. Returns true if an eviction occurred.
|
// Add adds a value to the cache. Returns true if an eviction occurred.
|
||||||
func (c *LRU) Add(key, value interface{}) bool {
|
func (c *LRU) Add(key, value interface{}) (evicted bool) {
|
||||||
// Check for existing item
|
// Check for existing item
|
||||||
if ent, ok := c.items[key]; ok {
|
if ent, ok := c.items[key]; ok {
|
||||||
c.evictList.MoveToFront(ent)
|
c.evictList.MoveToFront(ent)
|
||||||
|
@ -78,17 +78,18 @@ func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if a key is in the cache, without updating the recent-ness
|
// Contains checks if a key is in the cache, without updating the recent-ness
|
||||||
// or deleting it for being stale.
|
// or deleting it for being stale.
|
||||||
func (c *LRU) Contains(key interface{}) (ok bool) {
|
func (c *LRU) Contains(key interface{}) (ok bool) {
|
||||||
_, ok = c.items[key]
|
_, ok = c.items[key]
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the key value (or undefined if not found) without updating
|
// Peek returns the key value (or undefined if not found) without updating
|
||||||
// the "recently used"-ness of the key.
|
// the "recently used"-ness of the key.
|
||||||
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
|
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
|
||||||
if ent, ok := c.items[key]; ok {
|
var ent *list.Element
|
||||||
|
if ent, ok = c.items[key]; ok {
|
||||||
return ent.Value.(*entry).value, true
|
return ent.Value.(*entry).value, true
|
||||||
}
|
}
|
||||||
return nil, ok
|
return nil, ok
|
||||||
|
@ -96,7 +97,7 @@ func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
|
||||||
|
|
||||||
// Remove removes the provided key from the cache, returning if the
|
// Remove removes the provided key from the cache, returning if the
|
||||||
// key was contained.
|
// key was contained.
|
||||||
func (c *LRU) Remove(key interface{}) bool {
|
func (c *LRU) Remove(key interface{}) (present bool) {
|
||||||
if ent, ok := c.items[key]; ok {
|
if ent, ok := c.items[key]; ok {
|
||||||
c.removeElement(ent)
|
c.removeElement(ent)
|
||||||
return true
|
return true
|
||||||
|
@ -105,7 +106,7 @@ func (c *LRU) Remove(key interface{}) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOldest removes the oldest item from the cache.
|
// RemoveOldest removes the oldest item from the cache.
|
||||||
func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) {
|
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
|
||||||
ent := c.evictList.Back()
|
ent := c.evictList.Back()
|
||||||
if ent != nil {
|
if ent != nil {
|
||||||
c.removeElement(ent)
|
c.removeElement(ent)
|
||||||
|
@ -116,7 +117,7 @@ func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOldest returns the oldest entry
|
// GetOldest returns the oldest entry
|
||||||
func (c *LRU) GetOldest() (interface{}, interface{}, bool) {
|
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) {
|
||||||
ent := c.evictList.Back()
|
ent := c.evictList.Back()
|
||||||
if ent != nil {
|
if ent != nil {
|
||||||
kv := ent.Value.(*entry)
|
kv := ent.Value.(*entry)
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
package simplelru
|
||||||
|
|
||||||
|
// LRUCache is the interface for simple LRU cache.
|
||||||
|
type LRUCache interface {
|
||||||
|
// Adds a value to the cache, returns true if an eviction occurred and
|
||||||
|
// updates the "recently used"-ness of the key.
|
||||||
|
Add(key, value interface{}) bool
|
||||||
|
|
||||||
|
// Returns key's value from the cache and
|
||||||
|
// updates the "recently used"-ness of the key. #value, isFound
|
||||||
|
Get(key interface{}) (value interface{}, ok bool)
|
||||||
|
|
||||||
|
// Check if a key exsists in cache without updating the recent-ness.
|
||||||
|
Contains(key interface{}) (ok bool)
|
||||||
|
|
||||||
|
// Returns key's value without updating the "recently used"-ness of the key.
|
||||||
|
Peek(key interface{}) (value interface{}, ok bool)
|
||||||
|
|
||||||
|
// Removes a key from the cache.
|
||||||
|
Remove(key interface{}) bool
|
||||||
|
|
||||||
|
// Removes the oldest entry from cache.
|
||||||
|
RemoveOldest() (interface{}, interface{}, bool)
|
||||||
|
|
||||||
|
// Returns the oldest entry from the cache. #key, value, isFound
|
||||||
|
GetOldest() (interface{}, interface{}, bool)
|
||||||
|
|
||||||
|
// Returns a slice of the keys in the cache, from oldest to newest.
|
||||||
|
Keys() []interface{}
|
||||||
|
|
||||||
|
// Returns the number of items in the cache.
|
||||||
|
Len() int
|
||||||
|
|
||||||
|
// Clear all cache entries
|
||||||
|
Purge()
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
module github.com/hashicorp/hcl
|
||||||
|
|
||||||
|
require github.com/davecgh/go-spew v1.1.1
|
|
@ -0,0 +1,2 @@
|
||||||
|
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=
|
|
@ -205,6 +205,12 @@ func (p *Parser) objectItem() (*ast.ObjectItem, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// key=#comment
|
||||||
|
// val
|
||||||
|
if p.lineComment != nil {
|
||||||
|
o.LineComment, p.lineComment = p.lineComment, nil
|
||||||
|
}
|
||||||
|
|
||||||
// do a look-ahead for line comment
|
// do a look-ahead for line comment
|
||||||
p.scan()
|
p.scan()
|
||||||
if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
|
if len(keys) > 0 && o.Val.Pos().Line == keys[0].Pos().Line && p.lineComment != nil {
|
||||||
|
|
|
@ -252,6 +252,14 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If key and val are on different lines, treat line comments like lead comments.
|
||||||
|
if o.LineComment != nil && o.Val.Pos().Line != o.Keys[0].Pos().Line {
|
||||||
|
for _, comment := range o.LineComment.List {
|
||||||
|
buf.WriteString(comment.Text)
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for i, k := range o.Keys {
|
for i, k := range o.Keys {
|
||||||
buf.WriteString(k.Token.Text)
|
buf.WriteString(k.Token.Text)
|
||||||
buf.WriteByte(blank)
|
buf.WriteByte(blank)
|
||||||
|
@ -265,7 +273,7 @@ func (p *printer) objectItem(o *ast.ObjectItem) []byte {
|
||||||
|
|
||||||
buf.Write(p.output(o.Val))
|
buf.Write(p.output(o.Val))
|
||||||
|
|
||||||
if o.Val.Pos().Line == o.Keys[0].Pos().Line && o.LineComment != nil {
|
if o.LineComment != nil && o.Val.Pos().Line == o.Keys[0].Pos().Line {
|
||||||
buf.WriteByte(blank)
|
buf.WriteByte(blank)
|
||||||
for _, comment := range o.LineComment.List {
|
for _, comment := range o.LineComment.List {
|
||||||
buf.WriteString(comment.Text)
|
buf.WriteString(comment.Text)
|
||||||
|
@ -509,8 +517,13 @@ func (p *printer) alignedItems(items []*ast.ObjectItem) []byte {
|
||||||
|
|
||||||
// list returns the printable HCL form of an list type.
|
// list returns the printable HCL form of an list type.
|
||||||
func (p *printer) list(l *ast.ListType) []byte {
|
func (p *printer) list(l *ast.ListType) []byte {
|
||||||
|
if p.isSingleLineList(l) {
|
||||||
|
return p.singleLineList(l)
|
||||||
|
}
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
buf.WriteString("[")
|
buf.WriteString("[")
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
var longestLine int
|
var longestLine int
|
||||||
for _, item := range l.List {
|
for _, item := range l.List {
|
||||||
|
@ -523,115 +536,112 @@ func (p *printer) list(l *ast.ListType) []byte {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
insertSpaceBeforeItem := false
|
haveEmptyLine := false
|
||||||
lastHadLeadComment := false
|
|
||||||
for i, item := range l.List {
|
for i, item := range l.List {
|
||||||
// Keep track of whether this item is a heredoc since that has
|
// If we have a lead comment, then we want to write that first
|
||||||
// unique behavior.
|
leadComment := false
|
||||||
heredoc := false
|
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
|
||||||
|
leadComment = true
|
||||||
|
|
||||||
|
// Ensure an empty line before every element with a
|
||||||
|
// lead comment (except the first item in a list).
|
||||||
|
if !haveEmptyLine && i != 0 {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, comment := range lit.LeadComment.List {
|
||||||
|
buf.Write(p.indent([]byte(comment.Text)))
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// also indent each line
|
||||||
|
val := p.output(item)
|
||||||
|
curLen := len(val)
|
||||||
|
buf.Write(p.indent(val))
|
||||||
|
|
||||||
|
// if this item is a heredoc, then we output the comma on
|
||||||
|
// the next line. This is the only case this happens.
|
||||||
|
comma := []byte{','}
|
||||||
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
|
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
|
||||||
heredoc = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if item.Pos().Line != l.Lbrack.Line {
|
|
||||||
// multiline list, add newline before we add each item
|
|
||||||
buf.WriteByte(newline)
|
buf.WriteByte(newline)
|
||||||
insertSpaceBeforeItem = false
|
comma = p.indent(comma)
|
||||||
|
}
|
||||||
|
|
||||||
// If we have a lead comment, then we want to write that first
|
buf.Write(comma)
|
||||||
leadComment := false
|
|
||||||
if lit, ok := item.(*ast.LiteralType); ok && lit.LeadComment != nil {
|
|
||||||
leadComment = true
|
|
||||||
|
|
||||||
// If this isn't the first item and the previous element
|
if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
|
||||||
// didn't have a lead comment, then we need to add an extra
|
// if the next item doesn't have any comments, do not align
|
||||||
// newline to properly space things out. If it did have a
|
buf.WriteByte(blank) // align one space
|
||||||
// lead comment previously then this would be done
|
for i := 0; i < longestLine-curLen; i++ {
|
||||||
// automatically.
|
|
||||||
if i > 0 && !lastHadLeadComment {
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, comment := range lit.LeadComment.List {
|
|
||||||
buf.Write(p.indent([]byte(comment.Text)))
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// also indent each line
|
|
||||||
val := p.output(item)
|
|
||||||
curLen := len(val)
|
|
||||||
buf.Write(p.indent(val))
|
|
||||||
|
|
||||||
// if this item is a heredoc, then we output the comma on
|
|
||||||
// the next line. This is the only case this happens.
|
|
||||||
comma := []byte{','}
|
|
||||||
if heredoc {
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
comma = p.indent(comma)
|
|
||||||
}
|
|
||||||
|
|
||||||
buf.Write(comma)
|
|
||||||
|
|
||||||
if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
|
|
||||||
// if the next item doesn't have any comments, do not align
|
|
||||||
buf.WriteByte(blank) // align one space
|
|
||||||
for i := 0; i < longestLine-curLen; i++ {
|
|
||||||
buf.WriteByte(blank)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, comment := range lit.LineComment.List {
|
|
||||||
buf.WriteString(comment.Text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastItem := i == len(l.List)-1
|
|
||||||
if lastItem {
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
if leadComment && !lastItem {
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
lastHadLeadComment = leadComment
|
|
||||||
} else {
|
|
||||||
if insertSpaceBeforeItem {
|
|
||||||
buf.WriteByte(blank)
|
buf.WriteByte(blank)
|
||||||
insertSpaceBeforeItem = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output the item itself
|
for _, comment := range lit.LineComment.List {
|
||||||
// also indent each line
|
buf.WriteString(comment.Text)
|
||||||
val := p.output(item)
|
|
||||||
curLen := len(val)
|
|
||||||
buf.Write(val)
|
|
||||||
|
|
||||||
// If this is a heredoc item we always have to output a newline
|
|
||||||
// so that it parses properly.
|
|
||||||
if heredoc {
|
|
||||||
buf.WriteByte(newline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this isn't the last element, write a comma.
|
|
||||||
if i != len(l.List)-1 {
|
|
||||||
buf.WriteString(",")
|
|
||||||
insertSpaceBeforeItem = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if lit, ok := item.(*ast.LiteralType); ok && lit.LineComment != nil {
|
|
||||||
// if the next item doesn't have any comments, do not align
|
|
||||||
buf.WriteByte(blank) // align one space
|
|
||||||
for i := 0; i < longestLine-curLen; i++ {
|
|
||||||
buf.WriteByte(blank)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, comment := range lit.LineComment.List {
|
|
||||||
buf.WriteString(comment.Text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
|
||||||
|
// Ensure an empty line after every element with a
|
||||||
|
// lead comment (except the first item in a list).
|
||||||
|
haveEmptyLine = leadComment && i != len(l.List)-1
|
||||||
|
if haveEmptyLine {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteString("]")
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// isSingleLineList returns true if:
|
||||||
|
// * they were previously formatted entirely on one line
|
||||||
|
// * they consist entirely of literals
|
||||||
|
// * there are either no heredoc strings or the list has exactly one element
|
||||||
|
// * there are no line comments
|
||||||
|
func (printer) isSingleLineList(l *ast.ListType) bool {
|
||||||
|
for _, item := range l.List {
|
||||||
|
if item.Pos().Line != l.Lbrack.Line {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
lit, ok := item.(*ast.LiteralType)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lit.Token.Type == token.HEREDOC && len(l.List) != 1 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if lit.LineComment != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// singleLineList prints a simple single line list.
|
||||||
|
// For a definition of "simple", see isSingleLineList above.
|
||||||
|
func (p *printer) singleLineList(l *ast.ListType) []byte {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
|
||||||
|
buf.WriteString("[")
|
||||||
|
for i, item := range l.List {
|
||||||
|
if i != 0 {
|
||||||
|
buf.WriteString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output the item itself
|
||||||
|
buf.Write(p.output(item))
|
||||||
|
|
||||||
|
// The heredoc marker needs to be at the end of line.
|
||||||
|
if lit, ok := item.(*ast.LiteralType); ok && lit.Token.Type == token.HEREDOC {
|
||||||
|
buf.WriteByte(newline)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString("]")
|
buf.WriteString("]")
|
||||||
|
|
|
@ -74,14 +74,6 @@ func (s *Scanner) next() rune {
|
||||||
return eof
|
return eof
|
||||||
}
|
}
|
||||||
|
|
||||||
if ch == utf8.RuneError && size == 1 {
|
|
||||||
s.srcPos.Column++
|
|
||||||
s.srcPos.Offset += size
|
|
||||||
s.lastCharLen = size
|
|
||||||
s.err("illegal UTF-8 encoding")
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// remember last position
|
// remember last position
|
||||||
s.prevPos = s.srcPos
|
s.prevPos = s.srcPos
|
||||||
|
|
||||||
|
@ -89,18 +81,27 @@ func (s *Scanner) next() rune {
|
||||||
s.lastCharLen = size
|
s.lastCharLen = size
|
||||||
s.srcPos.Offset += size
|
s.srcPos.Offset += size
|
||||||
|
|
||||||
|
if ch == utf8.RuneError && size == 1 {
|
||||||
|
s.err("illegal UTF-8 encoding")
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
s.srcPos.Line++
|
s.srcPos.Line++
|
||||||
s.lastLineLen = s.srcPos.Column
|
s.lastLineLen = s.srcPos.Column
|
||||||
s.srcPos.Column = 0
|
s.srcPos.Column = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we see a null character with data left, then that is an error
|
if ch == '\x00' {
|
||||||
if ch == '\x00' && s.buf.Len() > 0 {
|
|
||||||
s.err("unexpected null character (0x00)")
|
s.err("unexpected null character (0x00)")
|
||||||
return eof
|
return eof
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ch == '\uE123' {
|
||||||
|
s.err("unicode code point U+E123 reserved for internal use")
|
||||||
|
return utf8.RuneError
|
||||||
|
}
|
||||||
|
|
||||||
// debug
|
// debug
|
||||||
// fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column)
|
// fmt.Printf("ch: %q, offset:column: %d:%d\n", ch, s.srcPos.Offset, s.srcPos.Column)
|
||||||
return ch
|
return ch
|
||||||
|
@ -432,16 +433,16 @@ func (s *Scanner) scanHeredoc() {
|
||||||
|
|
||||||
// Read the identifier
|
// Read the identifier
|
||||||
identBytes := s.src[offs : s.srcPos.Offset-s.lastCharLen]
|
identBytes := s.src[offs : s.srcPos.Offset-s.lastCharLen]
|
||||||
if len(identBytes) == 0 {
|
if len(identBytes) == 0 || (len(identBytes) == 1 && identBytes[0] == '-') {
|
||||||
s.err("zero-length heredoc anchor")
|
s.err("zero-length heredoc anchor")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var identRegexp *regexp.Regexp
|
var identRegexp *regexp.Regexp
|
||||||
if identBytes[0] == '-' {
|
if identBytes[0] == '-' {
|
||||||
identRegexp = regexp.MustCompile(fmt.Sprintf(`[[:space:]]*%s\z`, identBytes[1:]))
|
identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes[1:]))
|
||||||
} else {
|
} else {
|
||||||
identRegexp = regexp.MustCompile(fmt.Sprintf(`[[:space:]]*%s\z`, identBytes))
|
identRegexp = regexp.MustCompile(fmt.Sprintf(`^[[:space:]]*%s\r*\z`, identBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the actual string value
|
// Read the actual string value
|
||||||
|
@ -551,7 +552,7 @@ func (s *Scanner) scanDigits(ch rune, base, n int) rune {
|
||||||
s.err("illegal char escape")
|
s.err("illegal char escape")
|
||||||
}
|
}
|
||||||
|
|
||||||
if n != start {
|
if n != start && ch != eof {
|
||||||
// we scanned all digits, put the last non digit char back,
|
// we scanned all digits, put the last non digit char back,
|
||||||
// only if we read anything at all
|
// only if we read anything at all
|
||||||
s.unread()
|
s.unread()
|
||||||
|
|
|
@ -116,4 +116,4 @@ https://godoc.org/github.com/jpillora/backoff
|
||||||
|
|
||||||
#### Credits
|
#### Credits
|
||||||
|
|
||||||
Forked from some JavaScript written by [@tj](https://github.com/tj)
|
Forked from [some JavaScript](https://github.com/segmentio/backo) written by [@tj](https://github.com/tj)
|
||||||
|
|
|
@ -71,7 +71,8 @@ func (b *Backoff) ForAttempt(attempt float64) time.Duration {
|
||||||
//keep within bounds
|
//keep within bounds
|
||||||
if dur < min {
|
if dur < min {
|
||||||
return min
|
return min
|
||||||
} else if dur > max {
|
}
|
||||||
|
if dur > max {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
return dur
|
return dur
|
||||||
|
@ -86,3 +87,13 @@ func (b *Backoff) Reset() {
|
||||||
func (b *Backoff) Attempt() float64 {
|
func (b *Backoff) Attempt() float64 {
|
||||||
return b.attempt
|
return b.attempt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Copy returns a backoff with equals constraints as the original
|
||||||
|
func (b *Backoff) Copy() *Backoff {
|
||||||
|
return &Backoff{
|
||||||
|
Factor: b.Factor,
|
||||||
|
Jitter: b.Jitter,
|
||||||
|
Min: b.Min,
|
||||||
|
Max: b.Max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
|
- 1.10.x
|
||||||
- tip
|
- tip
|
||||||
install:
|
install:
|
||||||
- make dependency
|
- make dependency
|
||||||
|
|
|
@ -10,26 +10,26 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d2709f9f1f31ebcda9651b03077758c1f3a0018c"
|
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
||||||
version = "v3.0.0"
|
version = "v3.2.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/labstack/gommon"
|
name = "github.com/labstack/gommon"
|
||||||
packages = ["bytes","color","log","random"]
|
packages = ["bytes","color","log","random"]
|
||||||
revision = "1121fd3e243c202482226a7afe4dcd07ffc4139a"
|
revision = "6fe1405d73ec4bd4cd8a4ac8e2a2b2bf95d03954"
|
||||||
version = "v0.2.1"
|
version = "0.2.4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/mattn/go-colorable"
|
name = "github.com/mattn/go-colorable"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d228849504861217f796da67fae4f6e347643f15"
|
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||||
version = "v0.0.7"
|
version = "v0.0.9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/mattn/go-isatty"
|
name = "github.com/mattn/go-isatty"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
|
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||||
version = "v0.0.2"
|
version = "v0.0.3"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pmezard/go-difflib"
|
name = "github.com/pmezard/go-difflib"
|
||||||
|
@ -40,8 +40,8 @@
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/stretchr/testify"
|
name = "github.com/stretchr/testify"
|
||||||
packages = ["assert"]
|
packages = ["assert"]
|
||||||
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
||||||
version = "v1.1.4"
|
version = "v1.2.1"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -59,17 +59,17 @@
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
||||||
packages = ["acme","acme/autocert"]
|
packages = ["acme","acme/autocert"]
|
||||||
revision = "e1a4589e7d3ea14a3352255d04b6f1a418845e5e"
|
revision = "182114d582623c1caa54f73de9c7224e23a48487"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
packages = ["unix"]
|
packages = ["unix"]
|
||||||
revision = "b90f89a1e7a9c1f6b918820b3daa7f08488c8594"
|
revision = "c28acc882ebcbfbe8ce9f0f14b9ac26ee138dd51"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "5f74a2a2ba5b07475ad0faa1b4c021b973ad40b2ae749e3d94e15fe839bb440e"
|
inputs-digest = "9c7b45e80fe353405800cf01f429b3a203cfb8d4468a04c64a908e11a98ea764"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
|
|
@ -1,82 +1,37 @@
|
||||||
|
|
||||||
## Gopkg.toml example (these lines may be deleted)
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
## "metadata" defines metadata about the project that could be used by other independent
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
## systems. The metadata defined here will be ignored by dep.
|
# for detailed Gopkg.toml documentation.
|
||||||
# [metadata]
|
#
|
||||||
# key1 = "value that convey data to other systems"
|
|
||||||
# system1-data = "value that is used by a system"
|
|
||||||
# system2-data = "value that is used by another system"
|
|
||||||
|
|
||||||
## "required" lists a set of packages (not projects) that must be included in
|
|
||||||
## Gopkg.lock. This list is merged with the set of packages imported by the current
|
|
||||||
## project. Use it when your project needs a package it doesn't explicitly import -
|
|
||||||
## including "main" packages.
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
## "ignored" lists a set of packages (not projects) that are ignored when
|
#
|
||||||
## dep statically analyzes source code. Ignored packages can be in this project,
|
|
||||||
## or in a dependency.
|
|
||||||
# ignored = ["github.com/user/project/badpkg"]
|
|
||||||
|
|
||||||
## Constraints are rules for how directly imported projects
|
|
||||||
## may be incorporated into the depgraph. They are respected by
|
|
||||||
## dep whether coming from the Gopkg.toml of the current project or a dependency.
|
|
||||||
# [[constraint]]
|
# [[constraint]]
|
||||||
## Required: the root import path of the project being constrained.
|
# name = "github.com/user/project"
|
||||||
# name = "github.com/user/project"
|
# version = "1.0.0"
|
||||||
#
|
#
|
||||||
## Recommended: the version constraint to enforce for the project.
|
# [[constraint]]
|
||||||
## Only one of "branch", "version" or "revision" can be specified.
|
# name = "github.com/user/project2"
|
||||||
# version = "1.0.0"
|
# branch = "dev"
|
||||||
# branch = "master"
|
# source = "github.com/myfork/project2"
|
||||||
# revision = "abc123"
|
|
||||||
#
|
#
|
||||||
## Optional: an alternate location (URL or import path) for the project's source.
|
|
||||||
# source = "https://github.com/myfork/package.git"
|
|
||||||
#
|
|
||||||
## "metadata" defines metadata about the dependency or override that could be used
|
|
||||||
## by other independent systems. The metadata defined here will be ignored by dep.
|
|
||||||
# [metadata]
|
|
||||||
# key1 = "value that convey data to other systems"
|
|
||||||
# system1-data = "value that is used by a system"
|
|
||||||
# system2-data = "value that is used by another system"
|
|
||||||
|
|
||||||
## Overrides have the same structure as [[constraint]], but supersede all
|
|
||||||
## [[constraint]] declarations from all projects. Only [[override]] from
|
|
||||||
## the current project's are applied.
|
|
||||||
##
|
|
||||||
## Overrides are a sledgehammer. Use them only as a last resort.
|
|
||||||
# [[override]]
|
# [[override]]
|
||||||
## Required: the root import path of the project being constrained.
|
# name = "github.com/x/y"
|
||||||
# name = "github.com/user/project"
|
# version = "2.4.0"
|
||||||
#
|
|
||||||
## Optional: specifying a version constraint override will cause all other
|
|
||||||
## constraints on this project to be ignored; only the overridden constraint
|
|
||||||
## need be satisfied.
|
|
||||||
## Again, only one of "branch", "version" or "revision" can be specified.
|
|
||||||
# version = "1.0.0"
|
|
||||||
# branch = "master"
|
|
||||||
# revision = "abc123"
|
|
||||||
#
|
|
||||||
## Optional: specifying an alternate source location as an override will
|
|
||||||
## enforce that the alternate location is used for that project, regardless of
|
|
||||||
## what source location any dependent projects specify.
|
|
||||||
# source = "https://github.com/myfork/package.git"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
name = "github.com/dgrijalva/jwt-go"
|
||||||
version = "3.0.0"
|
version = "3.2.0"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/labstack/gommon"
|
name = "github.com/labstack/gommon"
|
||||||
version = "0.2.1"
|
version = "0.2.4"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/stretchr/testify"
|
name = "github.com/stretchr/testify"
|
||||||
version = "1.1.4"
|
version = "1.2.1"
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
@ -84,4 +39,4 @@
|
||||||
|
|
||||||
[[constraint]]
|
[[constraint]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/crypto"
|
name = "golang.org/x/crypto"
|
|
@ -11,3 +11,7 @@ test:
|
||||||
go test -race -coverprofile=profile.out -covermode=atomic $$d || exit 1; \
|
go test -race -coverprofile=profile.out -covermode=atomic $$d || exit 1; \
|
||||||
[ -f profile.out ] && cat profile.out >> coverage.txt && rm profile.out; \
|
[ -f profile.out ] && cat profile.out >> coverage.txt && rm profile.out; \
|
||||||
done
|
done
|
||||||
|
|
||||||
|
tag:
|
||||||
|
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
|
||||||
|
@git tag|grep -v ^v
|
||||||
|
|
|
@ -26,9 +26,13 @@
|
||||||
- Automatic TLS via Let’s Encrypt
|
- Automatic TLS via Let’s Encrypt
|
||||||
- HTTP/2 support
|
- HTTP/2 support
|
||||||
|
|
||||||
## Performance
|
## Benchmarks
|
||||||
|
|
||||||
<img src="https://api.labstack.com/chart/bar?values=20015,39584,7282,11276&labels=Static,GitHub%20API,Parse%20API,Google%20Plus%20API&x_title=Route&y_title=Time%20(ns/op)&colors=c&dpi=100">
|
Date: 2018/03/15<br>
|
||||||
|
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||||
|
Lower is better!
|
||||||
|
|
||||||
|
<img src="https://api.labstack.com/chart/bar?values=37223,55382,2985,5265|42013,59865,3350,6424&labels=Static,GitHub%20API,Parse%20API,Gplus%20API&titles=Echo,Gin&colors=lightseagreen,goldenrod&x_title=Routes&y_title=ns/op">
|
||||||
|
|
||||||
## [Guide](https://echo.labstack.com/guide)
|
## [Guide](https://echo.labstack.com/guide)
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
|
||||||
val := reflect.ValueOf(ptr).Elem()
|
val := reflect.ValueOf(ptr).Elem()
|
||||||
|
|
||||||
if typ.Kind() != reflect.Struct {
|
if typ.Kind() != reflect.Struct {
|
||||||
return errors.New("Binding element must be a struct")
|
return errors.New("binding element must be a struct")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < typ.NumField(); i++ {
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
|
|
@ -206,6 +206,13 @@ const (
|
||||||
indexPage = "index.html"
|
indexPage = "index.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *context) writeContentType(value string) {
|
||||||
|
header := c.Response().Header()
|
||||||
|
if header.Get(HeaderContentType) == "" {
|
||||||
|
header.Set(HeaderContentType, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (c *context) Request() *http.Request {
|
func (c *context) Request() *http.Request {
|
||||||
return c.request
|
return c.request
|
||||||
}
|
}
|
||||||
|
@ -430,7 +437,7 @@ func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||||
c.response.Header().Set(HeaderContentType, MIMEApplicationJavaScriptCharsetUTF8)
|
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||||
c.response.WriteHeader(code)
|
c.response.WriteHeader(code)
|
||||||
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -463,7 +470,7 @@ func (c *context) XMLPretty(code int, i interface{}, indent string) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||||
c.response.Header().Set(HeaderContentType, MIMEApplicationXMLCharsetUTF8)
|
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||||
c.response.WriteHeader(code)
|
c.response.WriteHeader(code)
|
||||||
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -473,14 +480,14 @@ func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
|
func (c *context) Blob(code int, contentType string, b []byte) (err error) {
|
||||||
c.response.Header().Set(HeaderContentType, contentType)
|
c.writeContentType(contentType)
|
||||||
c.response.WriteHeader(code)
|
c.response.WriteHeader(code)
|
||||||
_, err = c.response.Write(b)
|
_, err = c.response.Write(b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
|
func (c *context) Stream(code int, contentType string, r io.Reader) (err error) {
|
||||||
c.response.Header().Set(HeaderContentType, contentType)
|
c.writeContentType(contentType)
|
||||||
c.response.WriteHeader(code)
|
c.response.WriteHeader(code)
|
||||||
_, err = io.Copy(c.response, r)
|
_, err = io.Copy(c.response, r)
|
||||||
return
|
return
|
||||||
|
@ -509,18 +516,17 @@ func (c *context) File(file string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Attachment(file, name string) (err error) {
|
func (c *context) Attachment(file, name string) error {
|
||||||
return c.contentDisposition(file, name, "attachment")
|
return c.contentDisposition(file, name, "attachment")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) Inline(file, name string) (err error) {
|
func (c *context) Inline(file, name string) error {
|
||||||
return c.contentDisposition(file, name, "inline")
|
return c.contentDisposition(file, name, "inline")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) contentDisposition(file, name, dispositionType string) (err error) {
|
func (c *context) contentDisposition(file, name, dispositionType string) error {
|
||||||
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
|
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf("%s; filename=%q", dispositionType, name))
|
||||||
c.File(file)
|
return c.File(file)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) NoContent(code int) error {
|
func (c *context) NoContent(code int) error {
|
||||||
|
|
|
@ -38,6 +38,7 @@ package echo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
stdContext "context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -45,6 +46,7 @@ import (
|
||||||
stdLog "log"
|
stdLog "log"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -81,8 +83,7 @@ type (
|
||||||
Binder Binder
|
Binder Binder
|
||||||
Validator Validator
|
Validator Validator
|
||||||
Renderer Renderer
|
Renderer Renderer
|
||||||
// Mutex sync.RWMutex
|
Logger Logger
|
||||||
Logger Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route contains a handler and information for matching against requests.
|
// Route contains a handler and information for matching against requests.
|
||||||
|
@ -94,9 +95,9 @@ type (
|
||||||
|
|
||||||
// HTTPError represents an error that occurred while handling a request.
|
// HTTPError represents an error that occurred while handling a request.
|
||||||
HTTPError struct {
|
HTTPError struct {
|
||||||
Code int
|
Code int
|
||||||
Message interface{}
|
Message interface{}
|
||||||
Inner error // Stores the error returned by an external dependency
|
Internal error // Stores the error returned by an external dependency
|
||||||
}
|
}
|
||||||
|
|
||||||
// MiddlewareFunc defines a function to process middleware.
|
// MiddlewareFunc defines a function to process middleware.
|
||||||
|
@ -129,15 +130,16 @@ type (
|
||||||
|
|
||||||
// HTTP methods
|
// HTTP methods
|
||||||
const (
|
const (
|
||||||
CONNECT = "CONNECT"
|
CONNECT = "CONNECT"
|
||||||
DELETE = "DELETE"
|
DELETE = "DELETE"
|
||||||
GET = "GET"
|
GET = "GET"
|
||||||
HEAD = "HEAD"
|
HEAD = "HEAD"
|
||||||
OPTIONS = "OPTIONS"
|
OPTIONS = "OPTIONS"
|
||||||
PATCH = "PATCH"
|
PATCH = "PATCH"
|
||||||
POST = "POST"
|
POST = "POST"
|
||||||
PUT = "PUT"
|
PROPFIND = "PROPFIND"
|
||||||
TRACE = "TRACE"
|
PUT = "PUT"
|
||||||
|
TRACE = "TRACE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MIME types
|
// MIME types
|
||||||
|
@ -191,6 +193,7 @@ const (
|
||||||
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
HeaderXHTTPMethodOverride = "X-HTTP-Method-Override"
|
||||||
HeaderXRealIP = "X-Real-IP"
|
HeaderXRealIP = "X-Real-IP"
|
||||||
HeaderXRequestID = "X-Request-ID"
|
HeaderXRequestID = "X-Request-ID"
|
||||||
|
HeaderXRequestedWith = "X-Requested-With"
|
||||||
HeaderServer = "Server"
|
HeaderServer = "Server"
|
||||||
HeaderOrigin = "Origin"
|
HeaderOrigin = "Origin"
|
||||||
|
|
||||||
|
@ -214,7 +217,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
version = "3.2.6"
|
Version = "3.3.5"
|
||||||
website = "https://echo.labstack.com"
|
website = "https://echo.labstack.com"
|
||||||
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
// http://patorjk.com/software/taag/#p=display&f=Small%20Slant&t=Echo
|
||||||
banner = `
|
banner = `
|
||||||
|
@ -238,6 +241,7 @@ var (
|
||||||
OPTIONS,
|
OPTIONS,
|
||||||
PATCH,
|
PATCH,
|
||||||
POST,
|
POST,
|
||||||
|
PROPFIND,
|
||||||
PUT,
|
PUT,
|
||||||
TRACE,
|
TRACE,
|
||||||
}
|
}
|
||||||
|
@ -251,10 +255,10 @@ var (
|
||||||
ErrForbidden = NewHTTPError(http.StatusForbidden)
|
ErrForbidden = NewHTTPError(http.StatusForbidden)
|
||||||
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
|
ErrMethodNotAllowed = NewHTTPError(http.StatusMethodNotAllowed)
|
||||||
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
|
||||||
ErrValidatorNotRegistered = errors.New("Validator not registered")
|
ErrValidatorNotRegistered = errors.New("validator not registered")
|
||||||
ErrRendererNotRegistered = errors.New("Renderer not registered")
|
ErrRendererNotRegistered = errors.New("renderer not registered")
|
||||||
ErrInvalidRedirectCode = errors.New("Invalid redirect status code")
|
ErrInvalidRedirectCode = errors.New("invalid redirect status code")
|
||||||
ErrCookieNotFound = errors.New("Cookie not found")
|
ErrCookieNotFound = errors.New("cookie not found")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Error handlers
|
// Error handlers
|
||||||
|
@ -321,8 +325,8 @@ func (e *Echo) DefaultHTTPErrorHandler(err error, c Context) {
|
||||||
if he, ok := err.(*HTTPError); ok {
|
if he, ok := err.(*HTTPError); ok {
|
||||||
code = he.Code
|
code = he.Code
|
||||||
msg = he.Message
|
msg = he.Message
|
||||||
if he.Inner != nil {
|
if he.Internal != nil {
|
||||||
msg = fmt.Sprintf("%v, %v", err, he.Inner)
|
msg = fmt.Sprintf("%v, %v", err, he.Internal)
|
||||||
}
|
}
|
||||||
} else if e.Debug {
|
} else if e.Debug {
|
||||||
msg = err.Error()
|
msg = err.Error()
|
||||||
|
@ -443,7 +447,7 @@ func (e *Echo) Static(prefix, root string) *Route {
|
||||||
|
|
||||||
func static(i i, prefix, root string) *Route {
|
func static(i i, prefix, root string) *Route {
|
||||||
h := func(c Context) error {
|
h := func(c Context) error {
|
||||||
p, err := PathUnescape(c.Param("*"))
|
p, err := url.PathUnescape(c.Param("*"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -530,7 +534,7 @@ func (e *Echo) Reverse(name string, params ...interface{}) string {
|
||||||
|
|
||||||
// Routes returns the registered routes.
|
// Routes returns the registered routes.
|
||||||
func (e *Echo) Routes() []*Route {
|
func (e *Echo) Routes() []*Route {
|
||||||
routes := []*Route{}
|
routes := make([]*Route, 0, len(e.router.routes))
|
||||||
for _, v := range e.router.routes {
|
for _, v := range e.router.routes {
|
||||||
routes = append(routes, v)
|
routes = append(routes, v)
|
||||||
}
|
}
|
||||||
|
@ -551,39 +555,48 @@ func (e *Echo) ReleaseContext(c Context) {
|
||||||
|
|
||||||
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
// ServeHTTP implements `http.Handler` interface, which serves HTTP requests.
|
||||||
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// Acquire lock
|
|
||||||
// e.Mutex.RLock()
|
|
||||||
// defer e.Mutex.RUnlock()
|
|
||||||
|
|
||||||
// Acquire context
|
// Acquire context
|
||||||
c := e.pool.Get().(*context)
|
c := e.pool.Get().(*context)
|
||||||
defer e.pool.Put(c)
|
|
||||||
c.Reset(r, w)
|
c.Reset(r, w)
|
||||||
|
|
||||||
// Middleware
|
m := r.Method
|
||||||
h := func(c Context) error {
|
h := NotFoundHandler
|
||||||
method := r.Method
|
|
||||||
|
if e.premiddleware == nil {
|
||||||
path := r.URL.RawPath
|
path := r.URL.RawPath
|
||||||
if path == "" {
|
if path == "" {
|
||||||
path = r.URL.Path
|
path = r.URL.Path
|
||||||
}
|
}
|
||||||
e.router.Find(method, path, c)
|
e.router.Find(m, getPath(r), c)
|
||||||
h := c.Handler()
|
h = c.Handler()
|
||||||
for i := len(e.middleware) - 1; i >= 0; i-- {
|
for i := len(e.middleware) - 1; i >= 0; i-- {
|
||||||
h = e.middleware[i](h)
|
h = e.middleware[i](h)
|
||||||
}
|
}
|
||||||
return h(c)
|
} else {
|
||||||
}
|
h = func(c Context) error {
|
||||||
|
path := r.URL.RawPath
|
||||||
// Premiddleware
|
if path == "" {
|
||||||
for i := len(e.premiddleware) - 1; i >= 0; i-- {
|
path = r.URL.Path
|
||||||
h = e.premiddleware[i](h)
|
}
|
||||||
|
e.router.Find(m, getPath(r), c)
|
||||||
|
h := c.Handler()
|
||||||
|
for i := len(e.middleware) - 1; i >= 0; i-- {
|
||||||
|
h = e.middleware[i](h)
|
||||||
|
}
|
||||||
|
return h(c)
|
||||||
|
}
|
||||||
|
for i := len(e.premiddleware) - 1; i >= 0; i-- {
|
||||||
|
h = e.premiddleware[i](h)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute chain
|
// Execute chain
|
||||||
if err := h(c); err != nil {
|
if err := h(c); err != nil {
|
||||||
e.HTTPErrorHandler(err, c)
|
e.HTTPErrorHandler(err, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Release context
|
||||||
|
e.pool.Put(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts an HTTP server.
|
// Start starts an HTTP server.
|
||||||
|
@ -609,6 +622,10 @@ func (e *Echo) StartTLS(address string, certFile, keyFile string) (err error) {
|
||||||
|
|
||||||
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
|
// StartAutoTLS starts an HTTPS server using certificates automatically installed from https://letsencrypt.org.
|
||||||
func (e *Echo) StartAutoTLS(address string) error {
|
func (e *Echo) StartAutoTLS(address string) error {
|
||||||
|
if e.Listener == nil {
|
||||||
|
go http.ListenAndServe(":http", e.AutoTLSManager.HTTPHandler(nil))
|
||||||
|
}
|
||||||
|
|
||||||
s := e.TLSServer
|
s := e.TLSServer
|
||||||
s.TLSConfig = new(tls.Config)
|
s.TLSConfig = new(tls.Config)
|
||||||
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
|
s.TLSConfig.GetCertificate = e.AutoTLSManager.GetCertificate
|
||||||
|
@ -635,7 +652,7 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !e.HideBanner {
|
if !e.HideBanner {
|
||||||
e.colorer.Printf(banner, e.colorer.Red("v"+version), e.colorer.Blue(website))
|
e.colorer.Printf(banner, e.colorer.Red("v"+Version), e.colorer.Blue(website))
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.TLSConfig == nil {
|
if s.TLSConfig == nil {
|
||||||
|
@ -663,6 +680,24 @@ func (e *Echo) StartServer(s *http.Server) (err error) {
|
||||||
return s.Serve(e.TLSListener)
|
return s.Serve(e.TLSListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close immediately stops the server.
|
||||||
|
// It internally calls `http.Server#Close()`.
|
||||||
|
func (e *Echo) Close() error {
|
||||||
|
if err := e.TLSServer.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Server.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shutdown stops server the gracefully.
|
||||||
|
// It internally calls `http.Server#Shutdown()`.
|
||||||
|
func (e *Echo) Shutdown(ctx stdContext.Context) error {
|
||||||
|
if err := e.TLSServer.Shutdown(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.Server.Shutdown(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// NewHTTPError creates a new HTTPError instance.
|
// NewHTTPError creates a new HTTPError instance.
|
||||||
func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
func NewHTTPError(code int, message ...interface{}) *HTTPError {
|
||||||
he := &HTTPError{Code: code, Message: http.StatusText(code)}
|
he := &HTTPError{Code: code, Message: http.StatusText(code)}
|
||||||
|
@ -698,6 +733,14 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getPath(r *http.Request) string {
|
||||||
|
path := r.URL.RawPath
|
||||||
|
if path == "" {
|
||||||
|
path = r.URL.Path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
func handlerName(h HandlerFunc) string {
|
func handlerName(h HandlerFunc) string {
|
||||||
t := reflect.ValueOf(h).Type()
|
t := reflect.ValueOf(h).Type()
|
||||||
if t.Kind() == reflect.Func {
|
if t.Kind() == reflect.Func {
|
||||||
|
@ -706,6 +749,11 @@ func handlerName(h HandlerFunc) string {
|
||||||
return t.String()
|
return t.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// // PathUnescape is wraps `url.PathUnescape`
|
||||||
|
// func PathUnescape(s string) (string, error) {
|
||||||
|
// return url.PathUnescape(s)
|
||||||
|
// }
|
||||||
|
|
||||||
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
|
||||||
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
// connections. It's used by ListenAndServe and ListenAndServeTLS so
|
||||||
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
// dead TCP connections (e.g. closing laptop mid-download) eventually
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package echo
|
|
||||||
|
|
||||||
import (
|
|
||||||
stdContext "context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Close immediately stops the server.
|
|
||||||
// It internally calls `http.Server#Close()`.
|
|
||||||
func (e *Echo) Close() error {
|
|
||||||
if err := e.TLSServer.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return e.Server.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown stops server the gracefully.
|
|
||||||
// It internally calls `http.Server#Shutdown()`.
|
|
||||||
func (e *Echo) Shutdown(ctx stdContext.Context) error {
|
|
||||||
if err := e.TLSServer.Shutdown(ctx); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return e.Server.Shutdown(ctx)
|
|
||||||
}
|
|
|
@ -92,7 +92,7 @@ func (g *Group) Match(methods []string, path string, handler HandlerFunc, middle
|
||||||
|
|
||||||
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
|
// Group creates a new sub-group with prefix and optional sub-group-level middleware.
|
||||||
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
|
func (g *Group) Group(prefix string, middleware ...MiddlewareFunc) *Group {
|
||||||
m := []MiddlewareFunc{}
|
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||||
m = append(m, g.middleware...)
|
m = append(m, g.middleware...)
|
||||||
m = append(m, middleware...)
|
m = append(m, middleware...)
|
||||||
return g.echo.Group(g.prefix+prefix, m...)
|
return g.echo.Group(g.prefix+prefix, m...)
|
||||||
|
@ -113,7 +113,7 @@ func (g *Group) Add(method, path string, handler HandlerFunc, middleware ...Midd
|
||||||
// Combine into a new slice to avoid accidentally passing the same slice for
|
// Combine into a new slice to avoid accidentally passing the same slice for
|
||||||
// multiple routes, which would lead to later add() calls overwriting the
|
// multiple routes, which would lead to later add() calls overwriting the
|
||||||
// middleware from earlier calls.
|
// middleware from earlier calls.
|
||||||
m := []MiddlewareFunc{}
|
m := make([]MiddlewareFunc, 0, len(g.middleware)+len(middleware))
|
||||||
m = append(m, g.middleware...)
|
m = append(m, g.middleware...)
|
||||||
m = append(m, middleware...)
|
m = append(m, middleware...)
|
||||||
return g.echo.Add(method, g.prefix+path, handler, m...)
|
return g.echo.Add(method, g.prefix+path, handler, m...)
|
||||||
|
|
|
@ -93,10 +93,8 @@ func BasicAuthWithConfig(config BasicAuthConfig) echo.MiddlewareFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
realm := ""
|
realm := defaultRealm
|
||||||
if config.Realm == defaultRealm {
|
if config.Realm != defaultRealm {
|
||||||
realm = defaultRealm
|
|
||||||
} else {
|
|
||||||
realm = strconv.Quote(config.Realm)
|
realm = strconv.Quote(config.Realm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,11 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -105,6 +105,7 @@ func (r *limitedReader) Close() error {
|
||||||
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
|
func (r *limitedReader) Reset(reader io.ReadCloser, context echo.Context) {
|
||||||
r.reader = reader
|
r.reader = reader
|
||||||
r.context = context
|
r.context = context
|
||||||
|
r.read = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func limitedReaderPool(c BodyLimitConfig) sync.Pool {
|
func limitedReaderPool(c BodyLimitConfig) sync.Pool {
|
||||||
|
|
|
@ -126,8 +126,8 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||||
k, err := c.Cookie(config.CookieName)
|
k, err := c.Cookie(config.CookieName)
|
||||||
token := ""
|
token := ""
|
||||||
|
|
||||||
|
// Generate token
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Generate token
|
|
||||||
token = random.String(config.TokenLength)
|
token = random.String(config.TokenLength)
|
||||||
} else {
|
} else {
|
||||||
// Reuse token
|
// Reuse token
|
||||||
|
@ -143,7 +143,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
|
||||||
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
if !validateCSRFToken(token, clientToken) {
|
if !validateCSRFToken(token, clientToken) {
|
||||||
return echo.NewHTTPError(http.StatusForbidden, "Invalid csrf token")
|
return echo.NewHTTPError(http.StatusForbidden, "invalid csrf token")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ func csrfTokenFromForm(param string) csrfTokenExtractor {
|
||||||
return func(c echo.Context) (string, error) {
|
return func(c echo.Context) (string, error) {
|
||||||
token := c.FormValue(param)
|
token := c.FormValue(param)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return "", errors.New("Missing csrf token in the form parameter")
|
return "", errors.New("missing csrf token in the form parameter")
|
||||||
}
|
}
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
@ -199,7 +199,7 @@ func csrfTokenFromQuery(param string) csrfTokenExtractor {
|
||||||
return func(c echo.Context) (string, error) {
|
return func(c echo.Context) (string, error) {
|
||||||
token := c.QueryParam(param)
|
token := c.QueryParam(param)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return "", errors.New("Missing csrf token in the query string")
|
return "", errors.New("missing csrf token in the query string")
|
||||||
}
|
}
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,8 +58,8 @@ const (
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
var (
|
var (
|
||||||
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "Missing or malformed jwt")
|
ErrJWTMissing = echo.NewHTTPError(http.StatusBadRequest, "missing or malformed jwt")
|
||||||
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "Invalid or expired jwt")
|
ErrJWTInvalid = echo.NewHTTPError(http.StatusUnauthorized, "invalid or expired jwt")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -116,7 +116,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||||
config.keyFunc = func(t *jwt.Token) (interface{}, error) {
|
config.keyFunc = func(t *jwt.Token) (interface{}, error) {
|
||||||
// Check the signing method
|
// Check the signing method
|
||||||
if t.Method.Alg() != config.SigningMethod {
|
if t.Method.Alg() != config.SigningMethod {
|
||||||
return nil, fmt.Errorf("Unexpected jwt signing method=%v", t.Header["alg"])
|
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
|
||||||
}
|
}
|
||||||
return config.SigningKey, nil
|
return config.SigningKey, nil
|
||||||
}
|
}
|
||||||
|
@ -156,9 +156,9 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
|
||||||
return next(c)
|
return next(c)
|
||||||
}
|
}
|
||||||
return &echo.HTTPError{
|
return &echo.HTTPError{
|
||||||
Code: ErrJWTInvalid.Code,
|
Code: ErrJWTInvalid.Code,
|
||||||
Message: ErrJWTInvalid.Message,
|
Message: ErrJWTInvalid.Message,
|
||||||
Inner: err,
|
Internal: err,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,14 +114,14 @@ func keyFromHeader(header string, authScheme string) keyExtractor {
|
||||||
return func(c echo.Context) (string, error) {
|
return func(c echo.Context) (string, error) {
|
||||||
auth := c.Request().Header.Get(header)
|
auth := c.Request().Header.Get(header)
|
||||||
if auth == "" {
|
if auth == "" {
|
||||||
return "", errors.New("Missing key in request header")
|
return "", errors.New("missing key in request header")
|
||||||
}
|
}
|
||||||
if header == echo.HeaderAuthorization {
|
if header == echo.HeaderAuthorization {
|
||||||
l := len(authScheme)
|
l := len(authScheme)
|
||||||
if len(auth) > l+1 && auth[:l] == authScheme {
|
if len(auth) > l+1 && auth[:l] == authScheme {
|
||||||
return auth[l+1:], nil
|
return auth[l+1:], nil
|
||||||
}
|
}
|
||||||
return "", errors.New("Invalid key in the request header")
|
return "", errors.New("invalid key in the request header")
|
||||||
}
|
}
|
||||||
return auth, nil
|
return auth, nil
|
||||||
}
|
}
|
||||||
|
@ -132,7 +132,7 @@ func keyFromQuery(param string) keyExtractor {
|
||||||
return func(c echo.Context) (string, error) {
|
return func(c echo.Context) (string, error) {
|
||||||
key := c.QueryParam(param)
|
key := c.QueryParam(param)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return "", errors.New("Missing key in the query string")
|
return "", errors.New("missing key in the query string")
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ func keyFromForm(param string) keyExtractor {
|
||||||
return func(c echo.Context) (string, error) {
|
return func(c echo.Context) (string, error) {
|
||||||
key := c.FormValue(param)
|
key := c.FormValue(param)
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return "", errors.New("Missing key in the form")
|
return "", errors.New("missing key in the form")
|
||||||
}
|
}
|
||||||
return key, nil
|
return key, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ type (
|
||||||
// Example "${remote_ip} ${status}"
|
// Example "${remote_ip} ${status}"
|
||||||
//
|
//
|
||||||
// Optional. Default value DefaultLoggerConfig.Format.
|
// Optional. Default value DefaultLoggerConfig.Format.
|
||||||
Format string `yaml:"format"`
|
Format string `yaml:"format"`
|
||||||
|
|
||||||
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
|
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
|
||||||
CustomTimeFormat string `yaml:"custom_time_format"`
|
CustomTimeFormat string `yaml:"custom_time_format"`
|
||||||
|
@ -70,9 +70,9 @@ var (
|
||||||
`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
|
`"method":"${method}","uri":"${uri}","status":${status}, "latency":${latency},` +
|
||||||
`"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
|
`"latency_human":"${latency_human}","bytes_in":${bytes_in},` +
|
||||||
`"bytes_out":${bytes_out}}` + "\n",
|
`"bytes_out":${bytes_out}}` + "\n",
|
||||||
CustomTimeFormat:"2006-01-02 15:04:05.00000",
|
CustomTimeFormat: "2006-01-02 15:04:05.00000",
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
colorer: color.New(),
|
colorer: color.New(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -108,15 +108,15 @@ func proxyRaw(t *ProxyTarget, c echo.Context) http.Handler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
errc := make(chan error, 2)
|
errCh := make(chan error, 2)
|
||||||
cp := func(dst io.Writer, src io.Reader) {
|
cp := func(dst io.Writer, src io.Reader) {
|
||||||
_, err := io.Copy(dst, src)
|
_, err = io.Copy(dst, src)
|
||||||
errc <- err
|
errCh <- err
|
||||||
}
|
}
|
||||||
|
|
||||||
go cp(out, in)
|
go cp(out, in)
|
||||||
go cp(in, out)
|
go cp(in, out)
|
||||||
err = <-errc
|
err = <-errCh
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
|
c.Logger().Errorf("proxy raw, copy body error=%v, url=%s", t.URL, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,16 @@ package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/labstack/echo"
|
"github.com/labstack/echo"
|
||||||
|
"github.com/labstack/gommon/bytes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -36,6 +39,78 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<title>{{ .Name }}</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Menlo, Consolas, monospace;
|
||||||
|
padding: 48px;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
padding: 4px 16px;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px 0 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
width: 300px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
li a {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
}
|
||||||
|
li span {
|
||||||
|
color: #707070;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
li a:hover {
|
||||||
|
opacity: 0.50;
|
||||||
|
}
|
||||||
|
.dir {
|
||||||
|
color: #E91E63;
|
||||||
|
}
|
||||||
|
.file {
|
||||||
|
color: #673AB7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header>
|
||||||
|
{{ .Name }}
|
||||||
|
</header>
|
||||||
|
<ul>
|
||||||
|
{{ range .Files }}
|
||||||
|
<li>
|
||||||
|
{{ if .Dir }}
|
||||||
|
{{ $name := print .Name "/" }}
|
||||||
|
<a class="dir" href="{{ $name }}">{{ $name }}</a>
|
||||||
|
{{ else }}
|
||||||
|
<a class="file" href="{{ .Name }}">{{ .Name }}</a>
|
||||||
|
<span>{{ .Size }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// DefaultStaticConfig is the default Static middleware config.
|
// DefaultStaticConfig is the default Static middleware config.
|
||||||
DefaultStaticConfig = StaticConfig{
|
DefaultStaticConfig = StaticConfig{
|
||||||
|
@ -66,6 +141,12 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||||
config.Index = DefaultStaticConfig.Index
|
config.Index = DefaultStaticConfig.Index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index template
|
||||||
|
t, err := template.New("index").Parse(html)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("echo: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(c echo.Context) (err error) {
|
return func(c echo.Context) (err error) {
|
||||||
if config.Skipper(c) {
|
if config.Skipper(c) {
|
||||||
|
@ -76,7 +157,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||||
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
|
if strings.HasSuffix(c.Path(), "*") { // When serving from a group, e.g. `/static*`.
|
||||||
p = c.Param("*")
|
p = c.Param("*")
|
||||||
}
|
}
|
||||||
p, err = echo.PathUnescape(p)
|
p, err = url.PathUnescape(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -103,7 +184,7 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if config.Browse {
|
if config.Browse {
|
||||||
return listDir(name, c.Response())
|
return listDir(t, name, c.Response())
|
||||||
}
|
}
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return next(c)
|
return next(c)
|
||||||
|
@ -119,32 +200,30 @@ func StaticWithConfig(config StaticConfig) echo.MiddlewareFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listDir(name string, res *echo.Response) (err error) {
|
func listDir(t *template.Template, name string, res *echo.Response) (err error) {
|
||||||
dir, err := os.Open(name)
|
file, err := os.Open(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
dirs, err := dir.Readdir(-1)
|
files, err := file.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a directory index
|
// Create directory index
|
||||||
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
res.Header().Set(echo.HeaderContentType, echo.MIMETextHTMLCharsetUTF8)
|
||||||
if _, err = fmt.Fprintf(res, "<pre>\n"); err != nil {
|
data := struct {
|
||||||
return
|
Name string
|
||||||
|
Files []interface{}
|
||||||
|
}{
|
||||||
|
Name: name,
|
||||||
}
|
}
|
||||||
for _, d := range dirs {
|
for _, f := range files {
|
||||||
name := d.Name()
|
data.Files = append(data.Files, struct {
|
||||||
color := "#212121"
|
Name string
|
||||||
if d.IsDir() {
|
Dir bool
|
||||||
color = "#e91e63"
|
Size string
|
||||||
name += "/"
|
}{f.Name(), f.IsDir(), bytes.Format(f.Size())})
|
||||||
}
|
|
||||||
if _, err = fmt.Fprintf(res, "<a href=\"%s\" style=\"color: %s;\">%s</a>\n", name, color, name); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_, err = fmt.Fprintf(res, "</pre>\n")
|
return t.Execute(res, data)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -12,14 +11,13 @@ type (
|
||||||
// by an HTTP handler to construct an HTTP response.
|
// by an HTTP handler to construct an HTTP response.
|
||||||
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
// See: https://golang.org/pkg/net/http/#ResponseWriter
|
||||||
Response struct {
|
Response struct {
|
||||||
echo *Echo
|
echo *Echo
|
||||||
contentLength int64
|
beforeFuncs []func()
|
||||||
beforeFuncs []func()
|
afterFuncs []func()
|
||||||
afterFuncs []func()
|
Writer http.ResponseWriter
|
||||||
Writer http.ResponseWriter
|
Status int
|
||||||
Status int
|
Size int64
|
||||||
Size int64
|
Committed bool
|
||||||
Committed bool
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +62,6 @@ func (r *Response) WriteHeader(code int) {
|
||||||
r.Status = code
|
r.Status = code
|
||||||
r.Writer.WriteHeader(code)
|
r.Writer.WriteHeader(code)
|
||||||
r.Committed = true
|
r.Committed = true
|
||||||
r.contentLength, _ = strconv.ParseInt(r.Header().Get(HeaderContentLength), 10, 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write writes the data to the connection as part of an HTTP reply.
|
// Write writes the data to the connection as part of an HTTP reply.
|
||||||
|
@ -74,10 +71,8 @@ func (r *Response) Write(b []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
n, err = r.Writer.Write(b)
|
n, err = r.Writer.Write(b)
|
||||||
r.Size += int64(n)
|
r.Size += int64(n)
|
||||||
if r.Size == r.contentLength {
|
for _, fn := range r.afterFuncs {
|
||||||
for _, fn := range r.afterFuncs {
|
fn()
|
||||||
fn()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -106,7 +101,6 @@ func (r *Response) CloseNotify() <-chan bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Response) reset(w http.ResponseWriter) {
|
func (r *Response) reset(w http.ResponseWriter) {
|
||||||
r.contentLength = 0
|
|
||||||
r.beforeFuncs = nil
|
r.beforeFuncs = nil
|
||||||
r.afterFuncs = nil
|
r.afterFuncs = nil
|
||||||
r.Writer = w
|
r.Writer = w
|
||||||
|
|
|
@ -21,15 +21,16 @@ type (
|
||||||
kind uint8
|
kind uint8
|
||||||
children []*node
|
children []*node
|
||||||
methodHandler struct {
|
methodHandler struct {
|
||||||
connect HandlerFunc
|
connect HandlerFunc
|
||||||
delete HandlerFunc
|
delete HandlerFunc
|
||||||
get HandlerFunc
|
get HandlerFunc
|
||||||
head HandlerFunc
|
head HandlerFunc
|
||||||
options HandlerFunc
|
options HandlerFunc
|
||||||
patch HandlerFunc
|
patch HandlerFunc
|
||||||
post HandlerFunc
|
post HandlerFunc
|
||||||
put HandlerFunc
|
propfind HandlerFunc
|
||||||
trace HandlerFunc
|
put HandlerFunc
|
||||||
|
trace HandlerFunc
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -59,8 +60,8 @@ func (r *Router) Add(method, path string, h HandlerFunc) {
|
||||||
if path[0] != '/' {
|
if path[0] != '/' {
|
||||||
path = "/" + path
|
path = "/" + path
|
||||||
}
|
}
|
||||||
ppath := path // Pristine path
|
|
||||||
pnames := []string{} // Param names
|
pnames := []string{} // Param names
|
||||||
|
ppath := path // Pristine path
|
||||||
|
|
||||||
for i, l := 0, len(path); i < l; i++ {
|
for i, l := 0, len(path); i < l; i++ {
|
||||||
if path[i] == ':' {
|
if path[i] == ':' {
|
||||||
|
@ -225,22 +226,24 @@ func (n *node) findChildByKind(t kind) *node {
|
||||||
|
|
||||||
func (n *node) addHandler(method string, h HandlerFunc) {
|
func (n *node) addHandler(method string, h HandlerFunc) {
|
||||||
switch method {
|
switch method {
|
||||||
case GET:
|
|
||||||
n.methodHandler.get = h
|
|
||||||
case POST:
|
|
||||||
n.methodHandler.post = h
|
|
||||||
case PUT:
|
|
||||||
n.methodHandler.put = h
|
|
||||||
case DELETE:
|
|
||||||
n.methodHandler.delete = h
|
|
||||||
case PATCH:
|
|
||||||
n.methodHandler.patch = h
|
|
||||||
case OPTIONS:
|
|
||||||
n.methodHandler.options = h
|
|
||||||
case HEAD:
|
|
||||||
n.methodHandler.head = h
|
|
||||||
case CONNECT:
|
case CONNECT:
|
||||||
n.methodHandler.connect = h
|
n.methodHandler.connect = h
|
||||||
|
case DELETE:
|
||||||
|
n.methodHandler.delete = h
|
||||||
|
case GET:
|
||||||
|
n.methodHandler.get = h
|
||||||
|
case HEAD:
|
||||||
|
n.methodHandler.head = h
|
||||||
|
case OPTIONS:
|
||||||
|
n.methodHandler.options = h
|
||||||
|
case PATCH:
|
||||||
|
n.methodHandler.patch = h
|
||||||
|
case POST:
|
||||||
|
n.methodHandler.post = h
|
||||||
|
case PROPFIND:
|
||||||
|
n.methodHandler.propfind = h
|
||||||
|
case PUT:
|
||||||
|
n.methodHandler.put = h
|
||||||
case TRACE:
|
case TRACE:
|
||||||
n.methodHandler.trace = h
|
n.methodHandler.trace = h
|
||||||
}
|
}
|
||||||
|
@ -248,22 +251,24 @@ func (n *node) addHandler(method string, h HandlerFunc) {
|
||||||
|
|
||||||
func (n *node) findHandler(method string) HandlerFunc {
|
func (n *node) findHandler(method string) HandlerFunc {
|
||||||
switch method {
|
switch method {
|
||||||
case GET:
|
|
||||||
return n.methodHandler.get
|
|
||||||
case POST:
|
|
||||||
return n.methodHandler.post
|
|
||||||
case PUT:
|
|
||||||
return n.methodHandler.put
|
|
||||||
case DELETE:
|
|
||||||
return n.methodHandler.delete
|
|
||||||
case PATCH:
|
|
||||||
return n.methodHandler.patch
|
|
||||||
case OPTIONS:
|
|
||||||
return n.methodHandler.options
|
|
||||||
case HEAD:
|
|
||||||
return n.methodHandler.head
|
|
||||||
case CONNECT:
|
case CONNECT:
|
||||||
return n.methodHandler.connect
|
return n.methodHandler.connect
|
||||||
|
case DELETE:
|
||||||
|
return n.methodHandler.delete
|
||||||
|
case GET:
|
||||||
|
return n.methodHandler.get
|
||||||
|
case HEAD:
|
||||||
|
return n.methodHandler.head
|
||||||
|
case OPTIONS:
|
||||||
|
return n.methodHandler.options
|
||||||
|
case PATCH:
|
||||||
|
return n.methodHandler.patch
|
||||||
|
case POST:
|
||||||
|
return n.methodHandler.post
|
||||||
|
case PROPFIND:
|
||||||
|
return n.methodHandler.propfind
|
||||||
|
case PUT:
|
||||||
|
return n.methodHandler.put
|
||||||
case TRACE:
|
case TRACE:
|
||||||
return n.methodHandler.trace
|
return n.methodHandler.trace
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
// +build go1.7, !go1.8
|
|
||||||
|
|
||||||
package echo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/url"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PathUnescape is wraps `url.QueryUnescape`
|
|
||||||
func PathUnescape(s string) (string, error) {
|
|
||||||
return url.QueryUnescape(s)
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package echo
|
|
||||||
|
|
||||||
import "net/url"
|
|
||||||
|
|
||||||
// PathUnescape is wraps `url.PathUnescape`
|
|
||||||
func PathUnescape(s string) (string, error) {
|
|
||||||
return url.PathUnescape(s)
|
|
||||||
}
|
|
|
@ -1,14 +1,13 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.8
|
- 1.11.x
|
||||||
- 1.9
|
|
||||||
- tip
|
- tip
|
||||||
before_install:
|
before_install:
|
||||||
- go get -v github.com/golang/lint/golint
|
- go get -v golang.org/x/lint/golint
|
||||||
script:
|
script:
|
||||||
- $HOME/gopath/bin/golint -min_confidence 0.9 -set_exit_status
|
- $HOME/gopath/bin/golint -min_confidence 0.9 -set_exit_status
|
||||||
- GORACE="exitcode=1 halt_on_error=1" go test -v -coverprofile=coverage.txt -race -timeout 3m -count 3 -cpu 1,4
|
- GORACE="exitcode=1 halt_on_error=1" go test -v -coverprofile=coverage.txt -race -timeout 3m -count 3 -cpu 1,4
|
||||||
- go tool vet -v -all .
|
- go vet -v .
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
branches:
|
branches:
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (cmd *Commands) PartMessage(channel, message string) {
|
||||||
// PRIVMSG specifically. ctcpType is the CTCP command, e.g. "FINGER", "TIME",
|
// PRIVMSG specifically. ctcpType is the CTCP command, e.g. "FINGER", "TIME",
|
||||||
// "VERSION", etc.
|
// "VERSION", etc.
|
||||||
func (cmd *Commands) SendCTCP(target, ctcpType, message string) {
|
func (cmd *Commands) SendCTCP(target, ctcpType, message string) {
|
||||||
out := encodeCTCPRaw(ctcpType, message)
|
out := EncodeCTCPRaw(ctcpType, message)
|
||||||
if out == "" {
|
if out == "" {
|
||||||
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@ func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interf
|
||||||
// SendCTCPReply sends a CTCP response to target. Note that this method uses
|
// SendCTCPReply sends a CTCP response to target. Note that this method uses
|
||||||
// NOTICE specifically.
|
// NOTICE specifically.
|
||||||
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) {
|
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) {
|
||||||
out := encodeCTCPRaw(ctcpType, message)
|
out := EncodeCTCPRaw(ctcpType, message)
|
||||||
if out == "" {
|
if out == "" {
|
||||||
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
panic(fmt.Sprintf("invalid CTCP: %s -> %s: %s", target, ctcpType, message))
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ package girc
|
||||||
|
|
||||||
// Standard CTCP based constants.
|
// Standard CTCP based constants.
|
||||||
const (
|
const (
|
||||||
|
CTCP_ACTION = "ACTION"
|
||||||
CTCP_PING = "PING"
|
CTCP_PING = "PING"
|
||||||
CTCP_PONG = "PONG"
|
CTCP_PONG = "PONG"
|
||||||
CTCP_VERSION = "VERSION"
|
CTCP_VERSION = "VERSION"
|
||||||
|
|
|
@ -30,18 +30,22 @@ type CTCPEvent struct {
|
||||||
Reply bool `json:"reply"`
|
Reply bool `json:"reply"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// decodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned
|
// DecodeCTCP decodes an incoming CTCP event, if it is CTCP. nil is returned
|
||||||
// if the incoming event does not match a valid CTCP.
|
// if the incoming event does not have valid CTCP encoding.
|
||||||
func decodeCTCP(e *Event) *CTCPEvent {
|
func DecodeCTCP(e *Event) *CTCPEvent {
|
||||||
// http://www.irchelp.org/protocol/ctcpspec.html
|
// http://www.irchelp.org/protocol/ctcpspec.html
|
||||||
|
|
||||||
|
if e == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Must be targeting a user/channel, AND trailing must have
|
// Must be targeting a user/channel, AND trailing must have
|
||||||
// DELIM+TAG+DELIM minimum (at least 3 chars).
|
// DELIM+TAG+DELIM minimum (at least 3 chars).
|
||||||
if len(e.Params) != 1 || len(e.Trailing) < 3 {
|
if len(e.Params) != 1 || len(e.Trailing) < 3 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Command != PRIVMSG && e.Command != NOTICE) || !IsValidNick(e.Params[0]) {
|
if e.Command != PRIVMSG && e.Command != NOTICE {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,18 +92,18 @@ func decodeCTCP(e *Event) *CTCPEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeCTCP encodes a CTCP event into a string, including delimiters.
|
// EncodeCTCP encodes a CTCP event into a string, including delimiters.
|
||||||
func encodeCTCP(ctcp *CTCPEvent) (out string) {
|
func EncodeCTCP(ctcp *CTCPEvent) (out string) {
|
||||||
if ctcp == nil {
|
if ctcp == nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
return encodeCTCPRaw(ctcp.Command, ctcp.Text)
|
return EncodeCTCPRaw(ctcp.Command, ctcp.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeCTCPRaw is much like encodeCTCP, however accepts a raw command and
|
// EncodeCTCPRaw is much like EncodeCTCP, however accepts a raw command and
|
||||||
// string as input.
|
// string as input.
|
||||||
func encodeCTCPRaw(cmd, text string) (out string) {
|
func EncodeCTCPRaw(cmd, text string) (out string) {
|
||||||
if len(cmd) <= 0 {
|
if len(cmd) <= 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
@ -145,6 +149,11 @@ func (c *CTCP) call(client *Client, event *CTCPEvent) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := c.handlers[event.Command]; !ok {
|
if _, ok := c.handlers[event.Command]; !ok {
|
||||||
|
// If ACTION, don't do anything.
|
||||||
|
if event.Command == CTCP_ACTION {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Send a ERRMSG reply, if we know who sent it.
|
// Send a ERRMSG reply, if we know who sent it.
|
||||||
if event.Source != nil && IsValidNick(event.Source.Name) {
|
if event.Source != nil && IsValidNick(event.Source.Name) {
|
||||||
client.Cmd.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query")
|
client.Cmd.SendCTCPReply(event.Source.Name, CTCP_ERRMSG, "that is an unknown CTCP query")
|
||||||
|
|
|
@ -355,11 +355,15 @@ func (e *Event) Pretty() (out string, ok bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Command == PRIVMSG || e.Command == NOTICE) && len(e.Params) > 0 {
|
if (e.Command == PRIVMSG || e.Command == NOTICE) && len(e.Params) > 0 {
|
||||||
if ctcp := decodeCTCP(e); ctcp != nil {
|
if ctcp := DecodeCTCP(e); ctcp != nil {
|
||||||
if ctcp.Reply {
|
if ctcp.Reply {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ctcp.Command == CTCP_ACTION {
|
||||||
|
return fmt.Sprintf("[%s] **%s** %s", strings.Join(e.Params, ","), ctcp.Source.Name, ctcp.Text), true
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("[*] CTCP query from %s: %s%s", ctcp.Source.Name, ctcp.Command, " "+ctcp.Text), true
|
return fmt.Sprintf("[*] CTCP query from %s: %s%s", ctcp.Source.Name, ctcp.Command, " "+ctcp.Text), true
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s] (%s) %s", strings.Join(e.Params, ","), e.Source.Name, e.Trailing), true
|
return fmt.Sprintf("[%s] (%s) %s", strings.Join(e.Params, ","), e.Source.Name, e.Trailing), true
|
||||||
|
@ -448,23 +452,27 @@ func (e *Event) Pretty() (out string, ok bool) {
|
||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAction checks to see if the event is a PRIVMSG, and is an ACTION (/me).
|
// IsAction checks to see if the event is an ACTION (/me).
|
||||||
func (e *Event) IsAction() bool {
|
func (e *Event) IsAction() bool {
|
||||||
if e.Source == nil || e.Command != PRIVMSG || len(e.Trailing) < 9 {
|
if e.Command != PRIVMSG {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(e.Trailing, "\001ACTION") || e.Trailing[len(e.Trailing)-1] != ctcpDelim {
|
ok, ctcp := e.IsCTCP()
|
||||||
return false
|
return ok && ctcp.Command == CTCP_ACTION
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
// IsCTCP checks to see if the event is a CTCP event, and if so, returns the
|
||||||
|
// converted CTCP event.
|
||||||
|
func (e *Event) IsCTCP() (ok bool, ctcp *CTCPEvent) {
|
||||||
|
ctcp = DecodeCTCP(e)
|
||||||
|
return ctcp != nil, ctcp
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsFromChannel checks to see if a message was from a channel (rather than
|
// IsFromChannel checks to see if a message was from a channel (rather than
|
||||||
// a private message).
|
// a private message).
|
||||||
func (e *Event) IsFromChannel() bool {
|
func (e *Event) IsFromChannel() bool {
|
||||||
if e.Source == nil || e.Command != PRIVMSG || len(e.Params) < 1 {
|
if e.Source == nil || (e.Command != PRIVMSG && e.Command != NOTICE) || len(e.Params) < 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +486,7 @@ func (e *Event) IsFromChannel() bool {
|
||||||
// IsFromUser checks to see if a message was from a user (rather than a
|
// IsFromUser checks to see if a message was from a user (rather than a
|
||||||
// channel).
|
// channel).
|
||||||
func (e *Event) IsFromUser() bool {
|
func (e *Event) IsFromUser() bool {
|
||||||
if e.Source == nil || e.Command != PRIVMSG || len(e.Params) < 1 {
|
if e.Source == nil || (e.Command != PRIVMSG && e.Command != NOTICE) || len(e.Params) < 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -113,7 +113,7 @@ func Fmt(text string) string {
|
||||||
|
|
||||||
if last > -1 {
|
if last > -1 {
|
||||||
// A-Z, a-z, and ","
|
// A-Z, a-z, and ","
|
||||||
if text[i] != ',' && (text[i] <= 'A' || text[i] >= 'Z') && (text[i] <= 'a' || text[i] >= 'z') {
|
if text[i] != ',' && (text[i] < 'A' || text[i] > 'Z') && (text[i] < 'a' || text[i] > 'z') {
|
||||||
last = -1
|
last = -1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
module github.com/lrstanley/girc
|
|
@ -46,7 +46,7 @@ func (c *Client) RunHandlers(event *Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a CTCP.
|
// Check if it's a CTCP.
|
||||||
if ctcp := decodeCTCP(event.Copy()); ctcp != nil {
|
if ctcp := DecodeCTCP(event.Copy()); ctcp != nil {
|
||||||
// Execute it.
|
// Execute it.
|
||||||
c.CTCP.call(c, ctcp)
|
c.CTCP.call(c, ctcp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@ go:
|
||||||
- 1.7.x
|
- 1.7.x
|
||||||
- 1.8.x
|
- 1.8.x
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
- "1.10"
|
- "1.10.x"
|
||||||
- tip
|
- tip
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
|
### [1.8](https://github.com/magiconair/properties/tree/v1.8) - 15 May 2018
|
||||||
|
|
||||||
|
* [PR #26](https://github.com/magiconair/properties/pull/26): Disable expansion during loading
|
||||||
|
|
||||||
|
This adds the option to disable property expansion during loading.
|
||||||
|
|
||||||
|
Thanks to [@kmala](https://github.com/kmala) for the patch.
|
||||||
|
|
||||||
### [1.7.6](https://github.com/magiconair/properties/tree/v1.7.6) - 14 Feb 2018
|
### [1.7.6](https://github.com/magiconair/properties/tree/v1.7.6) - 14 Feb 2018
|
||||||
|
|
||||||
* [PR #29](https://github.com/magiconair/properties/pull/29): Reworked expansion logic to handle more complex cases.
|
* [PR #29](https://github.com/magiconair/properties/pull/29): Reworked expansion logic to handle more complex cases.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
// # refers to the users' home dir
|
// # refers to the users' home dir
|
||||||
// home = ${HOME}
|
// home = ${HOME}
|
||||||
//
|
//
|
||||||
// # local key takes precendence over env var: u = foo
|
// # local key takes precedence over env var: u = foo
|
||||||
// USER = foo
|
// USER = foo
|
||||||
// u = ${USER}
|
// u = ${USER}
|
||||||
//
|
//
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
// v = p.GetString("key", "def")
|
// v = p.GetString("key", "def")
|
||||||
// v = p.GetDuration("key", 999)
|
// v = p.GetDuration("key", 999)
|
||||||
//
|
//
|
||||||
// As an alterantive properties may be applied with the standard
|
// As an alternative properties may be applied with the standard
|
||||||
// library's flag implementation at any time.
|
// library's flag implementation at any time.
|
||||||
//
|
//
|
||||||
// # Standard configuration
|
// # Standard configuration
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -16,21 +16,157 @@ import (
|
||||||
type Encoding uint
|
type Encoding uint
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// utf8Default is a private placeholder for the zero value of Encoding to
|
||||||
|
// ensure that it has the correct meaning. UTF8 is the default encoding but
|
||||||
|
// was assigned a non-zero value which cannot be changed without breaking
|
||||||
|
// existing code. Clients should continue to use the public constants.
|
||||||
|
utf8Default Encoding = iota
|
||||||
|
|
||||||
// UTF8 interprets the input data as UTF-8.
|
// UTF8 interprets the input data as UTF-8.
|
||||||
UTF8 Encoding = 1 << iota
|
UTF8
|
||||||
|
|
||||||
// ISO_8859_1 interprets the input data as ISO-8859-1.
|
// ISO_8859_1 interprets the input data as ISO-8859-1.
|
||||||
ISO_8859_1
|
ISO_8859_1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Loader struct {
|
||||||
|
// Encoding determines how the data from files and byte buffers
|
||||||
|
// is interpreted. For URLs the Content-Type header is used
|
||||||
|
// to determine the encoding of the data.
|
||||||
|
Encoding Encoding
|
||||||
|
|
||||||
|
// DisableExpansion configures the property expansion of the
|
||||||
|
// returned property object. When set to true, the property values
|
||||||
|
// will not be expanded and the Property object will not be checked
|
||||||
|
// for invalid expansion expressions.
|
||||||
|
DisableExpansion bool
|
||||||
|
|
||||||
|
// IgnoreMissing configures whether missing files or URLs which return
|
||||||
|
// 404 are reported as errors. When set to true, missing files and 404
|
||||||
|
// status codes are not reported as errors.
|
||||||
|
IgnoreMissing bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load reads a buffer into a Properties struct.
|
||||||
|
func (l *Loader) LoadBytes(buf []byte) (*Properties, error) {
|
||||||
|
return l.loadBytes(buf, l.Encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAll reads the content of multiple URLs or files in the given order into
|
||||||
|
// a Properties struct. If IgnoreMissing is true then a 404 status code or
|
||||||
|
// missing file will not be reported as error. Encoding sets the encoding for
|
||||||
|
// files. For the URLs see LoadURL for the Content-Type header and the
|
||||||
|
// encoding.
|
||||||
|
func (l *Loader) LoadAll(names []string) (*Properties, error) {
|
||||||
|
all := NewProperties()
|
||||||
|
for _, name := range names {
|
||||||
|
n, err := expandName(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Properties
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(n, "http://"):
|
||||||
|
p, err = l.LoadURL(n)
|
||||||
|
case strings.HasPrefix(n, "https://"):
|
||||||
|
p, err = l.LoadURL(n)
|
||||||
|
default:
|
||||||
|
p, err = l.LoadFile(n)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
all.Merge(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
all.DisableExpansion = l.DisableExpansion
|
||||||
|
if all.DisableExpansion {
|
||||||
|
return all, nil
|
||||||
|
}
|
||||||
|
return all, all.check()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFile reads a file into a Properties struct.
|
||||||
|
// If IgnoreMissing is true then a missing file will not be
|
||||||
|
// reported as error.
|
||||||
|
func (l *Loader) LoadFile(filename string) (*Properties, error) {
|
||||||
|
data, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
if l.IgnoreMissing && os.IsNotExist(err) {
|
||||||
|
LogPrintf("properties: %s not found. skipping", filename)
|
||||||
|
return NewProperties(), nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return l.loadBytes(data, l.Encoding)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadURL reads the content of the URL into a Properties struct.
|
||||||
|
//
|
||||||
|
// The encoding is determined via the Content-Type header which
|
||||||
|
// should be set to 'text/plain'. If the 'charset' parameter is
|
||||||
|
// missing, 'iso-8859-1' or 'latin1' the encoding is set to
|
||||||
|
// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
|
||||||
|
// encoding is set to UTF-8. A missing content type header is
|
||||||
|
// interpreted as 'text/plain; charset=utf-8'.
|
||||||
|
func (l *Loader) LoadURL(url string) (*Properties, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode == 404 && l.IgnoreMissing {
|
||||||
|
LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
|
||||||
|
return NewProperties(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
ct := resp.Header.Get("Content-Type")
|
||||||
|
var enc Encoding
|
||||||
|
switch strings.ToLower(ct) {
|
||||||
|
case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1":
|
||||||
|
enc = ISO_8859_1
|
||||||
|
case "", "text/plain; charset=utf-8":
|
||||||
|
enc = UTF8
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("properties: invalid content type %s", ct)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.loadBytes(body, enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loader) loadBytes(buf []byte, enc Encoding) (*Properties, error) {
|
||||||
|
p, err := parse(convert(buf, enc))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.DisableExpansion = l.DisableExpansion
|
||||||
|
if p.DisableExpansion {
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
|
return p, p.check()
|
||||||
|
}
|
||||||
|
|
||||||
// Load reads a buffer into a Properties struct.
|
// Load reads a buffer into a Properties struct.
|
||||||
func Load(buf []byte, enc Encoding) (*Properties, error) {
|
func Load(buf []byte, enc Encoding) (*Properties, error) {
|
||||||
return loadBuf(buf, enc)
|
l := &Loader{Encoding: enc}
|
||||||
|
return l.LoadBytes(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadString reads an UTF8 string into a properties struct.
|
// LoadString reads an UTF8 string into a properties struct.
|
||||||
func LoadString(s string) (*Properties, error) {
|
func LoadString(s string) (*Properties, error) {
|
||||||
return loadBuf([]byte(s), UTF8)
|
l := &Loader{Encoding: UTF8}
|
||||||
|
return l.LoadBytes([]byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadMap creates a new Properties struct from a string map.
|
// LoadMap creates a new Properties struct from a string map.
|
||||||
|
@ -44,34 +180,32 @@ func LoadMap(m map[string]string) *Properties {
|
||||||
|
|
||||||
// LoadFile reads a file into a Properties struct.
|
// LoadFile reads a file into a Properties struct.
|
||||||
func LoadFile(filename string, enc Encoding) (*Properties, error) {
|
func LoadFile(filename string, enc Encoding) (*Properties, error) {
|
||||||
return loadAll([]string{filename}, enc, false)
|
l := &Loader{Encoding: enc}
|
||||||
|
return l.LoadAll([]string{filename})
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadFiles reads multiple files in the given order into
|
// LoadFiles reads multiple files in the given order into
|
||||||
// a Properties struct. If 'ignoreMissing' is true then
|
// a Properties struct. If 'ignoreMissing' is true then
|
||||||
// non-existent files will not be reported as error.
|
// non-existent files will not be reported as error.
|
||||||
func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
func LoadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||||
return loadAll(filenames, enc, ignoreMissing)
|
l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(filenames)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadURL reads the content of the URL into a Properties struct.
|
// LoadURL reads the content of the URL into a Properties struct.
|
||||||
//
|
// See Loader#LoadURL for details.
|
||||||
// The encoding is determined via the Content-Type header which
|
|
||||||
// should be set to 'text/plain'. If the 'charset' parameter is
|
|
||||||
// missing, 'iso-8859-1' or 'latin1' the encoding is set to
|
|
||||||
// ISO-8859-1. If the 'charset' parameter is set to 'utf-8' the
|
|
||||||
// encoding is set to UTF-8. A missing content type header is
|
|
||||||
// interpreted as 'text/plain; charset=utf-8'.
|
|
||||||
func LoadURL(url string) (*Properties, error) {
|
func LoadURL(url string) (*Properties, error) {
|
||||||
return loadAll([]string{url}, UTF8, false)
|
l := &Loader{Encoding: UTF8}
|
||||||
|
return l.LoadAll([]string{url})
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadURLs reads the content of multiple URLs in the given order into a
|
// LoadURLs reads the content of multiple URLs in the given order into a
|
||||||
// Properties struct. If 'ignoreMissing' is true then a 404 status code will
|
// Properties struct. If IgnoreMissing is true then a 404 status code will
|
||||||
// not be reported as error. See LoadURL for the Content-Type header
|
// not be reported as error. See Loader#LoadURL for the Content-Type header
|
||||||
// and the encoding.
|
// and the encoding.
|
||||||
func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
|
func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
|
||||||
return loadAll(urls, UTF8, ignoreMissing)
|
l := &Loader{Encoding: UTF8, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(urls)
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadAll reads the content of multiple URLs or files in the given order into a
|
// LoadAll reads the content of multiple URLs or files in the given order into a
|
||||||
|
@ -79,7 +213,8 @@ func LoadURLs(urls []string, ignoreMissing bool) (*Properties, error) {
|
||||||
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
// not be reported as error. Encoding sets the encoding for files. For the URLs please see
|
||||||
// LoadURL for the Content-Type header and the encoding.
|
// LoadURL for the Content-Type header and the encoding.
|
||||||
func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
func LoadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
||||||
return loadAll(names, enc, ignoreMissing)
|
l := &Loader{Encoding: enc, IgnoreMissing: ignoreMissing}
|
||||||
|
return l.LoadAll(names)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MustLoadString reads an UTF8 string into a Properties struct and
|
// MustLoadString reads an UTF8 string into a Properties struct and
|
||||||
|
@ -122,90 +257,6 @@ func MustLoadAll(names []string, enc Encoding, ignoreMissing bool) *Properties {
|
||||||
return must(LoadAll(names, enc, ignoreMissing))
|
return must(LoadAll(names, enc, ignoreMissing))
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadBuf(buf []byte, enc Encoding) (*Properties, error) {
|
|
||||||
p, err := parse(convert(buf, enc))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, p.check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadAll(names []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
|
||||||
result := NewProperties()
|
|
||||||
for _, name := range names {
|
|
||||||
n, err := expandName(name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var p *Properties
|
|
||||||
if strings.HasPrefix(n, "http://") || strings.HasPrefix(n, "https://") {
|
|
||||||
p, err = loadURL(n, ignoreMissing)
|
|
||||||
} else {
|
|
||||||
p, err = loadFile(n, enc, ignoreMissing)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
result.Merge(p)
|
|
||||||
|
|
||||||
}
|
|
||||||
return result, result.check()
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadFile(filename string, enc Encoding, ignoreMissing bool) (*Properties, error) {
|
|
||||||
data, err := ioutil.ReadFile(filename)
|
|
||||||
if err != nil {
|
|
||||||
if ignoreMissing && os.IsNotExist(err) {
|
|
||||||
LogPrintf("properties: %s not found. skipping", filename)
|
|
||||||
return NewProperties(), nil
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
p, err := parse(convert(data, enc))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadURL(url string, ignoreMissing bool) (*Properties, error) {
|
|
||||||
resp, err := http.Get(url)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("properties: error fetching %q. %s", url, err)
|
|
||||||
}
|
|
||||||
if resp.StatusCode == 404 && ignoreMissing {
|
|
||||||
LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode)
|
|
||||||
return NewProperties(), nil
|
|
||||||
}
|
|
||||||
if resp.StatusCode != 200 {
|
|
||||||
return nil, fmt.Errorf("properties: %s returned %d", url, resp.StatusCode)
|
|
||||||
}
|
|
||||||
body, err := ioutil.ReadAll(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
|
|
||||||
}
|
|
||||||
if err = resp.Body.Close(); err != nil {
|
|
||||||
return nil, fmt.Errorf("properties: %s error reading response. %s", url, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
ct := resp.Header.Get("Content-Type")
|
|
||||||
var enc Encoding
|
|
||||||
switch strings.ToLower(ct) {
|
|
||||||
case "text/plain", "text/plain; charset=iso-8859-1", "text/plain; charset=latin1":
|
|
||||||
enc = ISO_8859_1
|
|
||||||
case "", "text/plain; charset=utf-8":
|
|
||||||
enc = UTF8
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("properties: invalid content type %s", ct)
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := parse(convert(body, enc))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func must(p *Properties, err error) *Properties {
|
func must(p *Properties, err error) *Properties {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrorHandler(err)
|
ErrorHandler(err)
|
||||||
|
@ -226,7 +277,7 @@ func expandName(name string) (string, error) {
|
||||||
// first 256 unicode code points cover ISO-8859-1.
|
// first 256 unicode code points cover ISO-8859-1.
|
||||||
func convert(buf []byte, enc Encoding) string {
|
func convert(buf []byte, enc Encoding) string {
|
||||||
switch enc {
|
switch enc {
|
||||||
case UTF8:
|
case utf8Default, UTF8:
|
||||||
return string(buf)
|
return string(buf)
|
||||||
case ISO_8859_1:
|
case ISO_8859_1:
|
||||||
runes := make([]rune, len(buf))
|
runes := make([]rune, len(buf))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
@ -83,6 +83,17 @@ func NewProperties() *Properties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load reads a buffer into the given Properties struct.
|
||||||
|
func (p *Properties) Load(buf []byte, enc Encoding) error {
|
||||||
|
l := &Loader{Encoding: enc, DisableExpansion: p.DisableExpansion}
|
||||||
|
newProperties, err := l.LoadBytes(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.Merge(newProperties)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Get returns the expanded value for the given key if exists.
|
// Get returns the expanded value for the given key if exists.
|
||||||
// Otherwise, ok is false.
|
// Otherwise, ok is false.
|
||||||
func (p *Properties) Get(key string) (value string, ok bool) {
|
func (p *Properties) Get(key string) (value string, ok bool) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// Copyright 2017 Frank Schroeder. All rights reserved.
|
// Copyright 2018 Frank Schroeder. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ You may be licensed to use source code to create compiled versions not produced
|
||||||
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
|
2. Under a commercial license available from Mattermost, Inc. by contacting commercial@mattermost.com
|
||||||
|
|
||||||
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
|
You are licensed to use the source code in Admin Tools and Configuration Files (templates/, config/, model/,
|
||||||
webapp/client, webapp/fonts, webapp/i18n, webapp/images and all subdirectories thereof) under the Apache License v2.0.
|
plugin/ and all subdirectories thereof) under the Apache License v2.0.
|
||||||
|
|
||||||
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
|
We promise that we will not enforce the copyleft provisions in AGPL v3.0 against you if your application (a) does not
|
||||||
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
|
link to the Mattermost Platform directly, but exclusively uses the Mattermost Admin Tools and Configuration Files, and
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,50 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultLog manually encodes the log to STDOUT, providing a basic, default logging implementation
|
||||||
|
// before mlog is fully configured.
|
||||||
|
func defaultLog(level, msg string, fields ...Field) {
|
||||||
|
log := struct {
|
||||||
|
Level string `json:"level"`
|
||||||
|
Message string `json:"msg"`
|
||||||
|
Fields []Field `json:"fields,omitempty"`
|
||||||
|
}{
|
||||||
|
level,
|
||||||
|
msg,
|
||||||
|
fields,
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, err := json.Marshal(log); err != nil {
|
||||||
|
fmt.Printf(`{"level":"error","msg":"failed to encode log message"}%s`, "\n")
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s\n", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultDebugLog(msg string, fields ...Field) {
|
||||||
|
defaultLog("debug", msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultInfoLog(msg string, fields ...Field) {
|
||||||
|
defaultLog("info", msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultWarnLog(msg string, fields ...Field) {
|
||||||
|
defaultLog("warn", msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultErrorLog(msg string, fields ...Field) {
|
||||||
|
defaultLog("error", msg, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultCriticalLog(msg string, fields ...Field) {
|
||||||
|
// We map critical to error in zap, so be consistent.
|
||||||
|
defaultLog("error", msg, fields...)
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
var globalLogger *Logger
|
||||||
|
|
||||||
|
func InitGlobalLogger(logger *Logger) {
|
||||||
|
glob := *logger
|
||||||
|
glob.zap = glob.zap.WithOptions(zap.AddCallerSkip(1))
|
||||||
|
globalLogger = &glob
|
||||||
|
Debug = globalLogger.Debug
|
||||||
|
Info = globalLogger.Info
|
||||||
|
Warn = globalLogger.Warn
|
||||||
|
Error = globalLogger.Error
|
||||||
|
Critical = globalLogger.Critical
|
||||||
|
}
|
||||||
|
|
||||||
|
func RedirectStdLog(logger *Logger) {
|
||||||
|
zap.RedirectStdLogAt(logger.zap.With(zap.String("source", "stdlog")).WithOptions(zap.AddCallerSkip(-2)), zapcore.ErrorLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogFunc func(string, ...Field)
|
||||||
|
|
||||||
|
// DON'T USE THIS Modify the level on the app logger
|
||||||
|
func GloballyDisableDebugLogForTest() {
|
||||||
|
globalLogger.consoleLevel.SetLevel(zapcore.ErrorLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DON'T USE THIS Modify the level on the app logger
|
||||||
|
func GloballyEnableDebugLogForTest() {
|
||||||
|
globalLogger.consoleLevel.SetLevel(zapcore.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
var Debug LogFunc = defaultDebugLog
|
||||||
|
var Info LogFunc = defaultInfoLog
|
||||||
|
var Warn LogFunc = defaultWarnLog
|
||||||
|
var Error LogFunc = defaultErrorLog
|
||||||
|
var Critical LogFunc = defaultCriticalLog
|
|
@ -0,0 +1,173 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
"gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Very verbose messages for debugging specific issues
|
||||||
|
LevelDebug = "debug"
|
||||||
|
// Default log level, informational
|
||||||
|
LevelInfo = "info"
|
||||||
|
// Warnings are messages about possible issues
|
||||||
|
LevelWarn = "warn"
|
||||||
|
// Errors are messages about things we know are problems
|
||||||
|
LevelError = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type and function aliases from zap to limit the libraries scope into MM code
|
||||||
|
type Field = zapcore.Field
|
||||||
|
|
||||||
|
var Int64 = zap.Int64
|
||||||
|
var Int = zap.Int
|
||||||
|
var Uint32 = zap.Uint32
|
||||||
|
var String = zap.String
|
||||||
|
var Any = zap.Any
|
||||||
|
var Err = zap.Error
|
||||||
|
var Bool = zap.Bool
|
||||||
|
|
||||||
|
type LoggerConfiguration struct {
|
||||||
|
EnableConsole bool
|
||||||
|
ConsoleJson bool
|
||||||
|
ConsoleLevel string
|
||||||
|
EnableFile bool
|
||||||
|
FileJson bool
|
||||||
|
FileLevel string
|
||||||
|
FileLocation string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
zap *zap.Logger
|
||||||
|
consoleLevel zap.AtomicLevel
|
||||||
|
fileLevel zap.AtomicLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
func getZapLevel(level string) zapcore.Level {
|
||||||
|
switch level {
|
||||||
|
case LevelInfo:
|
||||||
|
return zapcore.InfoLevel
|
||||||
|
case LevelWarn:
|
||||||
|
return zapcore.WarnLevel
|
||||||
|
case LevelDebug:
|
||||||
|
return zapcore.DebugLevel
|
||||||
|
case LevelError:
|
||||||
|
return zapcore.ErrorLevel
|
||||||
|
default:
|
||||||
|
return zapcore.InfoLevel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEncoder(json bool) zapcore.Encoder {
|
||||||
|
encoderConfig := zap.NewProductionEncoderConfig()
|
||||||
|
if json {
|
||||||
|
return zapcore.NewJSONEncoder(encoderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
return zapcore.NewConsoleEncoder(encoderConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(config *LoggerConfiguration) *Logger {
|
||||||
|
cores := []zapcore.Core{}
|
||||||
|
logger := &Logger{
|
||||||
|
consoleLevel: zap.NewAtomicLevelAt(getZapLevel(config.ConsoleLevel)),
|
||||||
|
fileLevel: zap.NewAtomicLevelAt(getZapLevel(config.FileLevel)),
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.EnableConsole {
|
||||||
|
writer := zapcore.Lock(os.Stdout)
|
||||||
|
core := zapcore.NewCore(makeEncoder(config.ConsoleJson), writer, logger.consoleLevel)
|
||||||
|
cores = append(cores, core)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.EnableFile {
|
||||||
|
writer := zapcore.AddSync(&lumberjack.Logger{
|
||||||
|
Filename: config.FileLocation,
|
||||||
|
MaxSize: 100,
|
||||||
|
Compress: true,
|
||||||
|
})
|
||||||
|
core := zapcore.NewCore(makeEncoder(config.FileJson), writer, logger.fileLevel)
|
||||||
|
cores = append(cores, core)
|
||||||
|
}
|
||||||
|
|
||||||
|
combinedCore := zapcore.NewTee(cores...)
|
||||||
|
|
||||||
|
logger.zap = zap.New(combinedCore,
|
||||||
|
zap.AddCallerSkip(1),
|
||||||
|
zap.AddCaller(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) ChangeLevels(config *LoggerConfiguration) {
|
||||||
|
l.consoleLevel.SetLevel(getZapLevel(config.ConsoleLevel))
|
||||||
|
l.fileLevel.SetLevel(getZapLevel(config.FileLevel))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) SetConsoleLevel(level string) {
|
||||||
|
l.consoleLevel.SetLevel(getZapLevel(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) With(fields ...Field) *Logger {
|
||||||
|
newlogger := *l
|
||||||
|
newlogger.zap = newlogger.zap.With(fields...)
|
||||||
|
return &newlogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) StdLog(fields ...Field) *log.Logger {
|
||||||
|
return zap.NewStdLog(l.With(fields...).zap.WithOptions(getStdLogOption()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StdLogWriter returns a writer that can be hooked up to the output of a golang standard logger
|
||||||
|
// anything written will be interpreted as log entries accordingly
|
||||||
|
func (l *Logger) StdLogWriter() io.Writer {
|
||||||
|
newLogger := *l
|
||||||
|
newLogger.zap = newLogger.zap.WithOptions(zap.AddCallerSkip(4), getStdLogOption())
|
||||||
|
f := newLogger.Info
|
||||||
|
return &loggerWriter{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) WithCallerSkip(skip int) *Logger {
|
||||||
|
newlogger := *l
|
||||||
|
newlogger.zap = newlogger.zap.WithOptions(zap.AddCallerSkip(skip))
|
||||||
|
return &newlogger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Made for the plugin interface, wraps mlog in a simpler interface
|
||||||
|
// at the cost of performance
|
||||||
|
func (l *Logger) Sugar() *SugarLogger {
|
||||||
|
return &SugarLogger{
|
||||||
|
wrappedLogger: l,
|
||||||
|
zapSugar: l.zap.Sugar(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Debug(message string, fields ...Field) {
|
||||||
|
l.zap.Debug(message, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Info(message string, fields ...Field) {
|
||||||
|
l.zap.Info(message, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Warn(message string, fields ...Field) {
|
||||||
|
l.zap.Warn(message, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Error(message string, fields ...Field) {
|
||||||
|
l.zap.Error(message, fields...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Critical(message string, fields ...Field) {
|
||||||
|
l.zap.Error(message, fields...)
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implementation of zapcore.Core to interpret log messages from a standard logger
|
||||||
|
// and translate the levels to zapcore levels.
|
||||||
|
type stdLogLevelInterpreterCore struct {
|
||||||
|
wrappedCore zapcore.Core
|
||||||
|
}
|
||||||
|
|
||||||
|
func stdLogInterpretZapEntry(entry zapcore.Entry) zapcore.Entry {
|
||||||
|
message := entry.Message
|
||||||
|
if strings.Index(message, "[DEBUG]") == 0 {
|
||||||
|
entry.Level = zapcore.DebugLevel
|
||||||
|
entry.Message = message[7:]
|
||||||
|
} else if strings.Index(message, "[DEBG]") == 0 {
|
||||||
|
entry.Level = zapcore.DebugLevel
|
||||||
|
entry.Message = message[6:]
|
||||||
|
} else if strings.Index(message, "[WARN]") == 0 {
|
||||||
|
entry.Level = zapcore.WarnLevel
|
||||||
|
entry.Message = message[6:]
|
||||||
|
} else if strings.Index(message, "[ERROR]") == 0 {
|
||||||
|
entry.Level = zapcore.ErrorLevel
|
||||||
|
entry.Message = message[7:]
|
||||||
|
} else if strings.Index(message, "[EROR]") == 0 {
|
||||||
|
entry.Level = zapcore.ErrorLevel
|
||||||
|
entry.Message = message[6:]
|
||||||
|
} else if strings.Index(message, "[ERR]") == 0 {
|
||||||
|
entry.Level = zapcore.ErrorLevel
|
||||||
|
entry.Message = message[5:]
|
||||||
|
} else if strings.Index(message, "[INFO]") == 0 {
|
||||||
|
entry.Level = zapcore.InfoLevel
|
||||||
|
entry.Message = message[6:]
|
||||||
|
}
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdLogLevelInterpreterCore) Enabled(lvl zapcore.Level) bool {
|
||||||
|
return s.wrappedCore.Enabled(lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdLogLevelInterpreterCore) With(fields []zapcore.Field) zapcore.Core {
|
||||||
|
return s.wrappedCore.With(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdLogLevelInterpreterCore) Check(entry zapcore.Entry, checkedEntry *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||||
|
entry = stdLogInterpretZapEntry(entry)
|
||||||
|
return s.wrappedCore.Check(entry, checkedEntry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdLogLevelInterpreterCore) Write(entry zapcore.Entry, fields []zapcore.Field) error {
|
||||||
|
entry = stdLogInterpretZapEntry(entry)
|
||||||
|
return s.wrappedCore.Write(entry, fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stdLogLevelInterpreterCore) Sync() error {
|
||||||
|
return s.wrappedCore.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStdLogOption() zap.Option {
|
||||||
|
return zap.WrapCore(
|
||||||
|
func(core zapcore.Core) zapcore.Core {
|
||||||
|
return &stdLogLevelInterpreterCore{core}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type loggerWriter struct {
|
||||||
|
logFunc func(msg string, fields ...Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *loggerWriter) Write(p []byte) (int, error) {
|
||||||
|
trimmed := string(bytes.TrimSpace(p))
|
||||||
|
for _, line := range strings.Split(trimmed, "\n") {
|
||||||
|
l.logFunc(string(line))
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package mlog
|
||||||
|
|
||||||
|
import "go.uber.org/zap"
|
||||||
|
|
||||||
|
// Made for the plugin interface, use the regular logger for other uses
|
||||||
|
type SugarLogger struct {
|
||||||
|
wrappedLogger *Logger
|
||||||
|
zapSugar *zap.SugaredLogger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SugarLogger) Debug(msg string, keyValuePairs ...interface{}) {
|
||||||
|
l.zapSugar.Debugw(msg, keyValuePairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SugarLogger) Info(msg string, keyValuePairs ...interface{}) {
|
||||||
|
l.zapSugar.Infow(msg, keyValuePairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SugarLogger) Error(msg string, keyValuePairs ...interface{}) {
|
||||||
|
l.zapSugar.Errorw(msg, keyValuePairs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *SugarLogger) Warn(msg string, keyValuePairs ...interface{}) {
|
||||||
|
l.zapSugar.Warnw(msg, keyValuePairs...)
|
||||||
|
}
|
|
@ -74,41 +74,23 @@ func (me *AccessData) IsExpired() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ad *AccessData) ToJson() string {
|
func (ad *AccessData) ToJson() string {
|
||||||
b, err := json.Marshal(ad)
|
b, _ := json.Marshal(ad)
|
||||||
if err != nil {
|
return string(b)
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccessDataFromJson(data io.Reader) *AccessData {
|
func AccessDataFromJson(data io.Reader) *AccessData {
|
||||||
decoder := json.NewDecoder(data)
|
var ad *AccessData
|
||||||
var ad AccessData
|
json.NewDecoder(data).Decode(&ad)
|
||||||
err := decoder.Decode(&ad)
|
return ad
|
||||||
if err == nil {
|
|
||||||
return &ad
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ar *AccessResponse) ToJson() string {
|
func (ar *AccessResponse) ToJson() string {
|
||||||
b, err := json.Marshal(ar)
|
b, _ := json.Marshal(ar)
|
||||||
if err != nil {
|
return string(b)
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AccessResponseFromJson(data io.Reader) *AccessResponse {
|
func AccessResponseFromJson(data io.Reader) *AccessResponse {
|
||||||
decoder := json.NewDecoder(data)
|
var ar *AccessResponse
|
||||||
var ar AccessResponse
|
json.NewDecoder(data).Decode(&ar)
|
||||||
err := decoder.Decode(&ar)
|
return ar
|
||||||
if err == nil {
|
|
||||||
return &ar
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -16,23 +16,14 @@ type AnalyticsRow struct {
|
||||||
type AnalyticsRows []*AnalyticsRow
|
type AnalyticsRows []*AnalyticsRow
|
||||||
|
|
||||||
func (me *AnalyticsRow) ToJson() string {
|
func (me *AnalyticsRow) ToJson() string {
|
||||||
b, err := json.Marshal(me)
|
b, _ := json.Marshal(me)
|
||||||
if err != nil {
|
return string(b)
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
|
func AnalyticsRowFromJson(data io.Reader) *AnalyticsRow {
|
||||||
decoder := json.NewDecoder(data)
|
var me *AnalyticsRow
|
||||||
var me AnalyticsRow
|
json.NewDecoder(data).Decode(&me)
|
||||||
err := decoder.Decode(&me)
|
return me
|
||||||
if err == nil {
|
|
||||||
return &me
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (me AnalyticsRows) ToJson() string {
|
func (me AnalyticsRows) ToJson() string {
|
||||||
|
@ -44,12 +35,7 @@ func (me AnalyticsRows) ToJson() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
|
func AnalyticsRowsFromJson(data io.Reader) AnalyticsRows {
|
||||||
decoder := json.NewDecoder(data)
|
|
||||||
var me AnalyticsRows
|
var me AnalyticsRows
|
||||||
err := decoder.Decode(&me)
|
json.NewDecoder(data).Decode(&me)
|
||||||
if err == nil {
|
return me
|
||||||
return me
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -19,21 +19,12 @@ type Audit struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Audit) ToJson() string {
|
func (o *Audit) ToJson() string {
|
||||||
b, err := json.Marshal(o)
|
b, _ := json.Marshal(o)
|
||||||
if err != nil {
|
return string(b)
|
||||||
return ""
|
|
||||||
} else {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuditFromJson(data io.Reader) *Audit {
|
func AuditFromJson(data io.Reader) *Audit {
|
||||||
decoder := json.NewDecoder(data)
|
var o *Audit
|
||||||
var o Audit
|
json.NewDecoder(data).Decode(&o)
|
||||||
err := decoder.Decode(&o)
|
return o
|
||||||
if err == nil {
|
|
||||||
return &o
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -28,12 +28,7 @@ func (o Audits) ToJson() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuditsFromJson(data io.Reader) Audits {
|
func AuditsFromJson(data io.Reader) Audits {
|
||||||
decoder := json.NewDecoder(data)
|
|
||||||
var o Audits
|
var o Audits
|
||||||
err := decoder.Decode(&o)
|
json.NewDecoder(data).Decode(&o)
|
||||||
if err == nil {
|
return o
|
||||||
return o
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue