Update dependencies (#1951)

pull/1955/head
Wim 1 year ago committed by GitHub
parent eac2a8c8dc
commit 880586bac4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -41,7 +41,7 @@ func (b *Bmattermost) handleDownloadAvatar(userid string, channel string) {
} }
} }
// nolint:wrapcheck //nolint:wrapcheck
func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error { func (b *Bmattermost) handleDownloadFile(rmsg *config.Message, id string) error {
url, _, _ := b.mc.Client.GetFileLink(id) url, _, _ := b.mc.Client.GetFileLink(id)
finfo, _, err := b.mc.Client.GetFileInfo(id) finfo, _, err := b.mc.Client.GetFileInfo(id)
@ -91,7 +91,7 @@ func (b *Bmattermost) handleMatter() {
} }
} }
// nolint:cyclop //nolint:cyclop
func (b *Bmattermost) handleMatterClient(messages chan *config.Message) { func (b *Bmattermost) handleMatterClient(messages chan *config.Message) {
for message := range b.mc.MessageChan { for message := range b.mc.MessageChan {
b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType()) b.Log.Debugf("%#v %#v", message.Raw.GetData(), message.Raw.EventType())
@ -186,7 +186,7 @@ func (b *Bmattermost) handleUploadFile(msg *config.Message) (string, error) {
return res, err return res, err
} }
// nolint:forcetypeassert //nolint:forcetypeassert
func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) { func (b *Bmattermost) handleProps(rmsg *config.Message, message *matterclient.Message) {
props := message.Post.Props props := message.Post.Props
if props == nil { if props == nil {

@ -66,7 +66,7 @@ func (b *Bmattermost) doConnectWebhookURL() error {
return nil return nil
} }
// nolint:wrapcheck //nolint:wrapcheck
func (b *Bmattermost) apiLogin() error { func (b *Bmattermost) apiLogin() error {
password := b.GetString("Password") password := b.GetString("Password")
if b.GetString("Token") != "" { if b.GetString("Token") != "" {
@ -171,7 +171,7 @@ func (b *Bmattermost) sendWebhook(msg config.Message) (string, error) {
} }
// skipMessages returns true if this message should not be handled // skipMessages returns true if this message should not be handled
// nolint:gocyclo,cyclop //nolint:gocyclo,cyclop
func (b *Bmattermost) skipMessage(message *matterclient.Message) bool { func (b *Bmattermost) skipMessage(message *matterclient.Message) bool {
// Handle join/leave // Handle join/leave
if message.Type == "system_join_leave" || if message.Type == "system_join_leave" ||

@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"go.mau.fi/whatsmeow"
"go.mau.fi/whatsmeow/store" "go.mau.fi/whatsmeow/store"
"go.mau.fi/whatsmeow/store/sqlstore" "go.mau.fi/whatsmeow/store/sqlstore"
"go.mau.fi/whatsmeow/types" "go.mau.fi/whatsmeow/types"
@ -18,7 +19,7 @@ type ProfilePicInfo struct {
Status int16 `json:"status"` Status int16 `json:"status"`
} }
func (b *Bwhatsapp) reloadContacts(){ func (b *Bwhatsapp) reloadContacts() {
if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil { if _, err := b.wc.Store.Contacts.GetAllContacts(); err != nil {
b.Log.Errorf("error on update of contacts: %v", err) b.Log.Errorf("error on update of contacts: %v", err)
} }
@ -48,7 +49,7 @@ func (b *Bwhatsapp) getSenderName(info types.MessageInfo) string {
if exists && sender.FullName != "" { if exists && sender.FullName != "" {
return sender.FullName return sender.FullName
} }
if info.PushName != "" { if info.PushName != "" {
return info.PushName return info.PushName
} }
@ -56,7 +57,7 @@ func (b *Bwhatsapp) getSenderName(info types.MessageInfo) string {
if exists && sender.FirstName != "" { if exists && sender.FirstName != "" {
return sender.FirstName return sender.FirstName
} }
return "Someone" return "Someone"
} }
@ -71,11 +72,11 @@ func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
if !exists { if !exists {
return "someone" return "someone"
} }
if exists && sender.FullName != "" { if exists && sender.FullName != "" {
return sender.FullName return sender.FullName
} }
if exists && sender.PushName != "" { if exists && sender.PushName != "" {
return sender.PushName return sender.PushName
} }
@ -83,13 +84,16 @@ func (b *Bwhatsapp) getSenderNotify(senderJid types.JID) string {
if exists && sender.FirstName != "" { if exists && sender.FirstName != "" {
return sender.FirstName return sender.FirstName
} }
return "someone" return "someone"
} }
func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) { func (b *Bwhatsapp) GetProfilePicThumb(jid string) (*types.ProfilePictureInfo, error) {
pjid, _ := types.ParseJID(jid) pjid, _ := types.ParseJID(jid)
info, err := b.wc.GetProfilePictureInfo(pjid, true, "")
info, err := b.wc.GetProfilePictureInfo(pjid, &whatsmeow.GetProfilePictureParams{
Preview: true,
})
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get avatar: %v", err) return nil, fmt.Errorf("failed to get avatar: %v", err)
} }

@ -238,7 +238,7 @@ func (b *Bwhatsapp) PostDocumentMessage(msg config.Message, filetype string) (st
b.Log.Debugf("=> Sending %#v as a document", msg) b.Log.Debugf("=> Sending %#v as a document", msg)
ID := whatsmeow.GenerateMessageID() ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, ID, &message) _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err return ID, err
} }
@ -272,7 +272,7 @@ func (b *Bwhatsapp) PostImageMessage(msg config.Message, filetype string) (strin
b.Log.Debugf("=> Sending %#v as an image", msg) b.Log.Debugf("=> Sending %#v as an image", msg)
ID := whatsmeow.GenerateMessageID() ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, ID, &message) _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err return ID, err
} }
@ -305,7 +305,7 @@ func (b *Bwhatsapp) PostVideoMessage(msg config.Message, filetype string) (strin
b.Log.Debugf("=> Sending %#v as a video", msg) b.Log.Debugf("=> Sending %#v as a video", msg)
ID := whatsmeow.GenerateMessageID() ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, ID, &message) _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err return ID, err
} }
@ -335,14 +335,14 @@ func (b *Bwhatsapp) PostAudioMessage(msg config.Message, filetype string) (strin
b.Log.Debugf("=> Sending %#v as audio", msg) b.Log.Debugf("=> Sending %#v as audio", msg)
ID := whatsmeow.GenerateMessageID() ID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, ID, &message) _, err = b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
var captionMessage proto.Message var captionMessage proto.Message
caption := msg.Username + fi.Comment + "\u2B06" // the char on the end is upwards arrow emoji caption := msg.Username + fi.Comment + "\u2B06" // the char on the end is upwards arrow emoji
captionMessage.Conversation = &caption captionMessage.Conversation = &caption
captionID := whatsmeow.GenerateMessageID() captionID := whatsmeow.GenerateMessageID()
_, err = b.wc.SendMessage(context.TODO(), groupJID, captionID, &captionMessage) _, err = b.wc.SendMessage(context.TODO(), groupJID, &captionMessage, whatsmeow.SendRequestExtra{ID: captionID})
return ID, err return ID, err
} }
@ -389,10 +389,10 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
switch filetype { switch filetype {
case "image/jpeg", "image/png", "image/gif": case "image/jpeg", "image/png", "image/gif":
return b.PostImageMessage(msg, filetype) return b.PostImageMessage(msg, filetype)
case "video/mp4", "video/3gpp": //TODO: Check if codecs are supported by WA case "video/mp4", "video/3gpp": // TODO: Check if codecs are supported by WA
return b.PostVideoMessage(msg, filetype) return b.PostVideoMessage(msg, filetype)
case "audio/ogg": case "audio/ogg":
return b.PostAudioMessage(msg, "audio/ogg; codecs=opus") //TODO: Detect if it is actually OPUS return b.PostAudioMessage(msg, "audio/ogg; codecs=opus") // TODO: Detect if it is actually OPUS
case "audio/aac", "audio/mp4", "audio/amr", "audio/mpeg": case "audio/aac", "audio/mp4", "audio/amr", "audio/mpeg":
return b.PostAudioMessage(msg, filetype) return b.PostAudioMessage(msg, filetype)
default: default:
@ -407,7 +407,7 @@ func (b *Bwhatsapp) Send(msg config.Message) (string, error) {
message.Conversation = &text message.Conversation = &text
ID := whatsmeow.GenerateMessageID() ID := whatsmeow.GenerateMessageID()
_, err := b.wc.SendMessage(context.TODO(), groupJID, ID, &message) _, err := b.wc.SendMessage(context.TODO(), groupJID, &message, whatsmeow.SendRequestExtra{ID: ID})
return ID, err return ID, err
} }

@ -7,22 +7,22 @@ require (
github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560 github.com/Philipp15b/go-steam v1.0.1-0.20200727090957-6ae9b3c0a560
github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c github.com/Rhymen/go-whatsapp v0.1.2-0.20211102134409-31a2e740845c
github.com/SevereCloud/vksdk/v2 v2.15.0 github.com/SevereCloud/vksdk/v2 v2.15.0
github.com/bwmarrin/discordgo v0.26.1 github.com/bwmarrin/discordgo v0.27.0
github.com/d5/tengo/v2 v2.13.0 github.com/d5/tengo/v2 v2.13.0
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c github.com/gomarkdown/markdown v0.0.0-20221013030248-663e2500819c
github.com/google/gops v0.3.25 github.com/google/gops v0.3.26
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/websocket v1.5.0 github.com/gorilla/websocket v1.5.0
github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa github.com/harmony-development/shibshib v0.0.0-20220101224523-c98059d09cfa
github.com/hashicorp/golang-lru v0.5.4 github.com/hashicorp/golang-lru v0.6.0
github.com/jpillora/backoff v1.0.0 github.com/jpillora/backoff v1.0.0
github.com/keybase/go-keybase-chat-bot v0.0.0-20221010143313-3c4907bab5dd github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20
github.com/kyokomi/emoji/v2 v2.2.10 github.com/kyokomi/emoji/v2 v2.2.11
github.com/labstack/echo/v4 v4.9.1 github.com/labstack/echo/v4 v4.10.0
github.com/lrstanley/girc v0.0.0-20220821023908-8e7df6d970f8 github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4
github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696 github.com/matterbridge/Rocket.Chat.Go.SDK v0.0.0-20211016222428-79310a412696
github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be github.com/matterbridge/go-xmpp v0.0.0-20211030125215-791a06c5f1be
github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27 github.com/matterbridge/gomatrix v0.0.0-20220411225302-271e5088ea27
@ -37,25 +37,25 @@ require (
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
github.com/rs/xid v1.4.0 github.com/rs/xid v1.4.0
github.com/russross/blackfriday v1.6.0 github.com/russross/blackfriday v1.6.0
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
github.com/shazow/ssh-chat v1.10.1 github.com/shazow/ssh-chat v1.10.1
github.com/sirupsen/logrus v1.9.0 github.com/sirupsen/logrus v1.9.0
github.com/slack-go/slack v0.11.3 github.com/slack-go/slack v0.12.1
github.com/spf13/viper v1.12.0 github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1 github.com/stretchr/testify v1.8.1
github.com/vincent-petithory/dataurl v1.0.0 github.com/vincent-petithory/dataurl v1.0.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible github.com/writeas/go-strip-markdown v2.0.1+incompatible
github.com/yaegashi/msgraph.go v0.1.4 github.com/yaegashi/msgraph.go v0.1.4
github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289 github.com/zfjagann/golang-ring v0.0.0-20220330170733-19bcea1b6289
go.mau.fi/whatsmeow v0.0.0-20221126173344-e660988acdbc go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22
golang.org/x/image v0.1.0 golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.1.0 golang.org/x/oauth2 v0.4.0
golang.org/x/text v0.4.0 golang.org/x/text v0.6.0
gomod.garykim.dev/nc-talk v0.3.0 gomod.garykim.dev/nc-talk v0.3.0
google.golang.org/protobuf v1.28.1 google.golang.org/protobuf v1.28.1
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
layeh.com/gumble v0.0.0-20200818122324-146f9205029b layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
modernc.org/sqlite v1.19.5 modernc.org/sqlite v1.20.3
) )
require ( require (
@ -83,12 +83,12 @@ require (
github.com/klauspost/compress v1.15.8 // indirect github.com/klauspost/compress v1.15.8 // indirect
github.com/klauspost/cpuid/v2 v2.0.12 // indirect github.com/klauspost/cpuid/v2 v2.0.12 // indirect
github.com/labstack/gommon v0.4.0 // indirect github.com/labstack/gommon v0.4.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect github.com/mattermost/ldap v0.0.0-20201202150706-ee0e6284187d // indirect
github.com/mattermost/logr v1.0.13 // indirect github.com/mattermost/logr v1.0.13 // indirect
github.com/mattermost/logr/v2 v2.0.15 // indirect github.com/mattermost/logr/v2 v2.0.15 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
@ -105,7 +105,7 @@ require (
github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pborman/uuid v1.2.1 // indirect github.com/pborman/uuid v1.2.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.1 // indirect github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/philhofer/fwd v1.1.1 // indirect github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
@ -116,39 +116,39 @@ require (
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 // indirect
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 // indirect
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 // indirect
github.com/spf13/afero v1.8.2 // indirect github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.3.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect
github.com/tinylib/msgp v1.1.6 // indirect github.com/tinylib/msgp v1.1.6 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/wiggin77/cfg v1.0.2 // indirect github.com/wiggin77/cfg v1.0.2 // indirect
github.com/wiggin77/merror v1.0.3 // indirect github.com/wiggin77/merror v1.0.3 // indirect
github.com/wiggin77/srslog v1.0.1 // indirect github.com/wiggin77/srslog v1.0.1 // indirect
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf // indirect go.mau.fi/libsignal v0.1.0 // indirect
go.uber.org/atomic v1.9.0 // indirect go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.7.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.17.0 // indirect go.uber.org/zap v1.21.0 // indirect
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a // indirect golang.org/x/crypto v0.4.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.1.0 // indirect golang.org/x/net v0.5.0 // indirect
golang.org/x/sys v0.1.0 // indirect golang.org/x/sys v0.4.0 // indirect
golang.org/x/term v0.1.0 // indirect golang.org/x/term v0.4.0 // indirect
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect golang.org/x/time v0.2.0 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.21.5 // indirect modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect modernc.org/opt v0.1.3 // indirect

132
go.sum

@ -234,6 +234,8 @@ github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -285,8 +287,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/bwmarrin/discordgo v0.26.1 h1:AIrM+g3cl+iYBr4yBxCBp9tD9jR3K7upEjl0d89FRkE= github.com/bwmarrin/discordgo v0.27.0 h1:4ZK9KN+rGIxZ0fdGTmgdCcliQeW8Zhu6MnlFI92nf0Q=
github.com/bwmarrin/discordgo v0.26.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/bwmarrin/discordgo v0.27.0/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg= github.com/cenkalti/backoff/v4 v4.0.2/go.mod h1:eEew/i+1Q6OrCDZh3WiXYv3+nJwBASZ8Bog/87DQnVg=
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -433,6 +435,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -726,13 +729,14 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs= github.com/google/go-github/v35 v35.2.0/go.mod h1:s0515YVTI+IMrDoy9Y4pHt9ShGpzHvHO8rZ7L7acgvs=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gops v0.3.25 h1:Pf6uw+cO6pDhc7HJ71NiG0x8dyQTeQcmg3HQFF39qVw= github.com/google/gops v0.3.26 h1:Ziyfd8sEhWVbrCIy59c1WOKodI63Jzojwm0JSZbBPS4=
github.com/google/gops v0.3.25/go.mod h1:8A7ebAm0id9K3H0uOggeRVGxszSvnlURun9mg3GdYDw= github.com/google/gops v0.3.26/go.mod h1:vZ68aOXu2zJoybPyGpaHMmrCyd51DCxJoex4cO3ht/o=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -846,8 +850,9 @@ github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/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/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
@ -872,6 +877,7 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
@ -978,9 +984,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNU
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f h1:dnCYnTSltLuPMfc7dMrkz2uBUcEf/OFBR8yRh3oRT98= github.com/kettek/apng v0.0.0-20191108220231-414630eed80f h1:dnCYnTSltLuPMfc7dMrkz2uBUcEf/OFBR8yRh3oRT98=
github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q= github.com/kettek/apng v0.0.0-20191108220231-414630eed80f/go.mod h1:x78/VRQYKuCftMWS0uK5e+F5RJ7S4gSlESRWI0Prl6Q=
github.com/keybase/go-keybase-chat-bot v0.0.0-20221010143313-3c4907bab5dd h1:o22WsBgpQZs7w4faoT7Rfp2kGvEye8gRmhImW0NpRQA= github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20 h1:imPu/fy8VhpzD318SnouRyvIgJAoouEyph6+7XAZbbk=
github.com/keybase/go-keybase-chat-bot v0.0.0-20221010143313-3c4907bab5dd/go.mod h1:Yan1Krk0q1FglHdCkNrF5hFQ4Sgq8LnuG4gI2U4xQAk= github.com/keybase/go-keybase-chat-bot v0.0.0-20221220212439-e48d9abd2c20/go.mod h1:Yan1Krk0q1FglHdCkNrF5hFQ4Sgq8LnuG4gI2U4xQAk=
github.com/keybase/go-ps v0.0.0-20190827175125-91aafc93ba19/go.mod h1:hY+WOq6m2FpbvyrI93sMaypsttvaIL5nhVR92dTMUcQ=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -1030,12 +1035,12 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4= github.com/ktrysmt/go-bitbucket v0.6.4/go.mod h1:9u0v3hsd2rqCHRIpbir1oP7F58uo5dq19sBYvuMoyQ4=
github.com/kyokomi/emoji/v2 v2.2.10 h1:1z5eMVcxFifsmEoNpdeq4UahbcicgQ4FEHuzrCVwmiI= github.com/kyokomi/emoji/v2 v2.2.11 h1:Pf/ZWVTbnAVkHOLJLWjPxM/FmgyPe+d85cv/OLP5Yus=
github.com/kyokomi/emoji/v2 v2.2.10/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE= github.com/kyokomi/emoji/v2 v2.2.11/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y= github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo= github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
@ -1053,15 +1058,15 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lrstanley/girc v0.0.0-20220821023908-8e7df6d970f8 h1:OeTVm9ph5q9eq9E+HDkUbiJtVvu+kNOhuLe5Qs83sEs= github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4 h1:eOJJOM8RTmDcK1F0SqCBX/Ic1vgDnAZfdll6oik0Ups=
github.com/lrstanley/girc v0.0.0-20220821023908-8e7df6d970f8/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI= github.com/lrstanley/girc v0.0.0-20221222153823-a92667a5c9b4/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -1110,8 +1115,9 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -1330,8 +1336,8 @@ github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
@ -1454,8 +1460,8 @@ github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
@ -1469,7 +1475,7 @@ github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4 h1:zwQ1HBo5FYwn1ksMd
github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI= github.com/shazow/rateio v0.0.0-20200113175441-4461efc8bdc4/go.mod h1:vt2jWY/3Qw1bIzle5thrJWucsLuuX9iUNnp20CqCciI=
github.com/shazow/ssh-chat v1.10.1 h1:ePS+ngEYqm+yUuXegDPutysqLV2WoI22XDOeRgI6CE0= github.com/shazow/ssh-chat v1.10.1 h1:ePS+ngEYqm+yUuXegDPutysqLV2WoI22XDOeRgI6CE0=
github.com/shazow/ssh-chat v1.10.1/go.mod h1:0+7szsKylcre0vljkVnbuI6q7Odtc+QCDHxa+fFNV54= github.com/shazow/ssh-chat v1.10.1/go.mod h1:0+7szsKylcre0vljkVnbuI6q7Odtc+QCDHxa+fFNV54=
github.com/shirou/gopsutil/v3 v3.22.4/go.mod h1:D01hZJ4pVHPpCTZ3m3T2+wDF2YAGfd+H4ifUguaQzHM= github.com/shirou/gopsutil/v3 v3.22.10/go.mod h1:QNza6r4YQoydyCfo6rH0blGfKahgibh4dQmV5xdFkQk=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -1515,8 +1521,8 @@ github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882 h1:A7o8tOE
github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882/go.mod h1:5IwJoz9Pw7JsrCN4/skkxUtSWT7myuUPLhCgv6Q5vvQ= github.com/sizeofint/webpanimation v0.0.0-20210809145948-1d2b32119882/go.mod h1:5IwJoz9Pw7JsrCN4/skkxUtSWT7myuUPLhCgv6Q5vvQ=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE= github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9 h1:lpEzuenPuO1XNTeikEmvqYFcU37GVLl8SRNblzyvGBE=
github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= github.com/skip2/go-qrcode v0.0.0-20190110000554-dc11ecdae0a9/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo=
github.com/slack-go/slack v0.11.3 h1:GN7revxEMax4amCc3El9a+9SGnjmBvSUobs0QnO6ZO8= github.com/slack-go/slack v0.12.1 h1:X97b9g2hnITDtNsNe5GkGx6O2/Sz/uC20ejRZN6QxOw=
github.com/slack-go/slack v0.11.3/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw= github.com/slack-go/slack v0.12.1/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8= github.com/smartystreets/assertions v1.0.0 h1:UVQPSSmc3qtTi+zPPkCXvZX9VvW/xT/NsRvKfwY81a8=
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
@ -1535,8 +1541,8 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
@ -1548,6 +1554,7 @@ github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -1561,8 +1568,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/splitio/go-client/v6 v6.1.0/go.mod h1:CEGAEFT99Fwb32ZIRcnZoXTMXddtB6IIpTmt3RP8mnM= github.com/splitio/go-client/v6 v6.1.0/go.mod h1:CEGAEFT99Fwb32ZIRcnZoXTMXddtB6IIpTmt3RP8mnM=
github.com/splitio/go-split-commons/v3 v3.1.0/go.mod h1:29NCy20oAS4ZMy4qkwTd6277eieVDonx4V/aeDU/wUQ= github.com/splitio/go-split-commons/v3 v3.1.0/go.mod h1:29NCy20oAS4ZMy4qkwTd6277eieVDonx4V/aeDU/wUQ=
github.com/splitio/go-toolkit/v4 v4.2.0/go.mod h1:EdIHN0yzB1GTXDYQc0KdKvnjkO/jfUM2YqHVYfhD3Wo= github.com/splitio/go-toolkit/v4 v4.2.0/go.mod h1:EdIHN0yzB1GTXDYQc0KdKvnjkO/jfUM2YqHVYfhD3Wo=
@ -1591,8 +1598,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@ -1647,8 +1654,9 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU=
github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM=
@ -1728,10 +1736,10 @@ go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3C
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf h1:mzPxXBgDPHKDHMVV1tIWh7lwCiRpzCsXC0gNRX+K07c= go.mau.fi/libsignal v0.1.0 h1:vAKI/nJ5tMhdzke4cTK1fb0idJzz1JuEIpmjprueC+c=
go.mau.fi/libsignal v0.0.0-20221015105917-d970e7c3c9cf/go.mod h1:XCjaU93vl71YNRPn059jMrK0xRDwVO5gKbxoPxow9mQ= go.mau.fi/libsignal v0.1.0/go.mod h1:R8ovrTezxtUNzCQE5PH30StOQWWeBskBsWE55vMfY9I=
go.mau.fi/whatsmeow v0.0.0-20221126173344-e660988acdbc h1:uZCZs8Ju83OmM1A1+VhpZMXpvVAg5BEQNP0KBXALJBI= go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22 h1:za/zmM0hcfEKTRcLtr2zcUFE4VpUw8CndXNeV+v676c=
go.mau.fi/whatsmeow v0.0.0-20221126173344-e660988acdbc/go.mod h1:2yweL8nczvtlIxkrvCb0y8xiO13rveX9lJPambwYV/E= go.mau.fi/whatsmeow v0.0.0-20230128195103-dcbc8dd31a22/go.mod h1:TrdC8N6SnPFxWo5FiMnDIDFuVyfOLzy5dWDaUPNjcHY=
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8= go.mongodb.org/mongo-driver v1.7.0/go.mod h1:Q4oFMbo1+MSNqICAdYMlC/zSTrwCogR4R8NzkI+yfU8=
go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk=
@ -1763,16 +1771,20 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.8.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.7.0 h1:zaiO/rmgFjbmCXdSYJWQcdvOCsthmdaHfr3Gm2Kx4Ec=
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw=
golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4=
@ -1814,8 +1826,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a h1:NmSIgad6KjE6VvHciPZuNRTKxGhlPfD6OA87W/PLkqg= golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.0.0-20221012134737-56aed061732a/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -1844,8 +1856,8 @@ golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20210622092929-e6eecd499c2c/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk= golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c= golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -1957,8 +1969,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220403103023-749bd193bc2b/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180227000427-d7d64896b5ff/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1979,8 +1991,8 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y= golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
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/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -2129,13 +2141,13 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2145,15 +2157,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.2.0 h1:52I/1L54xyEQAYdtcSuxtiT84KGYTBGXwayxmIpNJhE=
golang.org/x/time v0.2.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -2447,8 +2460,9 @@ gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
@ -2523,8 +2537,8 @@ k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAG
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0=
layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY= layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14 h1:wY8eeq7DpM5iAugNbFrvuhdtmN8XM1iU+Ki5YZWjukg=
layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc= layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
@ -2639,8 +2653,8 @@ modernc.org/libc v1.11.98/go.mod h1:ynK5sbjsU77AP+nn61+k+wxUGRx9rOFcIqWYYMaDZ4c=
modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI= modernc.org/libc v1.11.99/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI= modernc.org/libc v1.11.101/go.mod h1:wLLYgEiY2D17NbBOEp+mIJJJBGSiy7fLL4ZrGGZ+8jI=
modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ= modernc.org/libc v1.11.104/go.mod h1:2MH3DaF/gCU8i/UBiVE1VFRos4o523M7zipmwH8SIgQ=
modernc.org/libc v1.21.5 h1:xBkU9fnHV+hvZuPSRszN0AXDG4M7nwPLwTWwkYcvLCI= modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.21.5/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8= modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k= modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@ -2660,8 +2674,8 @@ modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k= modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs= modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y= modernc.org/sqlite v1.14.3/go.mod h1:xMpicS1i2MJ4C8+Ap0vYBqTwYfpFvdnPE6brbFOtV2Y=
modernc.org/sqlite v1.19.5 h1:E3iHL55c1Vw1knqIeU9N7B0fSjuiOjHZo7iVMsO6U5U= modernc.org/sqlite v1.20.3 h1:SqGJMMxjj1PHusLxdYxeQSodg7Jxn9WWkaAQjKrntZs=
modernc.org/sqlite v1.19.5/go.mod h1:EsYz8rfOvLCiYTy5ZFsOYzoCcRMu98YYkwAcCw5YIYw= modernc.org/sqlite v1.20.3/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs= modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=

@ -10,10 +10,14 @@ type ComponentType uint
// MessageComponent types. // MessageComponent types.
const ( const (
ActionsRowComponent ComponentType = 1 ActionsRowComponent ComponentType = 1
ButtonComponent ComponentType = 2 ButtonComponent ComponentType = 2
SelectMenuComponent ComponentType = 3 SelectMenuComponent ComponentType = 3
TextInputComponent ComponentType = 4 TextInputComponent ComponentType = 4
UserSelectMenuComponent ComponentType = 5
RoleSelectMenuComponent ComponentType = 6
MentionableSelectMenuComponent ComponentType = 7
ChannelSelectMenuComponent ComponentType = 8
) )
// MessageComponent is a base interface for all message components. // MessageComponent is a base interface for all message components.
@ -41,7 +45,8 @@ func (umc *unmarshalableMessageComponent) UnmarshalJSON(src []byte) error {
umc.MessageComponent = &ActionsRow{} umc.MessageComponent = &ActionsRow{}
case ButtonComponent: case ButtonComponent:
umc.MessageComponent = &Button{} umc.MessageComponent = &Button{}
case SelectMenuComponent: case SelectMenuComponent, ChannelSelectMenuComponent, UserSelectMenuComponent,
RoleSelectMenuComponent, MentionableSelectMenuComponent:
umc.MessageComponent = &SelectMenu{} umc.MessageComponent = &SelectMenu{}
case TextInputComponent: case TextInputComponent:
umc.MessageComponent = &TextInput{} umc.MessageComponent = &TextInput{}
@ -169,8 +174,23 @@ type SelectMenuOption struct {
Default bool `json:"default"` Default bool `json:"default"`
} }
// SelectMenuType represents select menu type.
type SelectMenuType ComponentType
// SelectMenu types.
const (
StringSelectMenu = SelectMenuType(SelectMenuComponent)
UserSelectMenu = SelectMenuType(UserSelectMenuComponent)
RoleSelectMenu = SelectMenuType(RoleSelectMenuComponent)
MentionableSelectMenu = SelectMenuType(MentionableSelectMenuComponent)
ChannelSelectMenu = SelectMenuType(ChannelSelectMenuComponent)
)
// SelectMenu represents select menu component. // SelectMenu represents select menu component.
type SelectMenu struct { type SelectMenu struct {
// Type of the select menu.
MenuType SelectMenuType `json:"type,omitempty"`
// CustomID is a developer-defined identifier for the select menu.
CustomID string `json:"custom_id,omitempty"` CustomID string `json:"custom_id,omitempty"`
// The text which will be shown in the menu if there's no default options or all options was deselected and component was closed. // The text which will be shown in the menu if there's no default options or all options was deselected and component was closed.
Placeholder string `json:"placeholder"` Placeholder string `json:"placeholder"`
@ -179,25 +199,31 @@ type SelectMenu struct {
// This value determines the maximal amount of selected items in the menu. // This value determines the maximal amount of selected items in the menu.
// If MaxValues or MinValues are greater than one then the user can select multiple items in the component. // If MaxValues or MinValues are greater than one then the user can select multiple items in the component.
MaxValues int `json:"max_values,omitempty"` MaxValues int `json:"max_values,omitempty"`
Options []SelectMenuOption `json:"options"` Options []SelectMenuOption `json:"options,omitempty"`
Disabled bool `json:"disabled"` Disabled bool `json:"disabled"`
// NOTE: Can only be used in SelectMenu with Channel menu type.
ChannelTypes []ChannelType `json:"channel_types,omitempty"`
} }
// Type is a method to get the type of a component. // Type is a method to get the type of a component.
func (SelectMenu) Type() ComponentType { func (s SelectMenu) Type() ComponentType {
if s.MenuType != 0 {
return ComponentType(s.MenuType)
}
return SelectMenuComponent return SelectMenuComponent
} }
// MarshalJSON is a method for marshaling SelectMenu to a JSON object. // MarshalJSON is a method for marshaling SelectMenu to a JSON object.
func (m SelectMenu) MarshalJSON() ([]byte, error) { func (s SelectMenu) MarshalJSON() ([]byte, error) {
type selectMenu SelectMenu type selectMenu SelectMenu
return Marshal(struct { return Marshal(struct {
selectMenu selectMenu
Type ComponentType `json:"type"` Type ComponentType `json:"type"`
}{ }{
selectMenu: selectMenu(m), selectMenu: selectMenu(s),
Type: m.Type(), Type: s.Type(),
}) })
} }

@ -22,7 +22,7 @@ import (
) )
// VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/) // VERSION of DiscordGo, follows Semantic Versioning. (http://semver.org/)
const VERSION = "0.26.1" const VERSION = "0.27.0"
// New creates a new Discord session with provided token. // New creates a new Discord session with provided token.
// If the token is for a bot, it must be prefixed with "Bot " // If the token is for a bot, it must be prefixed with "Bot "

@ -60,11 +60,12 @@ var (
return EndpointCDNBanners + uID + "/" + cID + ".gif" return EndpointCDNBanners + uID + "/" + cID + ".gif"
} }
EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" } EndpointUserGuilds = func(uID string) string { return EndpointUsers + uID + "/guilds" }
EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID } EndpointUserGuild = func(uID, gID string) string { return EndpointUsers + uID + "/guilds/" + gID }
EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" } EndpointUserGuildMember = func(uID, gID string) string { return EndpointUserGuild(uID, gID) + "/member" }
EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" } EndpointUserChannels = func(uID string) string { return EndpointUsers + uID + "/channels" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" } EndpointUserApplicationRoleConnection = func(aID string) string { return EndpointUsers + "@me/applications/" + aID + "/role-connection" }
EndpointUserConnections = func(uID string) string { return EndpointUsers + uID + "/connections" }
EndpointGuild = func(gID string) string { return EndpointGuilds + gID } EndpointGuild = func(gID string) string { return EndpointGuilds + gID }
EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" } EndpointGuildAutoModeration = func(gID string) string { return EndpointGuild(gID) + "/auto-moderation" }
@ -96,6 +97,7 @@ var (
EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" } EndpointGuildEmojis = func(gID string) string { return EndpointGuilds + gID + "/emojis" }
EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID } EndpointGuildEmoji = func(gID, eID string) string { return EndpointGuilds + gID + "/emojis/" + eID }
EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" } EndpointGuildBanner = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".png" }
EndpointGuildBannerAnimated = func(gID, hash string) string { return EndpointCDNBanners + gID + "/" + hash + ".gif" }
EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" } EndpointGuildStickers = func(gID string) string { return EndpointGuilds + gID + "/stickers" }
EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID } EndpointGuildSticker = func(gID, sID string) string { return EndpointGuilds + gID + "/stickers/" + sID }
EndpointStageInstance = func(cID string) string { return EndpointStageInstances + "/" + cID } EndpointStageInstance = func(cID string) string { return EndpointStageInstances + "/" + cID }
@ -197,8 +199,9 @@ var (
EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" } EndpointEmoji = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".png" }
EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" } EndpointEmojiAnimated = func(eID string) string { return EndpointCDN + "emojis/" + eID + ".gif" }
EndpointApplications = EndpointAPI + "applications" EndpointApplications = EndpointAPI + "applications"
EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID } EndpointApplication = func(aID string) string { return EndpointApplications + "/" + aID }
EndpointApplicationRoleConnectionMetadata = func(aID string) string { return EndpointApplication(aID) + "/role-connections/metadata" }
EndpointOAuth2 = EndpointAPI + "oauth2/" EndpointOAuth2 = EndpointAPI + "oauth2/"
EndpointOAuth2Applications = EndpointOAuth2 + "applications" EndpointOAuth2Applications = EndpointOAuth2 + "applications"

@ -36,13 +36,13 @@ type Event struct {
// A Ready stores all data for the websocket READY event. // A Ready stores all data for the websocket READY event.
type Ready struct { type Ready struct {
Version int `json:"v"` Version int `json:"v"`
SessionID string `json:"session_id"` SessionID string `json:"session_id"`
User *User `json:"user"` User *User `json:"user"`
Guilds []*Guild `json:"guilds"` Shard *[2]int `json:"shard"`
PrivateChannels []*Channel `json:"private_channels"` Application *Application `json:"application"`
Guilds []*Guild `json:"guilds"`
// TODO: Application and Shard PrivateChannels []*Channel `json:"private_channels"`
} }
// ChannelCreate is the data for a ChannelCreate event. // ChannelCreate is the data for a ChannelCreate event.
@ -150,6 +150,7 @@ type GuildMemberAdd struct {
// GuildMemberUpdate is the data for a GuildMemberUpdate event. // GuildMemberUpdate is the data for a GuildMemberUpdate event.
type GuildMemberUpdate struct { type GuildMemberUpdate struct {
*Member *Member
BeforeUpdate *Member `json:"-"`
} }
// GuildMemberRemove is the data for a GuildMemberRemove event. // GuildMemberRemove is the data for a GuildMemberRemove event.

@ -42,6 +42,7 @@ type ApplicationCommand struct {
DefaultPermission *bool `json:"default_permission,omitempty"` DefaultPermission *bool `json:"default_permission,omitempty"`
DefaultMemberPermissions *int64 `json:"default_member_permissions,string,omitempty"` DefaultMemberPermissions *int64 `json:"default_member_permissions,string,omitempty"`
DMPermission *bool `json:"dm_permission,omitempty"` DMPermission *bool `json:"dm_permission,omitempty"`
NSFW *bool `json:"nsfw,omitempty"`
// NOTE: Chat commands only. Otherwise it mustn't be set. // NOTE: Chat commands only. Otherwise it mustn't be set.
@ -343,13 +344,22 @@ func (ApplicationCommandInteractionData) Type() InteractionType {
// MessageComponentInteractionData contains the data of message component interaction. // MessageComponentInteractionData contains the data of message component interaction.
type MessageComponentInteractionData struct { type MessageComponentInteractionData struct {
CustomID string `json:"custom_id"` CustomID string `json:"custom_id"`
ComponentType ComponentType `json:"component_type"` ComponentType ComponentType `json:"component_type"`
Resolved MessageComponentInteractionDataResolved `json:"resolved"`
// NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil. // NOTE: Only filled when ComponentType is SelectMenuComponent (3). Otherwise is nil.
Values []string `json:"values"` Values []string `json:"values"`
} }
// MessageComponentInteractionDataResolved contains the resolved data of selected option.
type MessageComponentInteractionDataResolved struct {
Users map[string]*User `json:"users"`
Members map[string]*Member `json:"members"`
Roles map[string]*Role `json:"roles"`
Channels map[string]*Channel `json:"channels"`
}
// Type returns the type of interaction data. // Type returns the type of interaction data.
func (MessageComponentInteractionData) Type() InteractionType { func (MessageComponentInteractionData) Type() InteractionType {
return InteractionMessageComponent return InteractionMessageComponent
@ -472,7 +482,7 @@ func (o ApplicationCommandInteractionDataOption) RoleValue(s *Session, gID strin
return &Role{ID: roleID} return &Role{ID: roleID}
} }
r, err := s.State.Role(roleID, gID) r, err := s.State.Role(gID, roleID)
if err != nil { if err != nil {
roles, err := s.GuildRoles(gID) roles, err := s.GuildRoles(gID)
if err == nil { if err == nil {

@ -249,6 +249,10 @@ type MessageEdit struct {
Embeds []*MessageEmbed `json:"embeds"` Embeds []*MessageEmbed `json:"embeds"`
AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"` AllowedMentions *MessageAllowedMentions `json:"allowed_mentions,omitempty"`
Flags MessageFlags `json:"flags,omitempty"` Flags MessageFlags `json:"flags,omitempty"`
// Files to append to the message
Files []*File `json:"-"`
// Overwrite existing attachments
Attachments *[]*MessageAttachment `json:"attachments,omitempty"`
ID string ID string
Channel string Channel string
@ -385,8 +389,8 @@ type MessageEmbedAuthor struct {
// MessageEmbedField is a part of a MessageEmbed struct. // MessageEmbedField is a part of a MessageEmbed struct.
type MessageEmbedField struct { type MessageEmbedField struct {
Name string `json:"name,omitempty"` Name string `json:"name"`
Value string `json:"value,omitempty"` Value string `json:"value"`
Inline bool `json:"inline,omitempty"` Inline bool `json:"inline,omitempty"`
} }

File diff suppressed because it is too large Load Diff

@ -207,6 +207,15 @@ func (s *State) presenceAdd(guildID string, presence *Presence) error {
if presence.Status != "" { if presence.Status != "" {
guild.Presences[i].Status = presence.Status guild.Presences[i].Status = presence.Status
} }
if presence.ClientStatus.Desktop != "" {
guild.Presences[i].ClientStatus.Desktop = presence.ClientStatus.Desktop
}
if presence.ClientStatus.Mobile != "" {
guild.Presences[i].ClientStatus.Mobile = presence.ClientStatus.Mobile
}
if presence.ClientStatus.Web != "" {
guild.Presences[i].ClientStatus.Web = presence.ClientStatus.Web
}
//Update the optionally sent user information //Update the optionally sent user information
//ID Is a mandatory field so you should not need to check if it is empty //ID Is a mandatory field so you should not need to check if it is empty
@ -909,9 +918,11 @@ func (s *State) onReady(se *Session, r *Ready) (err error) {
// if state is disabled, store the bare essentials. // if state is disabled, store the bare essentials.
if !se.StateEnabled { if !se.StateEnabled {
ready := Ready{ ready := Ready{
Version: r.Version, Version: r.Version,
SessionID: r.SessionID, SessionID: r.SessionID,
User: r.User, User: r.User,
Shard: r.Shard,
Application: r.Application,
} }
s.Ready = ready s.Ready = ready
@ -981,6 +992,13 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
} }
case *GuildMemberUpdate: case *GuildMemberUpdate:
if s.TrackMembers { if s.TrackMembers {
var old *Member
old, err = s.Member(t.GuildID, t.User.ID)
if err == nil {
oldCopy := *old
t.BeforeUpdate = &oldCopy
}
err = s.MemberAdd(t.Member) err = s.MemberAdd(t.Member)
} }
case *GuildMemberRemove: case *GuildMemberRemove:
@ -1023,7 +1041,14 @@ func (s *State) OnInterface(se *Session, i interface{}) (err error) {
} }
case *GuildEmojisUpdate: case *GuildEmojisUpdate:
if s.TrackEmojis { if s.TrackEmojis {
err = s.EmojisAdd(t.GuildID, t.Emojis) var guild *Guild
guild, err = s.Guild(t.GuildID)
if err != nil {
return err
}
s.Lock()
defer s.Unlock()
guild.Emojis = t.Emojis
} }
case *ChannelCreate: case *ChannelCreate:
if s.TrackChannels { if s.TrackChannels {

@ -17,7 +17,6 @@ import (
"math" "math"
"net/http" "net/http"
"regexp" "regexp"
"strings"
"sync" "sync"
"time" "time"
@ -156,6 +155,38 @@ type Application struct {
Flags int `json:"flags,omitempty"` Flags int `json:"flags,omitempty"`
} }
// ApplicationRoleConnectionMetadataType represents the type of application role connection metadata.
type ApplicationRoleConnectionMetadataType int
// Application role connection metadata types.
const (
ApplicationRoleConnectionMetadataIntegerLessThanOrEqual ApplicationRoleConnectionMetadataType = 1
ApplicationRoleConnectionMetadataIntegerGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 2
ApplicationRoleConnectionMetadataIntegerEqual ApplicationRoleConnectionMetadataType = 3
ApplicationRoleConnectionMetadataIntegerNotEqual ApplicationRoleConnectionMetadataType = 4
ApplicationRoleConnectionMetadataDatetimeLessThanOrEqual ApplicationRoleConnectionMetadataType = 5
ApplicationRoleConnectionMetadataDatetimeGreaterThanOrEqual ApplicationRoleConnectionMetadataType = 6
ApplicationRoleConnectionMetadataBooleanEqual ApplicationRoleConnectionMetadataType = 7
ApplicationRoleConnectionMetadataBooleanNotEqual ApplicationRoleConnectionMetadataType = 8
)
// ApplicationRoleConnectionMetadata stores application role connection metadata.
type ApplicationRoleConnectionMetadata struct {
Type ApplicationRoleConnectionMetadataType `json:"type"`
Key string `json:"key"`
Name string `json:"name"`
NameLocalizations map[Locale]string `json:"name_localizations"`
Description string `json:"description"`
DescriptionLocalizations map[Locale]string `json:"description_localizations"`
}
// ApplicationRoleConnection represents the role connection that an application has attached to a user.
type ApplicationRoleConnection struct {
PlatformName string `json:"platform_name"`
PlatformUsername string `json:"platform_username"`
Metadata map[string]string `json:"metadata"`
}
// UserConnection is a Connection returned from the UserConnections endpoint // UserConnection is a Connection returned from the UserConnections endpoint
type UserConnection struct { type UserConnection struct {
ID string `json:"id"` ID string `json:"id"`
@ -254,6 +285,42 @@ const (
ChannelTypeGuildPublicThread ChannelType = 11 ChannelTypeGuildPublicThread ChannelType = 11
ChannelTypeGuildPrivateThread ChannelType = 12 ChannelTypeGuildPrivateThread ChannelType = 12
ChannelTypeGuildStageVoice ChannelType = 13 ChannelTypeGuildStageVoice ChannelType = 13
ChannelTypeGuildForum ChannelType = 15
)
// ChannelFlags represent flags of a channel/thread.
type ChannelFlags int
// Block containing known ChannelFlags values.
const (
// ChannelFlagPinned indicates whether the thread is pinned in the forum channel.
// NOTE: forum threads only.
ChannelFlagPinned ChannelFlags = 1 << 1
// ChannelFlagRequireTag indicates whether a tag is required to be specified when creating a thread.
// NOTE: forum channels only.
ChannelFlagRequireTag ChannelFlags = 1 << 4
)
// ForumSortOrderType represents sort order of a forum channel.
type ForumSortOrderType int
const (
// ForumSortOrderLatestActivity sorts posts by activity.
ForumSortOrderLatestActivity ForumSortOrderType = 0
// ForumSortOrderCreationDate sorts posts by creation time (from most recent to oldest).
ForumSortOrderCreationDate ForumSortOrderType = 1
)
// ForumLayout represents layout of a forum channel.
type ForumLayout int
const (
// ForumLayoutNotSet represents no default layout.
ForumLayoutNotSet ForumLayout = 0
// ForumLayoutListView displays forum posts as a list.
ForumLayoutListView ForumLayout = 1
// ForumLayoutGalleryView displays forum posts as a collection of tiles.
ForumLayoutGalleryView ForumLayout = 2
) )
// A Channel holds all data related to an individual Discord channel. // A Channel holds all data related to an individual Discord channel.
@ -332,6 +399,30 @@ type Channel struct {
// All thread members. State channels only. // All thread members. State channels only.
Members []*ThreadMember `json:"-"` Members []*ThreadMember `json:"-"`
// Channel flags.
Flags ChannelFlags `json:"flags"`
// The set of tags that can be used in a forum channel.
AvailableTags []ForumTag `json:"available_tags"`
// The IDs of the set of tags that have been applied to a thread in a forum channel.
AppliedTags []string `json:"applied_tags"`
// Emoji to use as the default reaction to a forum post.
DefaultReactionEmoji ForumDefaultReaction `json:"default_reaction_emoji"`
// The initial RateLimitPerUser to set on newly created threads in a channel.
// This field is copied to the thread at creation time and does not live update.
DefaultThreadRateLimitPerUser int `json:"default_thread_rate_limit_per_user"`
// The default sort order type used to order posts in forum channels.
// Defaults to null, which indicates a preferred sort order hasn't been set by a channel admin.
DefaultSortOrder *ForumSortOrderType `json:"default_sort_order"`
// The default forum layout view used to display posts in forum channels.
// Defaults to ForumLayoutNotSet, which indicates a layout view has not been set by a channel admin.
DefaultForumLayout ForumLayout `json:"default_forum_layout"`
} }
// Mention returns a string which mentions the channel // Mention returns a string which mentions the channel
@ -346,15 +437,17 @@ func (c *Channel) IsThread() bool {
// A ChannelEdit holds Channel Field data for a channel edit. // A ChannelEdit holds Channel Field data for a channel edit.
type ChannelEdit struct { type ChannelEdit struct {
Name string `json:"name,omitempty"` Name string `json:"name,omitempty"`
Topic string `json:"topic,omitempty"` Topic string `json:"topic,omitempty"`
NSFW *bool `json:"nsfw,omitempty"` NSFW *bool `json:"nsfw,omitempty"`
Position int `json:"position"` Position int `json:"position"`
Bitrate int `json:"bitrate,omitempty"` Bitrate int `json:"bitrate,omitempty"`
UserLimit int `json:"user_limit,omitempty"` UserLimit int `json:"user_limit,omitempty"`
PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"` PermissionOverwrites []*PermissionOverwrite `json:"permission_overwrites,omitempty"`
ParentID string `json:"parent_id,omitempty"` ParentID string `json:"parent_id,omitempty"`
RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"` RateLimitPerUser *int `json:"rate_limit_per_user,omitempty"`
Flags *ChannelFlags `json:"flags,omitempty"`
DefaultThreadRateLimitPerUser *int `json:"default_thread_rate_limit_per_user,omitempty"`
// NOTE: threads only // NOTE: threads only
@ -362,6 +455,16 @@ type ChannelEdit struct {
AutoArchiveDuration int `json:"auto_archive_duration,omitempty"` AutoArchiveDuration int `json:"auto_archive_duration,omitempty"`
Locked *bool `json:"locked,omitempty"` Locked *bool `json:"locked,omitempty"`
Invitable *bool `json:"invitable,omitempty"` Invitable *bool `json:"invitable,omitempty"`
// NOTE: forum channels only
AvailableTags *[]ForumTag `json:"available_tags,omitempty"`
DefaultReactionEmoji *ForumDefaultReaction `json:"default_reaction_emoji,omitempty"`
DefaultSortOrder *ForumSortOrderType `json:"default_sort_order,omitempty"` // TODO: null
DefaultForumLayout *ForumLayout `json:"default_forum_layout,omitempty"`
// NOTE: forum threads only
AppliedTags *[]string `json:"applied_tags,omitempty"`
} }
// A ChannelFollow holds data returned after following a news channel // A ChannelFollow holds data returned after following a news channel
@ -395,6 +498,9 @@ type ThreadStart struct {
Type ChannelType `json:"type,omitempty"` Type ChannelType `json:"type,omitempty"`
Invitable bool `json:"invitable"` Invitable bool `json:"invitable"`
RateLimitPerUser int `json:"rate_limit_per_user,omitempty"` RateLimitPerUser int `json:"rate_limit_per_user,omitempty"`
// NOTE: forum threads only
AppliedTags []string `json:"applied_tags,omitempty"`
} }
// ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types. // ThreadMetadata contains a number of thread-specific channel fields that are not needed by other channel types.
@ -438,6 +544,24 @@ type AddedThreadMember struct {
Presence *Presence `json:"presence"` Presence *Presence `json:"presence"`
} }
// ForumDefaultReaction specifies emoji to use as the default reaction to a forum post.
// NOTE: Exactly one of EmojiID and EmojiName must be set.
type ForumDefaultReaction struct {
// The id of a guild's custom emoji.
EmojiID string `json:"emoji_id,omitempty"`
// The unicode character of the emoji.
EmojiName string `json:"emoji_name,omitempty"`
}
// ForumTag represents a tag that is able to be applied to a thread in a forum channel.
type ForumTag struct {
ID string `json:"id,omitempty"`
Name string `json:"name"`
Moderated bool `json:"moderated"`
EmojiID string `json:"emoji_id,omitempty"`
EmojiName string `json:"emoji_name,omitempty"`
}
// Emoji struct holds data related to Emoji's // Emoji struct holds data related to Emoji's
type Emoji struct { type Emoji struct {
ID string `json:"id"` ID string `json:"id"`
@ -452,7 +576,7 @@ type Emoji struct {
// EmojiRegex is the regex used to find and identify emojis in messages // EmojiRegex is the regex used to find and identify emojis in messages
var ( var (
EmojiRegex = regexp.MustCompile(`<(a|):[A-z0-9_~]+:[0-9]{18}>`) EmojiRegex = regexp.MustCompile(`<(a|):[A-z0-9_~]+:[0-9]{18,20}>`)
) )
// MessageFormat returns a correctly formatted Emoji for use in Message content and embeds // MessageFormat returns a correctly formatted Emoji for use in Message content and embeds
@ -792,16 +916,11 @@ type GuildPreview struct {
} }
// IconURL returns a URL to the guild's icon. // IconURL returns a URL to the guild's icon.
func (g *GuildPreview) IconURL() string { //
if g.Icon == "" { // size: The size of the desired icon image as a power of two
return "" // Image size can be any power of two between 16 and 4096.
} func (g *GuildPreview) IconURL(size string) string {
return iconURL(g.Icon, EndpointGuildIcon(g.ID, g.Icon), EndpointGuildIconAnimated(g.ID, g.Icon), size)
if strings.HasPrefix(g.Icon, "a_") {
return EndpointGuildIconAnimated(g.ID, g.Icon)
}
return EndpointGuildIcon(g.ID, g.Icon)
} }
// GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data. // GuildScheduledEvent is a representation of a scheduled event in a guild. Only for retrieval of the data.
@ -917,13 +1036,13 @@ type GuildScheduledEventStatus int
const ( const (
// GuildScheduledEventStatusScheduled represents the current event is in scheduled state // GuildScheduledEventStatusScheduled represents the current event is in scheduled state
GuildScheduledEventStatusScheduled = 1 GuildScheduledEventStatusScheduled GuildScheduledEventStatus = 1
// GuildScheduledEventStatusActive represents the current event is in active state // GuildScheduledEventStatusActive represents the current event is in active state
GuildScheduledEventStatusActive = 2 GuildScheduledEventStatusActive GuildScheduledEventStatus = 2
// GuildScheduledEventStatusCompleted represents the current event is in completed state // GuildScheduledEventStatusCompleted represents the current event is in completed state
GuildScheduledEventStatusCompleted = 3 GuildScheduledEventStatusCompleted GuildScheduledEventStatus = 3
// GuildScheduledEventStatusCanceled represents the current event is in canceled state // GuildScheduledEventStatusCanceled represents the current event is in canceled state
GuildScheduledEventStatusCanceled = 4 GuildScheduledEventStatusCanceled GuildScheduledEventStatus = 4
) )
// GuildScheduledEventEntityType is the type of entity associated with a guild scheduled event. // GuildScheduledEventEntityType is the type of entity associated with a guild scheduled event.
@ -932,11 +1051,11 @@ type GuildScheduledEventEntityType int
const ( const (
// GuildScheduledEventEntityTypeStageInstance represents a stage channel // GuildScheduledEventEntityTypeStageInstance represents a stage channel
GuildScheduledEventEntityTypeStageInstance = 1 GuildScheduledEventEntityTypeStageInstance GuildScheduledEventEntityType = 1
// GuildScheduledEventEntityTypeVoice represents a voice channel // GuildScheduledEventEntityTypeVoice represents a voice channel
GuildScheduledEventEntityTypeVoice = 2 GuildScheduledEventEntityTypeVoice GuildScheduledEventEntityType = 2
// GuildScheduledEventEntityTypeExternal represents an external event // GuildScheduledEventEntityTypeExternal represents an external event
GuildScheduledEventEntityTypeExternal = 3 GuildScheduledEventEntityTypeExternal GuildScheduledEventEntityType = 3
) )
// GuildScheduledEventUser is a user subscribed to a scheduled event. // GuildScheduledEventUser is a user subscribed to a scheduled event.
@ -1007,29 +1126,26 @@ type SystemChannelFlag int
// Block containing known SystemChannelFlag values // Block containing known SystemChannelFlag values
const ( const (
SystemChannelFlagsSuppressJoin SystemChannelFlag = 1 << 0 SystemChannelFlagsSuppressJoinNotifications SystemChannelFlag = 1 << 0
SystemChannelFlagsSuppressPremium SystemChannelFlag = 1 << 1 SystemChannelFlagsSuppressPremium SystemChannelFlag = 1 << 1
SystemChannelFlagsSuppressGuildReminderNotifications SystemChannelFlag = 1 << 2
SystemChannelFlagsSuppressJoinNotificationReplies SystemChannelFlag = 1 << 3
) )
// IconURL returns a URL to the guild's icon. // IconURL returns a URL to the guild's icon.
func (g *Guild) IconURL() string { //
if g.Icon == "" { // size: The size of the desired icon image as a power of two
return "" // Image size can be any power of two between 16 and 4096.
} func (g *Guild) IconURL(size string) string {
return iconURL(g.Icon, EndpointGuildIcon(g.ID, g.Icon), EndpointGuildIconAnimated(g.ID, g.Icon), size)
if strings.HasPrefix(g.Icon, "a_") {
return EndpointGuildIconAnimated(g.ID, g.Icon)
}
return EndpointGuildIcon(g.ID, g.Icon)
} }
// BannerURL returns a URL to the guild's banner. // BannerURL returns a URL to the guild's banner.
func (g *Guild) BannerURL() string { //
if g.Banner == "" { // size: The size of the desired banner image as a power of two
return "" // Image size can be any power of two between 16 and 4096.
} func (g *Guild) BannerURL(size string) string {
return EndpointGuildBanner(g.ID, g.Banner) return bannerURL(g.Banner, EndpointGuildBanner(g.ID, g.Banner), EndpointGuildBannerAnimated(g.ID, g.Banner), size)
} }
// A UserGuild holds a brief version of a Guild // A UserGuild holds a brief version of a Guild
@ -1076,12 +1192,22 @@ type GuildParams struct {
Region string `json:"region,omitempty"` Region string `json:"region,omitempty"`
VerificationLevel *VerificationLevel `json:"verification_level,omitempty"` VerificationLevel *VerificationLevel `json:"verification_level,omitempty"`
DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type? DefaultMessageNotifications int `json:"default_message_notifications,omitempty"` // TODO: Separate type?
ExplicitContentFilter int `json:"explicit_content_filter,omitempty"`
AfkChannelID string `json:"afk_channel_id,omitempty"` AfkChannelID string `json:"afk_channel_id,omitempty"`
AfkTimeout int `json:"afk_timeout,omitempty"` AfkTimeout int `json:"afk_timeout,omitempty"`
Icon string `json:"icon,omitempty"` Icon string `json:"icon,omitempty"`
OwnerID string `json:"owner_id,omitempty"` OwnerID string `json:"owner_id,omitempty"`
Splash string `json:"splash,omitempty"` Splash string `json:"splash,omitempty"`
DiscoverySplash string `json:"discovery_splash,omitempty"`
Banner string `json:"banner,omitempty"` Banner string `json:"banner,omitempty"`
SystemChannelID string `json:"system_channel_id,omitempty"`
SystemChannelFlags SystemChannelFlag `json:"system_channel_flags,omitempty"`
RulesChannelID string `json:"rules_channel_id,omitempty"`
PublicUpdatesChannelID string `json:"public_updates_channel_id,omitempty"`
PreferredLocale Locale `json:"preferred_locale,omitempty"`
Features []GuildFeature `json:"features,omitempty"`
Description string `json:"description,omitempty"`
PremiumProgressBarEnabled *bool `json:"premium_progress_bar_enabled,omitempty"`
} }
// A Role stores information about Discord guild member roles. // A Role stores information about Discord guild member roles.
@ -1167,10 +1293,11 @@ type VoiceState struct {
// A Presence stores the online, offline, or idle and game status of Guild members. // A Presence stores the online, offline, or idle and game status of Guild members.
type Presence struct { type Presence struct {
User *User `json:"user"` User *User `json:"user"`
Status Status `json:"status"` Status Status `json:"status"`
Activities []*Activity `json:"activities"` Activities []*Activity `json:"activities"`
Since *int `json:"since"` Since *int `json:"since"`
ClientStatus ClientStatus `json:"client_status"`
} }
// A TimeStamps struct contains start and end times used in the rich presence "playing .." Game // A TimeStamps struct contains start and end times used in the rich presence "playing .." Game
@ -1249,9 +1376,10 @@ func (m *Member) Mention() string {
} }
// AvatarURL returns the URL of the member's avatar // AvatarURL returns the URL of the member's avatar
// size: The size of the user's avatar as a power of two //
// if size is an empty string, no size parameter will // size: The size of the user's avatar as a power of two
// be added to the URL. // if size is an empty string, no size parameter will
// be added to the URL.
func (m *Member) AvatarURL(size string) string { func (m *Member) AvatarURL(size string) string {
if m.Avatar == "" { if m.Avatar == "" {
return m.User.AvatarURL(size) return m.User.AvatarURL(size)
@ -1262,6 +1390,13 @@ func (m *Member) AvatarURL(size string) string {
} }
// ClientStatus stores the online, offline, idle, or dnd status of each device of a Guild member.
type ClientStatus struct {
Desktop Status `json:"desktop"`
Mobile Status `json:"mobile"`
Web Status `json:"web"`
}
// Status type definition // Status type definition
type Status string type Status string
@ -1371,9 +1506,21 @@ type AutoModerationTriggerMetadata struct {
// Substrings which will be searched for in content. // Substrings which will be searched for in content.
// NOTE: should be only used with keyword trigger type. // NOTE: should be only used with keyword trigger type.
KeywordFilter []string `json:"keyword_filter,omitempty"` KeywordFilter []string `json:"keyword_filter,omitempty"`
// Regular expression patterns which will be matched against content (maximum of 10).
// NOTE: should be only used with keyword trigger type.
RegexPatterns []string `json:"regex_patterns,omitempty"`
// Internally pre-defined wordsets which will be searched for in content. // Internally pre-defined wordsets which will be searched for in content.
// NOTE: should be only used with keyword preset trigger type. // NOTE: should be only used with keyword preset trigger type.
Presets []AutoModerationKeywordPreset `json:"presets,omitempty"` Presets []AutoModerationKeywordPreset `json:"presets,omitempty"`
// Substrings which should not trigger the rule.
// NOTE: should be only used with keyword or keyword preset trigger type.
AllowList *[]string `json:"allow_list,omitempty"`
// Total number of unique role and user mentions allowed per message.
// NOTE: should be only used with mention spam trigger type.
MentionTotalLimit int `json:"mention_total_limit,omitempty"`
} }
// AutoModerationActionType represents an action which will execute whenever a rule is triggered. // AutoModerationActionType represents an action which will execute whenever a rule is triggered.
@ -2074,6 +2221,7 @@ const (
ErrCodeUnknownGuildWelcomeScreen = 10069 ErrCodeUnknownGuildWelcomeScreen = 10069
ErrCodeUnknownGuildScheduledEvent = 10070 ErrCodeUnknownGuildScheduledEvent = 10070
ErrCodeUnknownGuildScheduledEventUser = 10071 ErrCodeUnknownGuildScheduledEventUser = 10071
ErrUnknownTag = 10087
ErrCodeBotsCannotUseEndpoint = 20001 ErrCodeBotsCannotUseEndpoint = 20001
ErrCodeOnlyBotsCanUseEndpoint = 20002 ErrCodeOnlyBotsCanUseEndpoint = 20002
@ -2087,28 +2235,30 @@ const (
ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031 ErrCodeStageTopicContainsNotAllowedWordsForPublicStages = 20031
ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035 ErrCodeGuildPremiumSubscriptionLevelTooLow = 20035
ErrCodeMaximumGuildsReached = 30001 ErrCodeMaximumGuildsReached = 30001
ErrCodeMaximumPinsReached = 30003 ErrCodeMaximumPinsReached = 30003
ErrCodeMaximumNumberOfRecipientsReached = 30004 ErrCodeMaximumNumberOfRecipientsReached = 30004
ErrCodeMaximumGuildRolesReached = 30005 ErrCodeMaximumGuildRolesReached = 30005
ErrCodeMaximumNumberOfWebhooksReached = 30007 ErrCodeMaximumNumberOfWebhooksReached = 30007
ErrCodeMaximumNumberOfEmojisReached = 30008 ErrCodeMaximumNumberOfEmojisReached = 30008
ErrCodeTooManyReactions = 30010 ErrCodeTooManyReactions = 30010
ErrCodeMaximumNumberOfGuildChannelsReached = 30013 ErrCodeMaximumNumberOfGuildChannelsReached = 30013
ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015 ErrCodeMaximumNumberOfAttachmentsInAMessageReached = 30015
ErrCodeMaximumNumberOfInvitesReached = 30016 ErrCodeMaximumNumberOfInvitesReached = 30016
ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018 ErrCodeMaximumNumberOfAnimatedEmojisReached = 30018
ErrCodeMaximumNumberOfServerMembersReached = 30019 ErrCodeMaximumNumberOfServerMembersReached = 30019
ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030 ErrCodeMaximumNumberOfGuildDiscoverySubcategoriesReached = 30030
ErrCodeGuildAlreadyHasATemplate = 30031 ErrCodeGuildAlreadyHasATemplate = 30031
ErrCodeMaximumNumberOfThreadParticipantsReached = 30033 ErrCodeMaximumNumberOfThreadParticipantsReached = 30033
ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035 ErrCodeMaximumNumberOfBansForNonGuildMembersHaveBeenExceeded = 30035
ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037 ErrCodeMaximumNumberOfBansFetchesHasBeenReached = 30037
ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038 ErrCodeMaximumNumberOfUncompletedGuildScheduledEventsReached = 30038
ErrCodeMaximumNumberOfStickersReached = 30039 ErrCodeMaximumNumberOfStickersReached = 30039
ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040 ErrCodeMaximumNumberOfPruneRequestsHasBeenReached = 30040
ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042 ErrCodeMaximumNumberOfGuildWidgetSettingsUpdatesHasBeenReached = 30042
ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046 ErrCodeMaximumNumberOfEditsToMessagesOlderThanOneHourReached = 30046
ErrCodeMaximumNumberOfPinnedThreadsInForumChannelHasBeenReached = 30047
ErrCodeMaximumNumberOfTagsInForumChannelHasBeenReached = 30048
ErrCodeUnauthorized = 40001 ErrCodeUnauthorized = 40001
ErrCodeActionRequiredVerifiedAccount = 40002 ErrCodeActionRequiredVerifiedAccount = 40002
@ -2121,6 +2271,7 @@ const (
ErrCodeMessageAlreadyCrossposted = 40033 ErrCodeMessageAlreadyCrossposted = 40033
ErrCodeAnApplicationWithThatNameAlreadyExists = 40041 ErrCodeAnApplicationWithThatNameAlreadyExists = 40041
ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060 ErrCodeInteractionHasAlreadyBeenAcknowledged = 40060
ErrCodeTagNamesMustBeUnique = 40061
ErrCodeMissingAccess = 50001 ErrCodeMissingAccess = 50001
ErrCodeInvalidAccountType = 50002 ErrCodeInvalidAccountType = 50002

@ -51,7 +51,7 @@ func MultipartBodyWithJSON(data interface{}, files []*File) (requestContentType
for i, file := range files { for i, file := range files {
h := make(textproto.MIMEHeader) h := make(textproto.MIMEHeader)
h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, quoteEscaper.Replace(file.Name))) h.Set("Content-Disposition", fmt.Sprintf(`form-data; name="files[%d]"; filename="%s"`, i, quoteEscaper.Replace(file.Name)))
contentType := file.ContentType contentType := file.ContentType
if contentType == "" { if contentType == "" {
contentType = "application/octet-stream" contentType = "application/octet-stream"
@ -107,3 +107,19 @@ func bannerURL(bannerHash, staticBannerURL, animatedBannerURL, size string) stri
} }
return URL return URL
} }
func iconURL(iconHash, staticIconURL, animatedIconURL, size string) string {
var URL string
if iconHash == "" {
return ""
} else if strings.HasPrefix(iconHash, "a_") {
URL = animatedIconURL
} else {
URL = staticIconURL
}
if size != "" {
return URL + "?size=" + size
}
return URL
}

@ -360,6 +360,25 @@ func (v *VoiceConnection) wsListen(wsConn *websocket.Conn, close <-chan struct{}
v.wsConn = nil v.wsConn = nil
v.Unlock() v.Unlock()
// Wait for VOICE_SERVER_UPDATE.
// When the bot is moved by the user to another voice channel,
// VOICE_SERVER_UPDATE is received after the code 4014.
for i := 0; i < 5; i++ { // TODO: temp, wait for VoiceServerUpdate.
<-time.After(1 * time.Second)
v.RLock()
reconnected := v.wsConn != nil
v.RUnlock()
if !reconnected {
continue
}
v.log(LogInformational, "successfully reconnected after 4014 manual disconnection")
return
}
// When VOICE_SERVER_UPDATE is not received, disconnect as usual.
v.log(LogInformational, "disconnect due to 4014 manual disconnection")
v.session.Lock() v.session.Lock()
delete(v.session.VoiceConnections, v.GuildID) delete(v.session.VoiceConnections, v.GuildID)
v.session.Unlock() v.session.Unlock()
@ -835,7 +854,7 @@ func (v *VoiceConnection) opusReceiver(udpConn *net.UDPConn, close <-chan struct
if opus, ok := secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey); ok { if opus, ok := secretbox.Open(nil, recvbuf[12:rlen], &nonce, &v.op4.SecretKey); ok {
p.Opus = opus p.Opus = opus
} else { } else {
return continue
} }
// extension bit set, and not a RTCP packet // extension bit set, and not a RTCP packet

@ -320,7 +320,7 @@ func (s *Session) heartbeat(wsConn *websocket.Conn, listening <-chan interface{}
} }
} }
// UpdateStatusData ia provided to UpdateStatusComplex() // UpdateStatusData is provided to UpdateStatusComplex()
type UpdateStatusData struct { type UpdateStatusData struct {
IdleSince *int `json:"since"` IdleSince *int `json:"since"`
Activities []*Activity `json:"activities"` Activities []*Activity `json:"activities"`
@ -361,6 +361,14 @@ func (s *Session) UpdateGameStatus(idle int, name string) (err error) {
return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, "")) return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeGame, name, ""))
} }
// UpdateWatchStatus is used to update the user's watch status.
// If idle>0 then set status to idle.
// If name!="" then set movie/stream.
// if otherwise, set status to active, and no activity.
func (s *Session) UpdateWatchStatus(idle int, name string) (err error) {
return s.UpdateStatusComplex(*newUpdateStatusData(idle, ActivityTypeWatching, name, ""))
}
// UpdateStreamingStatus is used to update the user's streaming status. // UpdateStreamingStatus is used to update the user's streaming status.
// If idle>0 then set status to idle. // If idle>0 then set status to idle.
// If name!="" then set game. // If name!="" then set game.

@ -0,0 +1,30 @@
linters:
enable:
- megacheck
- revive
- govet
- unconvert
- megacheck
- gas
- gocyclo
- dupl
- misspell
- unparam
- unused
- typecheck
- ineffassign
- stylecheck
- exportloopref
- gocritic
- nakedret
- gosimple
- prealloc
fast: false
disable-all: true
issues:
exclude-rules:
- path: _test\.go
linters:
- dupl
exclude-use-default: false

@ -44,7 +44,7 @@ func New2Q(size int) (*TwoQueueCache, error) {
// New2QParams creates a new TwoQueueCache using the provided // New2QParams creates a new TwoQueueCache using the provided
// parameter values. // parameter values.
func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) { func New2QParams(size int, recentRatio, ghostRatio float64) (*TwoQueueCache, error) {
if size <= 0 { if size <= 0 {
return nil, fmt.Errorf("invalid size") return nil, fmt.Errorf("invalid size")
} }
@ -138,7 +138,6 @@ func (c *TwoQueueCache) Add(key, value interface{}) {
// Add to the recently seen list // Add to the recently seen list
c.ensureSpace(false) c.ensureSpace(false)
c.recent.Add(key, value) c.recent.Add(key, value)
return
} }
// ensureSpace is used to ensure we have space in the cache // ensureSpace is used to ensure we have space in the cache

@ -1,3 +1,5 @@
Copyright (c) 2014 HashiCorp, Inc.
Mozilla Public License, version 2.0 Mozilla Public License, version 2.0
1. Definitions 1. Definitions

@ -7,7 +7,7 @@ thread safe LRU cache. It is based on the cache in Groupcache.
Documentation Documentation
============= =============
Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru) Full docs are available on [Godoc](https://pkg.go.dev/github.com/hashicorp/golang-lru)
Example Example
======= =======

@ -173,7 +173,6 @@ func (c *ARCCache) Add(key, value interface{}) {
// Add to the recently seen list // Add to the recently seen list
c.t1.Add(key, value) c.t1.Add(key, value)
return
} }
// replace is used to adaptively evict from either T1 or T2 // replace is used to adaptively evict from either T1 or T2

@ -6,10 +6,17 @@ import (
"github.com/hashicorp/golang-lru/simplelru" "github.com/hashicorp/golang-lru/simplelru"
) )
const (
// DefaultEvictedBufferSize defines the default buffer size to store evicted key/val
DefaultEvictedBufferSize = 16
)
// 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.LRUCache lru *simplelru.LRU
lock sync.RWMutex evictedKeys, evictedVals []interface{}
onEvictedCB func(k, v interface{})
lock sync.RWMutex
} }
// New creates an LRU of the given size. // New creates an LRU of the given size.
@ -19,30 +26,63 @@ func New(size int) (*Cache, error) {
// NewWithEvict constructs a fixed size cache with the given eviction // NewWithEvict constructs a fixed size cache with the given eviction
// callback. // callback.
func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) { func NewWithEvict(size int, onEvicted func(key, value interface{})) (c *Cache, err error) {
lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted)) // create a cache with default settings
if err != nil { c = &Cache{
return nil, err onEvictedCB: onEvicted,
} }
c := &Cache{ if onEvicted != nil {
lru: lru, c.initEvictBuffers()
onEvicted = c.onEvicted
} }
return c, nil c.lru, err = simplelru.NewLRU(size, onEvicted)
return
}
func (c *Cache) initEvictBuffers() {
c.evictedKeys = make([]interface{}, 0, DefaultEvictedBufferSize)
c.evictedVals = make([]interface{}, 0, DefaultEvictedBufferSize)
}
// onEvicted save evicted key/val and sent in externally registered callback
// outside of critical section
func (c *Cache) onEvicted(k, v interface{}) {
c.evictedKeys = append(c.evictedKeys, k)
c.evictedVals = append(c.evictedVals, v)
} }
// Purge is used to completely clear the cache. // Purge is used to completely clear the cache.
func (c *Cache) Purge() { func (c *Cache) Purge() {
var ks, vs []interface{}
c.lock.Lock() c.lock.Lock()
c.lru.Purge() c.lru.Purge()
if c.onEvictedCB != nil && len(c.evictedKeys) > 0 {
ks, vs = c.evictedKeys, c.evictedVals
c.initEvictBuffers()
}
c.lock.Unlock() c.lock.Unlock()
// invoke callback outside of critical section
if c.onEvictedCB != nil {
for i := 0; i < len(ks); i++ {
c.onEvictedCB(ks[i], vs[i])
}
}
} }
// 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{}) (evicted bool) { func (c *Cache) Add(key, value interface{}) (evicted bool) {
var k, v interface{}
c.lock.Lock() c.lock.Lock()
evicted = c.lru.Add(key, value) evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock() c.lock.Unlock()
return evicted if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return
} }
// Get looks up a key's value from the cache. // Get looks up a key's value from the cache.
@ -75,13 +115,21 @@ func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
// 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, evicted bool) { func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
var k, v interface{}
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock()
if c.lru.Contains(key) { if c.lru.Contains(key) {
c.lock.Unlock()
return true, false return true, false
} }
evicted = c.lru.Add(key, value) evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return false, evicted return false, evicted
} }
@ -89,47 +137,80 @@ func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
// 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) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) { func (c *Cache) PeekOrAdd(key, value interface{}) (previous interface{}, ok, evicted bool) {
var k, v interface{}
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock()
previous, ok = c.lru.Peek(key) previous, ok = c.lru.Peek(key)
if ok { if ok {
c.lock.Unlock()
return previous, true, false return previous, true, false
} }
evicted = c.lru.Add(key, value) evicted = c.lru.Add(key, value)
if c.onEvictedCB != nil && evicted {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock()
if c.onEvictedCB != nil && evicted {
c.onEvictedCB(k, v)
}
return nil, false, evicted return nil, false, evicted
} }
// Remove removes the provided key from the cache. // Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) (present bool) { func (c *Cache) Remove(key interface{}) (present bool) {
var k, v interface{}
c.lock.Lock() c.lock.Lock()
present = c.lru.Remove(key) present = c.lru.Remove(key)
if c.onEvictedCB != nil && present {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock() c.lock.Unlock()
if c.onEvictedCB != nil && present {
c.onEvictedCB(k, v)
}
return return
} }
// Resize changes the cache size. // Resize changes the cache size.
func (c *Cache) Resize(size int) (evicted int) { func (c *Cache) Resize(size int) (evicted int) {
var ks, vs []interface{}
c.lock.Lock() c.lock.Lock()
evicted = c.lru.Resize(size) evicted = c.lru.Resize(size)
if c.onEvictedCB != nil && evicted > 0 {
ks, vs = c.evictedKeys, c.evictedVals
c.initEvictBuffers()
}
c.lock.Unlock() c.lock.Unlock()
if c.onEvictedCB != nil && evicted > 0 {
for i := 0; i < len(ks); i++ {
c.onEvictedCB(ks[i], vs[i])
}
}
return evicted return evicted
} }
// RemoveOldest removes the oldest item from the cache. // RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() (key interface{}, value interface{}, ok bool) { func (c *Cache) RemoveOldest() (key, value interface{}, ok bool) {
var k, v interface{}
c.lock.Lock() c.lock.Lock()
key, value, ok = c.lru.RemoveOldest() key, value, ok = c.lru.RemoveOldest()
if c.onEvictedCB != nil && ok {
k, v = c.evictedKeys[0], c.evictedVals[0]
c.evictedKeys, c.evictedVals = c.evictedKeys[:0], c.evictedVals[:0]
}
c.lock.Unlock() c.lock.Unlock()
if c.onEvictedCB != nil && ok {
c.onEvictedCB(k, v)
}
return return
} }
// GetOldest returns the oldest entry // GetOldest returns the oldest entry
func (c *Cache) GetOldest() (key interface{}, value interface{}, ok bool) { func (c *Cache) GetOldest() (key, value interface{}, ok bool) {
c.lock.Lock() c.lock.RLock()
key, value, ok = c.lru.GetOldest() key, value, ok = c.lru.GetOldest()
c.lock.Unlock() c.lock.RUnlock()
return return
} }

@ -25,7 +25,7 @@ type entry struct {
// NewLRU constructs an LRU of the given size // NewLRU constructs an LRU of the given size
func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
if size <= 0 { if size <= 0 {
return nil, errors.New("Must provide a positive size") return nil, errors.New("must provide a positive size")
} }
c := &LRU{ c := &LRU{
size: size, size: size,
@ -109,7 +109,7 @@ func (c *LRU) Remove(key interface{}) (present bool) {
} }
// RemoveOldest removes the oldest item from the cache. // RemoveOldest removes the oldest item from the cache.
func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) { func (c *LRU) RemoveOldest() (key, value interface{}, ok bool) {
ent := c.evictList.Back() ent := c.evictList.Back()
if ent != nil { if ent != nil {
c.removeElement(ent) c.removeElement(ent)
@ -120,7 +120,7 @@ func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
} }
// GetOldest returns the oldest entry // GetOldest returns the oldest entry
func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) { func (c *LRU) GetOldest() (key, 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)

@ -1,3 +1,4 @@
// Package simplelru provides simple LRU implementation based on build-in container/list.
package simplelru package simplelru
// LRUCache is the interface for simple LRU cache. // LRUCache is the interface for simple LRU cache.
@ -34,6 +35,6 @@ type LRUCache interface {
// Clears all cache entries. // Clears all cache entries.
Purge() Purge()
// Resizes cache, returning number evicted // Resizes cache, returning number evicted
Resize(int) int Resize(int) int
} }

@ -0,0 +1,16 @@
package lru
import (
"crypto/rand"
"math"
"math/big"
"testing"
)
func getRand(tb testing.TB) int64 {
out, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err != nil {
tb.Fatal(err)
}
return out.Int64()
}

@ -122,7 +122,8 @@ func (a *API) GetTextMessages(channel chat1.ChatChannel, unreadOnly bool) ([]cha
return res, nil return res, nil
} }
func (a *API) SendMessage(channel chat1.ChatChannel, body string, args ...interface{}) (SendResponse, error) { func (a *API) SendMessage(channel chat1.ChatChannel, body string, args ...interface{}) (resp SendResponse, err error) {
defer a.Trace(&err, "SendMessage")()
arg := newSendArg(sendMessageOptions{ arg := newSendArg(sendMessageOptions{
Channel: channel, Channel: channel,
Message: sendMessageBody{ Message: sendMessageBody{
@ -139,7 +140,8 @@ func (a *API) Broadcast(body string, args ...interface{}) (SendResponse, error)
}, fmt.Sprintf(body, args...)) }, fmt.Sprintf(body, args...))
} }
func (a *API) SendMessageByConvID(convID chat1.ConvIDStr, body string, args ...interface{}) (SendResponse, error) { func (a *API) SendMessageByConvID(convID chat1.ConvIDStr, body string, args ...interface{}) (resp SendResponse, err error) {
defer a.Trace(&err, "SendMessageByConvID")()
arg := newSendArg(sendMessageOptions{ arg := newSendArg(sendMessageOptions{
ConversationID: convID, ConversationID: convID,
Message: sendMessageBody{ Message: sendMessageBody{
@ -150,7 +152,8 @@ func (a *API) SendMessageByConvID(convID chat1.ConvIDStr, body string, args ...i
} }
// SendMessageByTlfName sends a message on the given TLF name // SendMessageByTlfName sends a message on the given TLF name
func (a *API) SendMessageByTlfName(tlfName string, body string, args ...interface{}) (SendResponse, error) { func (a *API) SendMessageByTlfName(tlfName string, body string, args ...interface{}) (resp SendResponse, err error) {
defer a.Trace(&err, "SendMessageByTlfName")()
arg := newSendArg(sendMessageOptions{ arg := newSendArg(sendMessageOptions{
Channel: chat1.ChatChannel{ Channel: chat1.ChatChannel{
Name: tlfName, Name: tlfName,
@ -162,7 +165,8 @@ func (a *API) SendMessageByTlfName(tlfName string, body string, args ...interfac
return a.doSend(arg) return a.doSend(arg)
} }
func (a *API) SendMessageByTeamName(teamName string, inChannel *string, body string, args ...interface{}) (SendResponse, error) { func (a *API) SendMessageByTeamName(teamName string, inChannel *string, body string, args ...interface{}) (resp SendResponse, err error) {
defer a.Trace(&err, "SendMessageByTeamName")()
channel := "general" channel := "general"
if inChannel != nil { if inChannel != nil {
channel = *inChannel channel = *inChannel

@ -6,6 +6,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"math"
"os" "os"
"os/exec" "os/exec"
"runtime" "runtime"
@ -45,10 +46,10 @@ type Subscription struct {
} }
func NewSubscription() *Subscription { func NewSubscription() *Subscription {
newMsgsCh := make(chan SubscriptionMessage, 100) newMsgsCh := make(chan SubscriptionMessage, 250)
newConvsCh := make(chan SubscriptionConversation, 100) newConvsCh := make(chan SubscriptionConversation, 250)
newWalletCh := make(chan SubscriptionWalletEvent, 100) newWalletCh := make(chan SubscriptionWalletEvent, 250)
errorCh := make(chan error, 100) errorCh := make(chan error, 250)
shutdownCh := make(chan struct{}) shutdownCh := make(chan struct{})
return &Subscription{ return &Subscription{
DebugOutput: NewDebugOutput("Subscription"), DebugOutput: NewDebugOutput("Subscription"),
@ -137,6 +138,8 @@ type RunOptions struct {
EnableTyping bool EnableTyping bool
// Disable bot lite mode // Disable bot lite mode
DisableBotLiteMode bool DisableBotLiteMode bool
// Number of processes to spin up to connect to the keybase service
NumPipes int
} }
func (r RunOptions) Location() string { func (r RunOptions) Location() string {
@ -164,13 +167,20 @@ func Start(runOpts RunOptions, opts ...func(*API)) (*API, error) {
return api, nil return api, nil
} }
type apiPipe struct {
sync.Mutex
input io.Writer
output *bufio.Reader
cmd *exec.Cmd
}
// API is the main object used for communicating with the Keybase JSON API // API is the main object used for communicating with the Keybase JSON API
type API struct { type API struct {
sync.Mutex sync.Mutex
*DebugOutput *DebugOutput
apiInput io.Writer // Round robin hand out API pipes to allow concurrent API requests.
apiOutput *bufio.Reader pipeIdx int
apiCmd *exec.Cmd pipes []*apiPipe
username string username string
runOpts RunOptions runOpts RunOptions
subscriptions []*Subscription subscriptions []*Subscription
@ -282,12 +292,15 @@ func (a *API) auth() (string, error) {
func (a *API) startPipes() (err error) { func (a *API) startPipes() (err error) {
a.Lock() a.Lock()
defer a.Unlock() defer a.Unlock()
if a.apiCmd != nil { for _, pipe := range a.pipes {
if err := a.apiCmd.Process.Kill(); err != nil { if pipe.cmd != nil {
return fmt.Errorf("unable to kill previous API command %v", err) if err := pipe.cmd.Process.Kill(); err != nil {
return fmt.Errorf("unable to kill previous API command %v", err)
}
} }
pipe.cmd = nil
} }
a.apiCmd = nil a.pipes = nil
if a.runOpts.StartService { if a.runOpts.StartService {
args := []string{fmt.Sprintf("-enable-bot-lite-mode=%v", a.runOpts.DisableBotLiteMode), "service"} args := []string{fmt.Sprintf("-enable-bot-lite-mode=%v", a.runOpts.DisableBotLiteMode), "service"}
@ -306,30 +319,39 @@ func (a *API) startPipes() (err error) {
a.Debug("unable to set notifiation settings %v", err) a.Debug("unable to set notifiation settings %v", err)
} }
a.apiCmd = a.runOpts.Command("chat", "api") // Startup NumPipes processes to the keybase chat api
if a.apiInput, err = a.apiCmd.StdinPipe(); err != nil { for i := 0; i < int(math.Max(float64(a.runOpts.NumPipes), 1)); i++ {
return fmt.Errorf("unable to get api stdin: %v", err) pipe := apiPipe{}
} pipe.cmd = a.runOpts.Command("chat", "api")
output, err := a.apiCmd.StdoutPipe() if pipe.input, err = pipe.cmd.StdinPipe(); err != nil {
if err != nil { return fmt.Errorf("unable to get api stdin: %v", err)
return fmt.Errorf("unable to get api stdout: %v", err) }
} output, err := pipe.cmd.StdoutPipe()
if runtime.GOOS != "windows" { if err != nil {
a.apiCmd.ExtraFiles = []*os.File{output.(*os.File)} return fmt.Errorf("unable to get api stdout: %v", err)
} }
if err := a.apiCmd.Start(); err != nil { if runtime.GOOS != "windows" {
return fmt.Errorf("unable to run chat api cmd: %v", err) pipe.cmd.ExtraFiles = []*os.File{output.(*os.File)}
}
if err := pipe.cmd.Start(); err != nil {
return fmt.Errorf("unable to run chat api cmd: %v", err)
}
pipe.output = bufio.NewReader(output)
a.pipes = append(a.pipes, &pipe)
} }
a.apiOutput = bufio.NewReader(output)
return nil return nil
} }
func (a *API) getAPIPipesLocked() (io.Writer, *bufio.Reader, error) { func (a *API) getAPIPipes() (*apiPipe, error) {
// this should only be called inside a lock a.Lock()
if a.apiCmd == nil { defer a.Unlock()
return nil, nil, errAPIDisconnected idx := a.pipeIdx % len(a.pipes)
a.pipeIdx++
pipe := a.pipes[idx]
if pipe.cmd == nil {
return nil, errAPIDisconnected
} }
return a.apiInput, a.apiOutput, nil return pipe, nil
} }
func (a *API) GetUsername() string { func (a *API) GetUsername() string {
@ -337,21 +359,21 @@ func (a *API) GetUsername() string {
} }
func (a *API) doSend(arg interface{}) (resp SendResponse, err error) { func (a *API) doSend(arg interface{}) (resp SendResponse, err error) {
a.Lock()
defer a.Unlock()
bArg, err := json.Marshal(arg) bArg, err := json.Marshal(arg)
if err != nil { if err != nil {
return SendResponse{}, fmt.Errorf("unable to send arg: %+v: %v", arg, err) return SendResponse{}, fmt.Errorf("unable to send arg: %+v: %v", arg, err)
} }
input, output, err := a.getAPIPipesLocked() pipe, err := a.getAPIPipes()
if err != nil { if err != nil {
return SendResponse{}, err return SendResponse{}, err
} }
if _, err := io.WriteString(input, string(bArg)); err != nil { pipe.Lock()
defer pipe.Unlock()
if _, err := io.WriteString(pipe.input, string(bArg)); err != nil {
return SendResponse{}, err return SendResponse{}, err
} }
responseRaw, err := output.ReadBytes('\n') responseRaw, err := pipe.output.ReadBytes('\n')
if err != nil { if err != nil {
return SendResponse{}, err return SendResponse{}, err
} }
@ -364,17 +386,17 @@ func (a *API) doSend(arg interface{}) (resp SendResponse, err error) {
} }
func (a *API) doFetch(apiInput string) ([]byte, error) { func (a *API) doFetch(apiInput string) ([]byte, error) {
a.Lock() pipe, err := a.getAPIPipes()
defer a.Unlock()
input, output, err := a.getAPIPipesLocked()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if _, err := io.WriteString(input, apiInput); err != nil { pipe.Lock()
defer pipe.Unlock()
if _, err := io.WriteString(pipe.input, apiInput); err != nil {
return nil, err return nil, err
} }
byteOutput, err := output.ReadBytes('\n') byteOutput, err := pipe.output.ReadBytes('\n')
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -412,16 +434,22 @@ func (a *API) Listen(opts ListenOptions) (*Subscription, error) {
} }
boutput.Scan() boutput.Scan()
t := boutput.Text() t := boutput.Text()
submitErr := func(err error) {
if len(sub.errorCh)*2 > cap(sub.errorCh) {
a.Debug("large errorCh queue: len: %d cap: %d ", len(sub.errorCh), cap(sub.errorCh))
}
sub.errorCh <- err
}
var typeHolder TypeHolder var typeHolder TypeHolder
if err := json.Unmarshal([]byte(t), &typeHolder); err != nil { if err := json.Unmarshal([]byte(t), &typeHolder); err != nil {
sub.errorCh <- fmt.Errorf("err: %v, data: %v", err, t) submitErr(fmt.Errorf("err: %v, data: %v", err, t))
break break
} }
switch typeHolder.Type { switch typeHolder.Type {
case "chat": case "chat":
var notification chat1.MsgNotification var notification chat1.MsgNotification
if err := json.Unmarshal([]byte(t), &notification); err != nil { if err := json.Unmarshal([]byte(t), &notification); err != nil {
sub.errorCh <- fmt.Errorf("err: %v, data: %v", err, t) submitErr(fmt.Errorf("err: %v, data: %v", err, t))
break break
} }
if notification.Error != nil { if notification.Error != nil {
@ -434,12 +462,15 @@ func (a *API) Listen(opts ListenOptions) (*Subscription, error) {
Channel: notification.Msg.Channel, Channel: notification.Msg.Channel,
}, },
} }
if len(sub.newMsgsCh)*2 > cap(sub.newMsgsCh) {
a.Debug("large newMsgsCh queue: len: %d cap: %d ", len(sub.newMsgsCh), cap(sub.newMsgsCh))
}
sub.newMsgsCh <- subscriptionMessage sub.newMsgsCh <- subscriptionMessage
} }
case "chat_conv": case "chat_conv":
var notification chat1.ConvNotification var notification chat1.ConvNotification
if err := json.Unmarshal([]byte(t), &notification); err != nil { if err := json.Unmarshal([]byte(t), &notification); err != nil {
sub.errorCh <- fmt.Errorf("err: %v, data: %v", err, t) submitErr(fmt.Errorf("err: %v, data: %v", err, t))
break break
} }
if notification.Error != nil { if notification.Error != nil {
@ -448,15 +479,21 @@ func (a *API) Listen(opts ListenOptions) (*Subscription, error) {
subscriptionConv := SubscriptionConversation{ subscriptionConv := SubscriptionConversation{
Conversation: *notification.Conv, Conversation: *notification.Conv,
} }
if len(sub.newConvsCh)*2 > cap(sub.newConvsCh) {
a.Debug("large newConvsCh queue: len: %d cap: %d ", len(sub.newConvsCh), cap(sub.newConvsCh))
}
sub.newConvsCh <- subscriptionConv sub.newConvsCh <- subscriptionConv
} }
case "wallet": case "wallet":
var holder PaymentHolder var holder PaymentHolder
if err := json.Unmarshal([]byte(t), &holder); err != nil { if err := json.Unmarshal([]byte(t), &holder); err != nil {
sub.errorCh <- fmt.Errorf("err: %v, data: %v", err, t) submitErr(fmt.Errorf("err: %v, data: %v", err, t))
break break
} }
subscriptionPayment := SubscriptionWalletEvent(holder) subscriptionPayment := SubscriptionWalletEvent(holder)
if len(sub.newWalletCh)*2 > cap(sub.newWalletCh) {
a.Debug("large newWalletCh queue: len: %d cap: %d ", len(sub.newWalletCh), cap(sub.newWalletCh))
}
sub.newWalletCh <- subscriptionPayment sub.newWalletCh <- subscriptionPayment
default: default:
continue continue
@ -518,7 +555,6 @@ func (a *API) Listen(opts ListenOptions) (*Subscription, error) {
} }
boutput := bufio.NewScanner(output) boutput := bufio.NewScanner(output)
if err := p.Start(); err != nil { if err := p.Start(); err != nil {
a.Debug("Listen: failed to make listen scanner: %s", err) a.Debug("Listen: failed to make listen scanner: %s", err)
time.Sleep(pause) time.Sleep(pause)
continue continue
@ -568,10 +604,12 @@ func (a *API) Shutdown() (err error) {
for _, sub := range a.subscriptions { for _, sub := range a.subscriptions {
sub.Shutdown() sub.Shutdown()
} }
if a.apiCmd != nil { for _, pipe := range a.pipes {
a.Debug("waiting for API command") if pipe.cmd != nil {
if err := a.apiCmd.Wait(); err != nil { a.Debug("waiting for API command")
return err if err := pipe.cmd.Wait(); err != nil {
return err
}
} }
} }

@ -1,7 +1,7 @@
# Emoji # Emoji
Emoji is a simple golang package. Emoji is a simple golang package.
[![wercker status](https://app.wercker.com/status/7bef60de2c6d3e0e6c13d56b2393c5d8/s/master "wercker status")](https://app.wercker.com/project/byKey/7bef60de2c6d3e0e6c13d56b2393c5d8) ![master workflow](https://github.com/kyokomi/emoji/actions/workflows/go.yml/badge.svg)
[![Coverage Status](https://coveralls.io/repos/kyokomi/emoji/badge.png?branch=master)](https://coveralls.io/r/kyokomi/emoji?branch=master) [![Coverage Status](https://coveralls.io/repos/kyokomi/emoji/badge.png?branch=master)](https://coveralls.io/r/kyokomi/emoji?branch=master)
[![GoDoc](https://pkg.go.dev/badge/github.com/kyokomi/emoji.svg)](https://pkg.go.dev/github.com/kyokomi/emoji/v2) [![GoDoc](https://pkg.go.dev/badge/github.com/kyokomi/emoji.svg)](https://pkg.go.dev/github.com/kyokomi/emoji/v2)

@ -84,6 +84,7 @@ func emojiCode() map[string]string {
":UP!_button:": "\U0001f199", ":UP!_button:": "\U0001f199",
":VS_button:": "\U0001f19a", ":VS_button:": "\U0001f19a",
":Virgo:": "\u264d", ":Virgo:": "\u264d",
":ZZZ:": "\U0001f4a4",
":a:": "\U0001f170\ufe0f", ":a:": "\U0001f170\ufe0f",
":ab:": "\U0001f18e", ":ab:": "\U0001f18e",
":abacus:": "\U0001f9ee", ":abacus:": "\U0001f9ee",
@ -298,6 +299,7 @@ func emojiCode() map[string]string {
":birthday_cake:": "\U0001f382", ":birthday_cake:": "\U0001f382",
":bison:": "\U0001f9ac", ":bison:": "\U0001f9ac",
":biting_lip:": "\U0001fae6", ":biting_lip:": "\U0001fae6",
":black_bird:": "\U0001f426\u200d\u2b1b",
":black_cat:": "\U0001f408\u200d\u2b1b", ":black_cat:": "\U0001f408\u200d\u2b1b",
":black_circle:": "\u26ab", ":black_circle:": "\u26ab",
":black_circle_for_record:": "\u23fa\ufe0f", ":black_circle_for_record:": "\u23fa\ufe0f",
@ -779,6 +781,7 @@ func emojiCode() map[string]string {
":dolphin:": "\U0001f42c", ":dolphin:": "\U0001f42c",
":dominica:": "\U0001f1e9\U0001f1f2", ":dominica:": "\U0001f1e9\U0001f1f2",
":dominican_republic:": "\U0001f1e9\U0001f1f4", ":dominican_republic:": "\U0001f1e9\U0001f1f4",
":donkey:": "\U0001facf",
":door:": "\U0001f6aa", ":door:": "\U0001f6aa",
":dotted_line_face:": "\U0001fae5", ":dotted_line_face:": "\U0001fae5",
":dotted_six-pointed_star:": "\U0001f52f", ":dotted_six-pointed_star:": "\U0001f52f",
@ -850,6 +853,7 @@ func emojiCode() map[string]string {
":empty_nest:": "\U0001fab9", ":empty_nest:": "\U0001fab9",
":end:": "\U0001f51a", ":end:": "\U0001f51a",
":england:": "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f", ":england:": "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f",
":enraged_face:": "\U0001f621",
":envelope:": "\u2709", ":envelope:": "\u2709",
":envelope_with_arrow:": "\U0001f4e9", ":envelope_with_arrow:": "\U0001f4e9",
":equatorial_guinea:": "\U0001f1ec\U0001f1f6", ":equatorial_guinea:": "\U0001f1ec\U0001f1f6",
@ -1837,12 +1841,14 @@ func emojiCode() map[string]string {
":flower_playing_cards:": "\U0001f3b4", ":flower_playing_cards:": "\U0001f3b4",
":flushed:": "\U0001f633", ":flushed:": "\U0001f633",
":flushed_face:": "\U0001f633", ":flushed_face:": "\U0001f633",
":flute:": "\U0001fa88",
":fly:": "\U0001fab0", ":fly:": "\U0001fab0",
":flying_disc:": "\U0001f94f", ":flying_disc:": "\U0001f94f",
":flying_saucer:": "\U0001f6f8", ":flying_saucer:": "\U0001f6f8",
":fog:": "\U0001f32b\ufe0f", ":fog:": "\U0001f32b\ufe0f",
":foggy:": "\U0001f301", ":foggy:": "\U0001f301",
":folded_hands:": "\U0001f64f", ":folded_hands:": "\U0001f64f",
":folding_hand_fan:": "\U0001faad",
":fondue:": "\U0001fad5", ":fondue:": "\U0001fad5",
":foot:": "\U0001f9b6", ":foot:": "\U0001f9b6",
":football:": "\U0001f3c8", ":football:": "\U0001f3c8",
@ -1906,6 +1912,7 @@ func emojiCode() map[string]string {
":gibraltar:": "\U0001f1ec\U0001f1ee", ":gibraltar:": "\U0001f1ec\U0001f1ee",
":gift:": "\U0001f381", ":gift:": "\U0001f381",
":gift_heart:": "\U0001f49d", ":gift_heart:": "\U0001f49d",
":ginger_root:": "\U0001fada",
":giraffe:": "\U0001f992", ":giraffe:": "\U0001f992",
":giraffe_face:": "\U0001f992", ":giraffe_face:": "\U0001f992",
":girl:": "\U0001f467", ":girl:": "\U0001f467",
@ -1932,6 +1939,7 @@ func emojiCode() map[string]string {
":golfing:": "\U0001f3cc\ufe0f", ":golfing:": "\U0001f3cc\ufe0f",
":golfing_man:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f", ":golfing_man:": "\U0001f3cc\ufe0f\u200d\u2642\ufe0f",
":golfing_woman:": "\U0001f3cc\ufe0f\u200d\u2640\ufe0f", ":golfing_woman:": "\U0001f3cc\ufe0f\u200d\u2640\ufe0f",
":goose:": "\U0001fabf",
":gorilla:": "\U0001f98d", ":gorilla:": "\U0001f98d",
":graduation_cap:": "\U0001f393", ":graduation_cap:": "\U0001f393",
":grapes:": "\U0001f347", ":grapes:": "\U0001f347",
@ -1945,6 +1953,7 @@ func emojiCode() map[string]string {
":greenland:": "\U0001f1ec\U0001f1f1", ":greenland:": "\U0001f1ec\U0001f1f1",
":grenada:": "\U0001f1ec\U0001f1e9", ":grenada:": "\U0001f1ec\U0001f1e9",
":grey_exclamation:": "\u2755", ":grey_exclamation:": "\u2755",
":grey_heart:": "\U0001fa76",
":grey_question:": "\u2754", ":grey_question:": "\u2754",
":grimacing:": "\U0001f62c", ":grimacing:": "\U0001f62c",
":grimacing_face:": "\U0001f62c", ":grimacing_face:": "\U0001f62c",
@ -1976,6 +1985,7 @@ func emojiCode() map[string]string {
":guitar:": "\U0001f3b8", ":guitar:": "\U0001f3b8",
":gun:": "\U0001f52b", ":gun:": "\U0001f52b",
":guyana:": "\U0001f1ec\U0001f1fe", ":guyana:": "\U0001f1ec\U0001f1fe",
":hair_pick:": "\U0001faae",
":haircut:": "\U0001f487\u200d\u2640\ufe0f", ":haircut:": "\U0001f487\u200d\u2640\ufe0f",
":haircut_man:": "\U0001f487\u200d\u2642\ufe0f", ":haircut_man:": "\U0001f487\u200d\u2642\ufe0f",
":haircut_woman:": "\U0001f487\u200d\u2640\ufe0f", ":haircut_woman:": "\U0001f487\u200d\u2640\ufe0f",
@ -2094,6 +2104,7 @@ func emojiCode() map[string]string {
":hushed:": "\U0001f62f", ":hushed:": "\U0001f62f",
":hushed_face:": "\U0001f62f", ":hushed_face:": "\U0001f62f",
":hut:": "\U0001f6d6", ":hut:": "\U0001f6d6",
":hyacinth:": "\U0001fabb",
":i_love_you_hand_sign:": "\U0001f91f", ":i_love_you_hand_sign:": "\U0001f91f",
":ice:": "\U0001f9ca", ":ice:": "\U0001f9ca",
":ice_cream:": "\U0001f368", ":ice_cream:": "\U0001f368",
@ -2142,6 +2153,7 @@ func emojiCode() map[string]string {
":japanese_ogre:": "\U0001f479", ":japanese_ogre:": "\U0001f479",
":jar:": "\U0001fad9", ":jar:": "\U0001fad9",
":jeans:": "\U0001f456", ":jeans:": "\U0001f456",
":jellyfish:": "\U0001fabc",
":jersey:": "\U0001f1ef\U0001f1ea", ":jersey:": "\U0001f1ef\U0001f1ea",
":jigsaw:": "\U0001f9e9", ":jigsaw:": "\U0001f9e9",
":joker:": "\U0001f0cf", ":joker:": "\U0001f0cf",
@ -2175,6 +2187,7 @@ func emojiCode() map[string]string {
":keycap_9:": "9\ufe0f\u20e3", ":keycap_9:": "9\ufe0f\u20e3",
":keycap_star:": "*\ufe0f\u20e3", ":keycap_star:": "*\ufe0f\u20e3",
":keycap_ten:": "\U0001f51f", ":keycap_ten:": "\U0001f51f",
":khanda:": "\U0001faaf",
":kick_scooter:": "\U0001f6f4", ":kick_scooter:": "\U0001f6f4",
":kimono:": "\U0001f458", ":kimono:": "\U0001f458",
":kiribati:": "\U0001f1f0\U0001f1ee", ":kiribati:": "\U0001f1f0\U0001f1ee",
@ -2261,6 +2274,7 @@ func emojiCode() map[string]string {
":left_speech_bubble:": "\U0001f5e8\ufe0f", ":left_speech_bubble:": "\U0001f5e8\ufe0f",
":leftwards_arrow_with_hook:": "\u21a9\ufe0f", ":leftwards_arrow_with_hook:": "\u21a9\ufe0f",
":leftwards_hand:": "\U0001faf2", ":leftwards_hand:": "\U0001faf2",
":leftwards_pushing_hand:": "\U0001faf7",
":leg:": "\U0001f9b5", ":leg:": "\U0001f9b5",
":lemon:": "\U0001f34b", ":lemon:": "\U0001f34b",
":leo:": "\u264c", ":leo:": "\u264c",
@ -2271,6 +2285,7 @@ func emojiCode() map[string]string {
":libra:": "\u264e", ":libra:": "\u264e",
":libya:": "\U0001f1f1\U0001f1fe", ":libya:": "\U0001f1f1\U0001f1fe",
":liechtenstein:": "\U0001f1f1\U0001f1ee", ":liechtenstein:": "\U0001f1f1\U0001f1ee",
":light_blue_heart:": "\U0001fa75",
":light_bulb:": "\U0001f4a1", ":light_bulb:": "\U0001f4a1",
":light_rail:": "\U0001f688", ":light_rail:": "\U0001f688",
":lightning:": "\U0001f329\ufe0f", ":lightning:": "\U0001f329\ufe0f",
@ -2807,6 +2822,7 @@ func emojiCode() map[string]string {
":map:": "\U0001f5fa", ":map:": "\U0001f5fa",
":map_of_Japan:": "\U0001f5fe", ":map_of_Japan:": "\U0001f5fe",
":maple_leaf:": "\U0001f341", ":maple_leaf:": "\U0001f341",
":maracas:": "\U0001fa87",
":marshall_islands:": "\U0001f1f2\U0001f1ed", ":marshall_islands:": "\U0001f1f2\U0001f1ed",
":martial_arts_uniform:": "\U0001f94b", ":martial_arts_uniform:": "\U0001f94b",
":martinique:": "\U0001f1f2\U0001f1f6", ":martinique:": "\U0001f1f2\U0001f1f6",
@ -2911,6 +2927,7 @@ func emojiCode() map[string]string {
":moon:": "\U0001f314", ":moon:": "\U0001f314",
":moon_cake:": "\U0001f96e", ":moon_cake:": "\U0001f96e",
":moon_viewing_ceremony:": "\U0001f391", ":moon_viewing_ceremony:": "\U0001f391",
":moose:": "\U0001face",
":morocco:": "\U0001f1f2\U0001f1e6", ":morocco:": "\U0001f1f2\U0001f1e6",
":mortar_board:": "\U0001f393", ":mortar_board:": "\U0001f393",
":mosque:": "\U0001f54c", ":mosque:": "\U0001f54c",
@ -3159,6 +3176,7 @@ func emojiCode() map[string]string {
":passport_control:": "\U0001f6c2", ":passport_control:": "\U0001f6c2",
":pause_button:": "\u23f8", ":pause_button:": "\u23f8",
":paw_prints:": "\U0001f43e", ":paw_prints:": "\U0001f43e",
":pea_pod:": "\U0001fadb",
":peace:": "\u262e", ":peace:": "\u262e",
":peace_symbol:": "\u262e\ufe0f", ":peace_symbol:": "\u262e\ufe0f",
":peach:": "\U0001f351", ":peach:": "\U0001f351",
@ -3410,6 +3428,7 @@ func emojiCode() map[string]string {
":pine_decoration:": "\U0001f38d", ":pine_decoration:": "\U0001f38d",
":pineapple:": "\U0001f34d", ":pineapple:": "\U0001f34d",
":ping_pong:": "\U0001f3d3", ":ping_pong:": "\U0001f3d3",
":pink_heart:": "\U0001fa77",
":pirate_flag:": "\U0001f3f4\u200d\u2620\ufe0f", ":pirate_flag:": "\U0001f3f4\u200d\u2620\ufe0f",
":pisces:": "\u2653", ":pisces:": "\u2653",
":pitcairn_islands:": "\U0001f1f5\U0001f1f3", ":pitcairn_islands:": "\U0001f1f5\U0001f1f3",
@ -3486,7 +3505,7 @@ func emojiCode() map[string]string {
":pouring_liquid:": "\U0001fad7", ":pouring_liquid:": "\U0001fad7",
":pout:": "\U0001f621", ":pout:": "\U0001f621",
":pouting_cat:": "\U0001f63e", ":pouting_cat:": "\U0001f63e",
":pouting_face:": "\U0001f621", ":pouting_face:": "\U0001f64e",
":pouting_man:": "\U0001f64e\u200d\u2642\ufe0f", ":pouting_man:": "\U0001f64e\u200d\u2642\ufe0f",
":pouting_woman:": "\U0001f64e\u200d\u2640\ufe0f", ":pouting_woman:": "\U0001f64e\u200d\u2640\ufe0f",
":pray:": "\U0001f64f", ":pray:": "\U0001f64f",
@ -3641,6 +3660,7 @@ func emojiCode() map[string]string {
":right_facing_fist_tone4:": "\U0001f91c\U0001f3fe", ":right_facing_fist_tone4:": "\U0001f91c\U0001f3fe",
":right_facing_fist_tone5:": "\U0001f91c\U0001f3ff", ":right_facing_fist_tone5:": "\U0001f91c\U0001f3ff",
":rightwards_hand:": "\U0001faf1", ":rightwards_hand:": "\U0001faf1",
":rightwards_pushing_hand:": "\U0001faf8",
":ring:": "\U0001f48d", ":ring:": "\U0001f48d",
":ring_buoy:": "\U0001f6df", ":ring_buoy:": "\U0001f6df",
":ringed_planet:": "\U0001fa90", ":ringed_planet:": "\U0001fa90",
@ -3747,6 +3767,7 @@ func emojiCode() map[string]string {
":seven_oclock:": "\U0001f556", ":seven_oclock:": "\U0001f556",
":sewing_needle:": "\U0001faa1", ":sewing_needle:": "\U0001faa1",
":seychelles:": "\U0001f1f8\U0001f1e8", ":seychelles:": "\U0001f1f8\U0001f1e8",
":shaking_face:": "\U0001fae8",
":shallow_pan_of_food:": "\U0001f958", ":shallow_pan_of_food:": "\U0001f958",
":shamrock:": "\u2618\ufe0f", ":shamrock:": "\u2618\ufe0f",
":shark:": "\U0001f988", ":shark:": "\U0001f988",
@ -4275,9 +4296,11 @@ func emojiCode() map[string]string {
":wind_face:": "\U0001f32c", ":wind_face:": "\U0001f32c",
":window:": "\U0001fa9f", ":window:": "\U0001fa9f",
":wine_glass:": "\U0001f377", ":wine_glass:": "\U0001f377",
":wing:": "\U0001fabd",
":wink:": "\U0001f609", ":wink:": "\U0001f609",
":winking_face:": "\U0001f609", ":winking_face:": "\U0001f609",
":winking_face_with_tongue:": "\U0001f61c", ":winking_face_with_tongue:": "\U0001f61c",
":wireless:": "\U0001f6dc",
":wolf:": "\U0001f43a", ":wolf:": "\U0001f43a",
":woman:": "\U0001f469", ":woman:": "\U0001f469",
":woman-biking:": "\U0001f6b4\u200d\u2640\ufe0f", ":woman-biking:": "\U0001f6b4\u200d\u2640\ufe0f",
@ -4708,6 +4731,7 @@ func emojiCode() map[string]string {
":writing_hand_tone5:": "\u270d\U0001f3ff", ":writing_hand_tone5:": "\u270d\U0001f3ff",
":x:": "\u274c", ":x:": "\u274c",
":x-ray:": "\U0001fa7b", ":x-ray:": "\U0001fa7b",
":x_ray:": "\U0001fa7b",
":yarn:": "\U0001f9f6", ":yarn:": "\U0001f9f6",
":yawning_face:": "\U0001f971", ":yawning_face:": "\U0001f971",
":yellow_circle:": "\U0001f7e1", ":yellow_circle:": "\U0001f7e1",
@ -5470,6 +5494,7 @@ func emojiRevCode() map[string][]string {
"\U0001f424": {":baby_chick:"}, "\U0001f424": {":baby_chick:"},
"\U0001f425": {":hatched_chick:", ":front-facing_baby_chick:"}, "\U0001f425": {":hatched_chick:", ":front-facing_baby_chick:"},
"\U0001f426": {":bird:"}, "\U0001f426": {":bird:"},
"\U0001f426\u200d\u2b1b": {":black_bird:"},
"\U0001f427": {":penguin:"}, "\U0001f427": {":penguin:"},
"\U0001f428": {":koala:"}, "\U0001f428": {":koala:"},
"\U0001f429": {":poodle:"}, "\U0001f429": {":poodle:"},
@ -6104,7 +6129,7 @@ func emojiRevCode() map[string][]string {
"\U0001f4a1": {":bulb:", ":light_bulb:"}, "\U0001f4a1": {":bulb:", ":light_bulb:"},
"\U0001f4a2": {":anger:", ":anger_symbol:"}, "\U0001f4a2": {":anger:", ":anger_symbol:"},
"\U0001f4a3": {":bomb:"}, "\U0001f4a3": {":bomb:"},
"\U0001f4a4": {":zzz:"}, "\U0001f4a4": {":ZZZ:", ":zzz:"},
"\U0001f4a5": {":boom:", ":collision:"}, "\U0001f4a5": {":boom:", ":collision:"},
"\U0001f4a6": {":sweat_drops:", ":sweat_droplets:"}, "\U0001f4a6": {":sweat_drops:", ":sweat_droplets:"},
"\U0001f4a7": {":droplet:"}, "\U0001f4a7": {":droplet:"},
@ -6438,7 +6463,7 @@ func emojiRevCode() map[string][]string {
"\U0001f61e": {":disappointed:", ":disappointed_face:"}, "\U0001f61e": {":disappointed:", ":disappointed_face:"},
"\U0001f61f": {":worried:", ":worried_face:"}, "\U0001f61f": {":worried:", ":worried_face:"},
"\U0001f620": {":angry:", ":angry_face:"}, "\U0001f620": {":angry:", ":angry_face:"},
"\U0001f621": {":pout:", ":rage:", ":pouting_face:"}, "\U0001f621": {":pout:", ":rage:", ":enraged_face:"},
"\U0001f622": {":cry:", ":crying_face:"}, "\U0001f622": {":cry:", ":crying_face:"},
"\U0001f623": {":persevere:", ":persevering_face:"}, "\U0001f623": {":persevere:", ":persevering_face:"},
"\U0001f624": {":triumph:", ":face_with_steam_from_nose:"}, "\U0001f624": {":triumph:", ":face_with_steam_from_nose:"},
@ -6576,7 +6601,7 @@ func emojiRevCode() map[string][]string {
"\U0001f64d\U0001f3ff\u200d\u2642\ufe0f": {":man_frowning_tone5:"}, "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f": {":man_frowning_tone5:"},
"\U0001f64d\u200d\u2640\ufe0f": {":frowning_woman:", ":woman-frowning:", ":woman_frowning:", ":person_frowning:"}, "\U0001f64d\u200d\u2640\ufe0f": {":frowning_woman:", ":woman-frowning:", ":woman_frowning:", ":person_frowning:"},
"\U0001f64d\u200d\u2642\ufe0f": {":frowning_man:", ":man-frowning:", ":man_frowning:"}, "\U0001f64d\u200d\u2642\ufe0f": {":frowning_man:", ":man-frowning:", ":man_frowning:"},
"\U0001f64e": {":person_pouting:"}, "\U0001f64e": {":pouting_face:", ":person_pouting:"},
"\U0001f64e\U0001f3fb": {":person_pouting_tone1:"}, "\U0001f64e\U0001f3fb": {":person_pouting_tone1:"},
"\U0001f64e\U0001f3fb\u200d\u2640\ufe0f": {":woman_pouting_tone1:"}, "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f": {":woman_pouting_tone1:"},
"\U0001f64e\U0001f3fb\u200d\u2642\ufe0f": {":man_pouting_tone1:"}, "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f": {":man_pouting_tone1:"},
@ -6761,6 +6786,7 @@ func emojiRevCode() map[string][]string {
"\U0001f6d5": {":hindu_temple:"}, "\U0001f6d5": {":hindu_temple:"},
"\U0001f6d6": {":hut:"}, "\U0001f6d6": {":hut:"},
"\U0001f6d7": {":elevator:"}, "\U0001f6d7": {":elevator:"},
"\U0001f6dc": {":wireless:"},
"\U0001f6dd": {":playground_slide:"}, "\U0001f6dd": {":playground_slide:"},
"\U0001f6de": {":wheel:"}, "\U0001f6de": {":wheel:"},
"\U0001f6df": {":ring_buoy:"}, "\U0001f6df": {":ring_buoy:"},
@ -7422,10 +7448,13 @@ func emojiRevCode() map[string][]string {
"\U0001fa72": {":briefs:", ":swim_brief:"}, "\U0001fa72": {":briefs:", ":swim_brief:"},
"\U0001fa73": {":shorts:"}, "\U0001fa73": {":shorts:"},
"\U0001fa74": {":thong_sandal:"}, "\U0001fa74": {":thong_sandal:"},
"\U0001fa75": {":light_blue_heart:"},
"\U0001fa76": {":grey_heart:"},
"\U0001fa77": {":pink_heart:"},
"\U0001fa78": {":drop_of_blood:"}, "\U0001fa78": {":drop_of_blood:"},
"\U0001fa79": {":adhesive_bandage:"}, "\U0001fa79": {":adhesive_bandage:"},
"\U0001fa7a": {":stethoscope:"}, "\U0001fa7a": {":stethoscope:"},
"\U0001fa7b": {":x-ray:"}, "\U0001fa7b": {":x-ray:", ":x_ray:"},
"\U0001fa7c": {":crutch:"}, "\U0001fa7c": {":crutch:"},
"\U0001fa80": {":yo-yo:", ":yo_yo:"}, "\U0001fa80": {":yo-yo:", ":yo_yo:"},
"\U0001fa81": {":kite:"}, "\U0001fa81": {":kite:"},
@ -7434,6 +7463,8 @@ func emojiRevCode() map[string][]string {
"\U0001fa84": {":magic_wand:"}, "\U0001fa84": {":magic_wand:"},
"\U0001fa85": {":pinata:", ":piñata:"}, "\U0001fa85": {":pinata:", ":piñata:"},
"\U0001fa86": {":nesting_dolls:"}, "\U0001fa86": {":nesting_dolls:"},
"\U0001fa87": {":maracas:"},
"\U0001fa88": {":flute:"},
"\U0001fa90": {":ringed_planet:"}, "\U0001fa90": {":ringed_planet:"},
"\U0001fa91": {":chair:"}, "\U0001fa91": {":chair:"},
"\U0001fa92": {":razor:"}, "\U0001fa92": {":razor:"},
@ -7463,6 +7494,9 @@ func emojiRevCode() map[string][]string {
"\U0001faaa": {":identification_card:"}, "\U0001faaa": {":identification_card:"},
"\U0001faab": {":low_battery:"}, "\U0001faab": {":low_battery:"},
"\U0001faac": {":hamsa:"}, "\U0001faac": {":hamsa:"},
"\U0001faad": {":folding_hand_fan:"},
"\U0001faae": {":hair_pick:"},
"\U0001faaf": {":khanda:"},
"\U0001fab0": {":fly:"}, "\U0001fab0": {":fly:"},
"\U0001fab1": {":worm:"}, "\U0001fab1": {":worm:"},
"\U0001fab2": {":beetle:"}, "\U0001fab2": {":beetle:"},
@ -7474,12 +7508,18 @@ func emojiRevCode() map[string][]string {
"\U0001fab8": {":coral:"}, "\U0001fab8": {":coral:"},
"\U0001fab9": {":empty_nest:"}, "\U0001fab9": {":empty_nest:"},
"\U0001faba": {":nest_with_eggs:"}, "\U0001faba": {":nest_with_eggs:"},
"\U0001fabb": {":hyacinth:"},
"\U0001fabc": {":jellyfish:"},
"\U0001fabd": {":wing:"},
"\U0001fabf": {":goose:"},
"\U0001fac0": {":anatomical_heart:"}, "\U0001fac0": {":anatomical_heart:"},
"\U0001fac1": {":lungs:"}, "\U0001fac1": {":lungs:"},
"\U0001fac2": {":people_hugging:"}, "\U0001fac2": {":people_hugging:"},
"\U0001fac3": {":pregnant_man:"}, "\U0001fac3": {":pregnant_man:"},
"\U0001fac4": {":pregnant_person:"}, "\U0001fac4": {":pregnant_person:"},
"\U0001fac5": {":person_with_crown:"}, "\U0001fac5": {":person_with_crown:"},
"\U0001face": {":moose:"},
"\U0001facf": {":donkey:"},
"\U0001fad0": {":blueberries:"}, "\U0001fad0": {":blueberries:"},
"\U0001fad1": {":bell_pepper:"}, "\U0001fad1": {":bell_pepper:"},
"\U0001fad2": {":olive:"}, "\U0001fad2": {":olive:"},
@ -7490,6 +7530,8 @@ func emojiRevCode() map[string][]string {
"\U0001fad7": {":pouring_liquid:"}, "\U0001fad7": {":pouring_liquid:"},
"\U0001fad8": {":beans:"}, "\U0001fad8": {":beans:"},
"\U0001fad9": {":jar:"}, "\U0001fad9": {":jar:"},
"\U0001fada": {":ginger_root:"},
"\U0001fadb": {":pea_pod:"},
"\U0001fae0": {":melting_face:"}, "\U0001fae0": {":melting_face:"},
"\U0001fae1": {":saluting_face:"}, "\U0001fae1": {":saluting_face:"},
"\U0001fae2": {":face_with_open_eyes_and_hand_over_mouth:"}, "\U0001fae2": {":face_with_open_eyes_and_hand_over_mouth:"},
@ -7498,6 +7540,7 @@ func emojiRevCode() map[string][]string {
"\U0001fae5": {":dotted_line_face:"}, "\U0001fae5": {":dotted_line_face:"},
"\U0001fae6": {":biting_lip:"}, "\U0001fae6": {":biting_lip:"},
"\U0001fae7": {":bubbles:"}, "\U0001fae7": {":bubbles:"},
"\U0001fae8": {":shaking_face:"},
"\U0001faf0": {":hand_with_index_finger_and_thumb_crossed:"}, "\U0001faf0": {":hand_with_index_finger_and_thumb_crossed:"},
"\U0001faf1": {":rightwards_hand:"}, "\U0001faf1": {":rightwards_hand:"},
"\U0001faf2": {":leftwards_hand:"}, "\U0001faf2": {":leftwards_hand:"},
@ -7505,6 +7548,8 @@ func emojiRevCode() map[string][]string {
"\U0001faf4": {":palm_up_hand:"}, "\U0001faf4": {":palm_up_hand:"},
"\U0001faf5": {":index_pointing_at_the_viewer:"}, "\U0001faf5": {":index_pointing_at_the_viewer:"},
"\U0001faf6": {":heart_hands:"}, "\U0001faf6": {":heart_hands:"},
"\U0001faf7": {":leftwards_pushing_hand:"},
"\U0001faf8": {":rightwards_pushing_hand:"},
"\u00a9\ufe0f": {":copyright:"}, "\u00a9\ufe0f": {":copyright:"},
"\u00ae\ufe0f": {":registered:"}, "\u00ae\ufe0f": {":registered:"},
"\u203c": {":double_exclamation_mark:"}, "\u203c": {":double_exclamation_mark:"},

@ -1,33 +0,0 @@
box: golang
build:
steps:
- setup-go-workspace
- script:
name: go version
code: go version
- script:
name: install tools
code: |
go install github.com/mattn/goveralls@latest
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
- script:
name: go get
code: |
go get ./...
- script:
name: go build
code: |
go build ./...
- script:
name: golangci-lint
code: |
golangci-lint run
- script:
name: go test
code: |
go test ./...
- script:
name: coveralls
code: |
goveralls -v -service wercker.com -repotoken $COVERALLS_TOKEN

@ -1,5 +1,42 @@
# Changelog # Changelog
## v4.10.0 - 2022-12-27
**Security**
* We are deprecating JWT middleware in this repository. Please use https://github.com/labstack/echo-jwt instead.
JWT middleware is moved to separate repository to allow us to bump/upgrade version of JWT implementation (`github.com/golang-jwt/jwt`) we are using
which we can not do in Echo core because this would break backwards compatibility guarantees we try to maintain.
* This minor version bumps minimum Go version to 1.17 (from 1.16) due `golang.org/x/` packages we depend on. There are
several vulnerabilities fixed in these libraries.
Echo still tries to support last 4 Go versions but there are occasions we can not guarantee this promise.
**Enhancements**
* Bump x/text to 0.3.8 [#2305](https://github.com/labstack/echo/pull/2305)
* Bump dependencies and add notes about Go releases we support [#2336](https://github.com/labstack/echo/pull/2336)
* Add helper interface for ProxyBalancer interface [#2316](https://github.com/labstack/echo/pull/2316)
* Expose `middleware.CreateExtractors` function so we can use it from echo-contrib repository [#2338](https://github.com/labstack/echo/pull/2338)
* Refactor func(Context) error to HandlerFunc [#2315](https://github.com/labstack/echo/pull/2315)
* Improve function comments [#2329](https://github.com/labstack/echo/pull/2329)
* Add new method HTTPError.WithInternal [#2340](https://github.com/labstack/echo/pull/2340)
* Replace io/ioutil package usages [#2342](https://github.com/labstack/echo/pull/2342)
* Add staticcheck to CI flow [#2343](https://github.com/labstack/echo/pull/2343)
* Replace relative path determination from proprietary to std [#2345](https://github.com/labstack/echo/pull/2345)
* Remove square brackets from ipv6 addresses in XFF (X-Forwarded-For header) [#2182](https://github.com/labstack/echo/pull/2182)
* Add testcases for some BodyLimit middleware configuration options [#2350](https://github.com/labstack/echo/pull/2350)
* Additional configuration options for RequestLogger and Logger middleware [#2341](https://github.com/labstack/echo/pull/2341)
* Add route to request log [#2162](https://github.com/labstack/echo/pull/2162)
* GitHub Workflows security hardening [#2358](https://github.com/labstack/echo/pull/2358)
* Add govulncheck to CI and bump dependencies [#2362](https://github.com/labstack/echo/pull/2362)
* Fix rate limiter docs [#2366](https://github.com/labstack/echo/pull/2366)
* Refactor how `e.Routes()` work and introduce `e.OnAddRouteHandler` callback [#2337](https://github.com/labstack/echo/pull/2337)
## v4.9.1 - 2022-10-12 ## v4.9.1 - 2022-10-12
**Fixes** **Fixes**

@ -10,8 +10,10 @@ check: lint vet race ## Check project
init: init:
@go install golang.org/x/lint/golint@latest @go install golang.org/x/lint/golint@latest
@go install honnef.co/go/tools/cmd/staticcheck@latest
lint: ## Lint the files lint: ## Lint the files
@staticcheck ${PKG_LIST}
@golint -set_exit_status ${PKG_LIST} @golint -set_exit_status ${PKG_LIST}
vet: ## Vet the files vet: ## Vet the files
@ -29,6 +31,6 @@ benchmark: ## Run benchmarks
help: ## Display this help screen help: ## Display this help screen
@grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
goversion ?= "1.16" goversion ?= "1.17"
test_version: ## Run tests inside Docker with given version (defaults to 1.15 oldest supported). Example: make test_version goversion=1.16 test_version: ## Run tests inside Docker with given version (defaults to 1.17 oldest supported). Example: make test_version goversion=1.17
@docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check" @docker run --rm -it -v $(shell pwd):/project golang:$(goversion) /bin/sh -c "cd /project && make init check"

@ -169,7 +169,11 @@ type (
// Redirect redirects the request to a provided URL with status code. // Redirect redirects the request to a provided URL with status code.
Redirect(code int, url string) error Redirect(code int, url string) error
// Error invokes the registered HTTP error handler. Generally used by middleware. // Error invokes the registered global HTTP error handler. Generally used by middleware.
// A side-effect of calling global error handler is that now Response has been committed (sent to the client) and
// middlewares up in chain can not change Response status code or Response body anymore.
//
// Avoid using this method in handlers as no middleware will be able to effectively handle errors after that.
Error(err error) Error(err error)
// Handler returns the matched handler by router. // Handler returns the matched handler by router.
@ -282,11 +286,16 @@ func (c *context) RealIP() string {
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" { if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
i := strings.IndexAny(ip, ",") i := strings.IndexAny(ip, ",")
if i > 0 { if i > 0 {
return strings.TrimSpace(ip[:i]) xffip := strings.TrimSpace(ip[:i])
xffip = strings.TrimPrefix(xffip, "[")
xffip = strings.TrimSuffix(xffip, "]")
return xffip
} }
return ip return ip
} }
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" { if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
ip = strings.TrimPrefix(ip, "[")
ip = strings.TrimSuffix(ip, "]")
return ip return ip
} }
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr) ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)

@ -3,50 +3,49 @@ Package echo implements high performance, minimalist Go web framework.
Example: Example:
package main package main
import ( import (
"net/http" "net/http"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
) )
// Handler // Handler
func hello(c echo.Context) error { func hello(c echo.Context) error {
return c.String(http.StatusOK, "Hello, World!") return c.String(http.StatusOK, "Hello, World!")
} }
func main() { func main() {
// Echo instance // Echo instance
e := echo.New() e := echo.New()
// Middleware // Middleware
e.Use(middleware.Logger()) e.Use(middleware.Logger())
e.Use(middleware.Recover()) e.Use(middleware.Recover())
// Routes // Routes
e.GET("/", hello) e.GET("/", hello)
// Start server // Start server
e.Logger.Fatal(e.Start(":1323")) e.Logger.Fatal(e.Start(":1323"))
} }
Learn more at https://echo.labstack.com Learn more at https://echo.labstack.com
*/ */
package echo package echo
import ( import (
"bytes"
stdContext "context" stdContext "context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/ioutil"
stdLog "log" stdLog "log"
"net" "net"
"net/http" "net/http"
"os"
"reflect" "reflect"
"runtime" "runtime"
"sync" "sync"
@ -62,20 +61,28 @@ import (
type ( type (
// Echo is the top-level framework instance. // Echo is the top-level framework instance.
//
// Goroutine safety: Do not mutate Echo instance fields after server has started. Accessing these
// fields from handlers/middlewares and changing field values at the same time leads to data-races.
// Adding new routes after the server has been started is also not safe!
Echo struct { Echo struct {
filesystem filesystem
common common
// startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get // startupMutex is mutex to lock Echo instance access during server configuration and startup. Useful for to get
// listener address info (on which interface/port was listener binded) without having data races. // listener address info (on which interface/port was listener binded) without having data races.
startupMutex sync.RWMutex startupMutex sync.RWMutex
colorer *color.Color
// premiddleware are middlewares that are run before routing is done. In case a pre-middleware returns
// an error the router is not executed and the request will end up in the global error handler.
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
router *Router
routers map[string]*Router
pool sync.Pool
StdLogger *stdLog.Logger StdLogger *stdLog.Logger
colorer *color.Color
premiddleware []MiddlewareFunc
middleware []MiddlewareFunc
maxParam *int
router *Router
routers map[string]*Router
pool sync.Pool
Server *http.Server Server *http.Server
TLSServer *http.Server TLSServer *http.Server
Listener net.Listener Listener net.Listener
@ -93,6 +100,9 @@ type (
Logger Logger Logger Logger
IPExtractor IPExtractor IPExtractor IPExtractor
ListenerNetwork string ListenerNetwork string
// OnAddRouteHandler is called when Echo adds new route to specific host router.
OnAddRouteHandler func(host string, route Route, handler HandlerFunc, middleware []MiddlewareFunc)
} }
// Route contains a handler and information for matching against requests. // Route contains a handler and information for matching against requests.
@ -116,7 +126,7 @@ type (
HandlerFunc func(c Context) error HandlerFunc func(c Context) error
// HTTPErrorHandler is a centralized HTTP error handler. // HTTPErrorHandler is a centralized HTTP error handler.
HTTPErrorHandler func(error, Context) HTTPErrorHandler func(err error, c Context)
// Validator is the interface that wraps the Validate function. // Validator is the interface that wraps the Validate function.
Validator interface { Validator interface {
@ -248,7 +258,7 @@ const (
const ( const (
// Version of Echo // Version of Echo
Version = "4.9.0" Version = "4.10.0"
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 = `
@ -527,21 +537,20 @@ func (e *Echo) File(path, file string, m ...MiddlewareFunc) *Route {
return e.file(path, file, e.GET, m...) return e.file(path, file, e.GET, m...)
} }
func (e *Echo) add(host, method, path string, handler HandlerFunc, middleware ...MiddlewareFunc) *Route { func (e *Echo) add(host, method, path string, handler HandlerFunc, middlewares ...MiddlewareFunc) *Route {
name := handlerName(handler)
router := e.findRouter(host) router := e.findRouter(host)
// FIXME: when handler+middleware are both nil ... make it behave like handler removal //FIXME: when handler+middleware are both nil ... make it behave like handler removal
router.Add(method, path, func(c Context) error { name := handlerName(handler)
h := applyMiddleware(handler, middleware...) route := router.add(method, path, name, func(c Context) error {
h := applyMiddleware(handler, middlewares...)
return h(c) return h(c)
}) })
r := &Route{
Method: method, if e.OnAddRouteHandler != nil {
Path: path, e.OnAddRouteHandler(host, *route, handler, middlewares)
Name: name,
} }
e.router.routes[method+path] = r
return r return route
} }
// Add registers a new route for an HTTP method and path with matching handler // Add registers a new route for an HTTP method and path with matching handler
@ -565,7 +574,7 @@ func (e *Echo) Group(prefix string, m ...MiddlewareFunc) (g *Group) {
return return
} }
// URI generates a URI from handler. // URI generates an URI from handler.
func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string { func (e *Echo) URI(handler HandlerFunc, params ...interface{}) string {
name := handlerName(handler) name := handlerName(handler)
return e.Reverse(name, params...) return e.Reverse(name, params...)
@ -578,35 +587,13 @@ func (e *Echo) URL(h HandlerFunc, params ...interface{}) string {
// Reverse generates an URL from route name and provided parameters. // Reverse generates an URL from route name and provided parameters.
func (e *Echo) Reverse(name string, params ...interface{}) string { func (e *Echo) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer) return e.router.Reverse(name, params...)
ln := len(params)
n := 0
for _, r := range e.router.routes {
if r.Name == name {
for i, l := 0, len(r.Path); i < l; i++ {
if (r.Path[i] == ':' || r.Path[i] == '*') && n < ln {
for ; i < l && r.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(r.Path[i])
}
}
break
}
}
return uri.String()
} }
// Routes returns the registered routes. // Routes returns the registered routes for default router.
// In case when Echo serves multiple hosts/domains use `e.Routers()["domain2.site"].Routes()` to get specific host routes.
func (e *Echo) Routes() []*Route { func (e *Echo) Routes() []*Route {
routes := make([]*Route, 0, len(e.router.routes)) return e.router.Routes()
for _, v := range e.router.routes {
routes = append(routes, v)
}
return routes
} }
// AcquireContext returns an empty `Context` instance from the pool. // AcquireContext returns an empty `Context` instance from the pool.
@ -626,7 +613,7 @@ func (e *Echo) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Acquire context // Acquire context
c := e.pool.Get().(*context) c := e.pool.Get().(*context)
c.Reset(r, w) c.Reset(r, w)
var h func(Context) error var h HandlerFunc
if e.premiddleware == nil { if e.premiddleware == nil {
e.findRouter(r.Host).Find(r.Method, GetPath(r), c) e.findRouter(r.Host).Find(r.Method, GetPath(r), c)
@ -700,7 +687,7 @@ func (e *Echo) StartTLS(address string, certFile, keyFile interface{}) (err erro
func filepathOrContent(fileOrContent interface{}) (content []byte, err error) { func filepathOrContent(fileOrContent interface{}) (content []byte, err error) {
switch v := fileOrContent.(type) { switch v := fileOrContent.(type) {
case string: case string:
return ioutil.ReadFile(v) return os.ReadFile(v)
case []byte: case []byte:
return v, nil return v, nil
default: default:
@ -884,6 +871,15 @@ func (he *HTTPError) SetInternal(err error) *HTTPError {
return he return he
} }
// WithInternal returns clone of HTTPError with err set to HTTPError.Internal field
func (he *HTTPError) WithInternal(err error) *HTTPError {
return &HTTPError{
Code: he.Code,
Message: he.Message,
Internal: err,
}
}
// Unwrap satisfies the Go 1.13 error wrapper interface. // Unwrap satisfies the Go 1.13 error wrapper interface.
func (he *HTTPError) Unwrap() error { func (he *HTTPError) Unwrap() error {
return he.Internal return he.Internal
@ -913,8 +909,8 @@ func WrapMiddleware(m func(http.Handler) http.Handler) MiddlewareFunc {
// GetPath returns RawPath, if it's empty returns Path from URL // GetPath returns RawPath, if it's empty returns Path from URL
// Difference between RawPath and Path is: // Difference between RawPath and Path is:
// * Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/. // - Path is where request path is stored. Value is stored in decoded form: /%47%6f%2f becomes /Go/.
// * RawPath is an optional field which only gets set if the default encoding is different from Path. // - RawPath is an optional field which only gets set if the default encoding is different from Path.
func GetPath(r *http.Request) string { func GetPath(r *http.Request) string {
path := r.URL.RawPath path := r.URL.RawPath
if path == "" { if path == "" {

@ -7,7 +7,6 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
) )
@ -125,7 +124,7 @@ func subFS(currentFs fs.FS, root string) (fs.FS, error) {
// we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS. // we need to make exception for `defaultFS` instances as it interprets root prefix differently from fs.FS.
// fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we // fs.Fs.Open does not like relative paths ("./", "../") and absolute paths at all but prior echo.Filesystem we
// were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs // were able to use paths like `./myfile.log`, `/etc/hosts` and these would work fine with `os.Open` but not with fs.Fs
if isRelativePath(root) { if !filepath.IsAbs(root) {
root = filepath.Join(dFS.prefix, root) root = filepath.Join(dFS.prefix, root)
} }
return &defaultFS{ return &defaultFS{
@ -136,21 +135,6 @@ func subFS(currentFs fs.FS, root string) (fs.FS, error) {
return fs.Sub(currentFs, root) return fs.Sub(currentFs, root)
} }
func isRelativePath(path string) bool {
if path == "" {
return true
}
if path[0] == '/' {
return false
}
if runtime.GOOS == "windows" && strings.IndexByte(path, ':') != -1 {
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#file_and_directory_names
// https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats
return false
}
return true
}
// MustSubFS creates sub FS from current filesystem or panic on failure. // MustSubFS creates sub FS from current filesystem or panic on failure.
// Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules. // Panic happens when `fsRoot` contains invalid path according to `fs.ValidPath` rules.
// //

@ -227,6 +227,8 @@ func ExtractIPFromRealIPHeader(options ...TrustOption) IPExtractor {
return func(req *http.Request) string { return func(req *http.Request) string {
realIP := req.Header.Get(HeaderXRealIP) realIP := req.Header.Get(HeaderXRealIP)
if realIP != "" { if realIP != "" {
realIP = strings.TrimPrefix(realIP, "[")
realIP = strings.TrimSuffix(realIP, "]")
if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) { if ip := net.ParseIP(realIP); ip != nil && checker.trust(ip) {
return realIP return realIP
} }
@ -248,7 +250,10 @@ func ExtractIPFromXFFHeader(options ...TrustOption) IPExtractor {
} }
ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP) ips := append(strings.Split(strings.Join(xffs, ","), ","), directIP)
for i := len(ips) - 1; i >= 0; i-- { for i := len(ips) - 1; i >= 0; i-- {
ip := net.ParseIP(strings.TrimSpace(ips[i])) ips[i] = strings.TrimSpace(ips[i])
ips[i] = strings.TrimPrefix(ips[i], "[")
ips[i] = strings.TrimSuffix(ips[i], "]")
ip := net.ParseIP(ips[i])
if ip == nil { if ip == nil {
// Unable to parse IP; cannot trust entire records // Unable to parse IP; cannot trust entire records
return directIP return directIP

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
@ -68,9 +67,9 @@ func BodyDumpWithConfig(config BodyDumpConfig) echo.MiddlewareFunc {
// Request // Request
reqBody := []byte{} reqBody := []byte{}
if c.Request().Body != nil { // Read if c.Request().Body != nil { // Read
reqBody, _ = ioutil.ReadAll(c.Request().Body) reqBody, _ = io.ReadAll(c.Request().Body)
} }
c.Request().Body = ioutil.NopCloser(bytes.NewBuffer(reqBody)) // Reset c.Request().Body = io.NopCloser(bytes.NewBuffer(reqBody)) // Reset
// Response // Response
resBody := new(bytes.Buffer) resBody := new(bytes.Buffer)

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"compress/gzip" "compress/gzip"
"io" "io"
"io/ioutil"
"net" "net"
"net/http" "net/http"
"strings" "strings"
@ -89,7 +88,7 @@ func GzipWithConfig(config GzipConfig) echo.MiddlewareFunc {
// nothing is written to body or error is returned. // nothing is written to body or error is returned.
// See issue #424, #407. // See issue #424, #407.
res.Writer = rw res.Writer = rw
w.Reset(ioutil.Discard) w.Reset(io.Discard)
} }
w.Close() w.Close()
pool.Put(w) pool.Put(w)
@ -135,7 +134,7 @@ func (w *gzipResponseWriter) Push(target string, opts *http.PushOptions) error {
func gzipCompressPool(config GzipConfig) sync.Pool { func gzipCompressPool(config GzipConfig) sync.Pool {
return sync.Pool{ return sync.Pool{
New: func() interface{} { New: func() interface{} {
w, err := gzip.NewWriterLevel(ioutil.Discard, config.Level) w, err := gzip.NewWriterLevel(io.Discard, config.Level)
if err != nil { if err != nil {
return err return err
} }

@ -119,7 +119,7 @@ func CSRFWithConfig(config CSRFConfig) echo.MiddlewareFunc {
config.CookieSecure = true config.CookieSecure = true
} }
extractors, err := createExtractors(config.TokenLookup, "") extractors, err := CreateExtractors(config.TokenLookup)
if err != nil { if err != nil {
panic(err) panic(err)
} }

@ -24,6 +24,26 @@ var errFormExtractorValueMissing = errors.New("missing value in the form")
// ValuesExtractor defines a function for extracting values (keys/tokens) from the given context. // ValuesExtractor defines a function for extracting values (keys/tokens) from the given context.
type ValuesExtractor func(c echo.Context) ([]string, error) type ValuesExtractor func(c echo.Context) ([]string, error)
// CreateExtractors creates ValuesExtractors from given lookups.
// Lookups is a string in the form of "<source>:<name>" or "<source>:<name>,<source>:<name>" that is used
// to extract key from the request.
// Possible values:
// - "header:<name>" or "header:<name>:<cut-prefix>"
// `<cut-prefix>` is argument value to cut/trim prefix of the extracted value. This is useful if header
// value has static prefix like `Authorization: <auth-scheme> <authorisation-parameters>` where part that we
// want to cut is `<auth-scheme> ` note the space at the end.
// In case of basic authentication `Authorization: Basic <credentials>` prefix we want to remove is `Basic `.
// - "query:<name>"
// - "param:<name>"
// - "form:<name>"
// - "cookie:<name>"
//
// Multiple sources example:
// - "header:Authorization,header:X-Api-Key"
func CreateExtractors(lookups string) ([]ValuesExtractor, error) {
return createExtractors(lookups, "")
}
func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) { func createExtractors(lookups string, authScheme string) ([]ValuesExtractor, error) {
if lookups == "" { if lookups == "" {
return nil, nil return nil, nil

@ -154,6 +154,8 @@ var (
// //
// See: https://jwt.io/introduction // See: https://jwt.io/introduction
// See `JWTConfig.TokenLookup` // See `JWTConfig.TokenLookup`
//
// Deprecated: Please use https://github.com/labstack/echo-jwt instead
func JWT(key interface{}) echo.MiddlewareFunc { func JWT(key interface{}) echo.MiddlewareFunc {
c := DefaultJWTConfig c := DefaultJWTConfig
c.SigningKey = key c.SigningKey = key
@ -162,6 +164,8 @@ func JWT(key interface{}) echo.MiddlewareFunc {
// JWTWithConfig returns a JWT auth middleware with config. // JWTWithConfig returns a JWT auth middleware with config.
// See: `JWT()`. // See: `JWT()`.
//
// Deprecated: Please use https://github.com/labstack/echo-jwt instead
func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc { func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
// Defaults // Defaults
if config.Skipper == nil { if config.Skipper == nil {
@ -262,7 +266,7 @@ func JWTWithConfig(config JWTConfig) echo.MiddlewareFunc {
} }
func (config *JWTConfig) defaultParseToken(auth string, c echo.Context) (interface{}, error) { func (config *JWTConfig) defaultParseToken(auth string, c echo.Context) (interface{}, error) {
token := new(jwt.Token) var token *jwt.Token
var err error var err error
// Issue #647, #656 // Issue #647, #656
if _, ok := config.Claims.(jwt.MapClaims); ok { if _, ok := config.Claims.(jwt.MapClaims); ok {

@ -35,6 +35,7 @@ type (
// - host // - host
// - method // - method
// - path // - path
// - route
// - protocol // - protocol
// - referer // - referer
// - user_agent // - user_agent
@ -47,6 +48,7 @@ type (
// - header:<NAME> // - header:<NAME>
// - query:<NAME> // - query:<NAME>
// - form:<NAME> // - form:<NAME>
// - custom (see CustomTagFunc field)
// //
// Example "${remote_ip} ${status}" // Example "${remote_ip} ${status}"
// //
@ -56,6 +58,11 @@ type (
// Optional. Default value DefaultLoggerConfig.CustomTimeFormat. // Optional. Default value DefaultLoggerConfig.CustomTimeFormat.
CustomTimeFormat string `yaml:"custom_time_format"` CustomTimeFormat string `yaml:"custom_time_format"`
// CustomTagFunc is function called for `${custom}` tag to output user implemented text by writing it to buf.
// Make sure that outputted text creates valid JSON string with other logged tags.
// Optional.
CustomTagFunc func(c echo.Context, buf *bytes.Buffer) (int, error)
// Output is a writer where logs in JSON format are written. // Output is a writer where logs in JSON format are written.
// Optional. Default value os.Stdout. // Optional. Default value os.Stdout.
Output io.Writer Output io.Writer
@ -126,6 +133,11 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) { if _, err = config.template.ExecuteFunc(buf, func(w io.Writer, tag string) (int, error) {
switch tag { switch tag {
case "custom":
if config.CustomTagFunc == nil {
return 0, nil
}
return config.CustomTagFunc(c, buf)
case "time_unix": case "time_unix":
return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10)) return buf.WriteString(strconv.FormatInt(time.Now().Unix(), 10))
case "time_unix_milli": case "time_unix_milli":
@ -162,6 +174,8 @@ func LoggerWithConfig(config LoggerConfig) echo.MiddlewareFunc {
p = "/" p = "/"
} }
return buf.WriteString(p) return buf.WriteString(p)
case "route":
return buf.WriteString(c.Path())
case "protocol": case "protocol":
return buf.WriteString(req.Proto) return buf.WriteString(req.Proto)
case "referer": case "referer":

@ -72,6 +72,11 @@ type (
Next(echo.Context) *ProxyTarget Next(echo.Context) *ProxyTarget
} }
// TargetProvider defines an interface that gives the opportunity for balancer to return custom errors when selecting target.
TargetProvider interface {
NextTarget(echo.Context) (*ProxyTarget, error)
}
commonBalancer struct { commonBalancer struct {
targets []*ProxyTarget targets []*ProxyTarget
mutex sync.RWMutex mutex sync.RWMutex
@ -223,6 +228,7 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
} }
} }
provider, isTargetProvider := config.Balancer.(TargetProvider)
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) {
@ -231,7 +237,16 @@ func ProxyWithConfig(config ProxyConfig) echo.MiddlewareFunc {
req := c.Request() req := c.Request()
res := c.Response() res := c.Response()
tgt := config.Balancer.Next(c)
var tgt *ProxyTarget
if isTargetProvider {
tgt, err = provider.NextTarget(c)
if err != nil {
return err
}
} else {
tgt = config.Balancer.Next(c)
}
c.Set(config.ContextKey, tgt) c.Set(config.ContextKey, tgt)
if err := rewriteURL(config.RegexRewrite, req); err != nil { if err := rewriteURL(config.RegexRewrite, req); err != nil {

@ -155,7 +155,7 @@ type (
RateLimiterMemoryStore struct { RateLimiterMemoryStore struct {
visitors map[string]*Visitor visitors map[string]*Visitor
mutex sync.Mutex mutex sync.Mutex
rate rate.Limit //for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. rate rate.Limit // for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
burst int burst int
expiresIn time.Duration expiresIn time.Duration
@ -170,15 +170,16 @@ type (
/* /*
NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with NewRateLimiterMemoryStore returns an instance of RateLimiterMemoryStore with
the provided rate (as req/s). The provided rate less than 1 will be treated as zero. the provided rate (as req/s).
for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. for more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst and ExpiresIn will be set to default values. Burst and ExpiresIn will be set to default values.
Note that if the provided rate is a float number and Burst is zero, Burst will be treated as the rounded down value of the rate.
Example (with 20 requests/sec): Example (with 20 requests/sec):
limiterStore := middleware.NewRateLimiterMemoryStore(20) limiterStore := middleware.NewRateLimiterMemoryStore(20)
*/ */
func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore) { func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore) {
return NewRateLimiterMemoryStoreWithConfig(RateLimiterMemoryStoreConfig{ return NewRateLimiterMemoryStoreWithConfig(RateLimiterMemoryStoreConfig{
@ -188,7 +189,7 @@ func NewRateLimiterMemoryStore(rate rate.Limit) (store *RateLimiterMemoryStore)
/* /*
NewRateLimiterMemoryStoreWithConfig returns an instance of RateLimiterMemoryStore NewRateLimiterMemoryStoreWithConfig returns an instance of RateLimiterMemoryStore
with the provided configuration. Rate must be provided. Burst will be set to the value of with the provided configuration. Rate must be provided. Burst will be set to the rounded down value of
the configured rate if not provided or set to 0. the configured rate if not provided or set to 0.
The build-in memory store is usually capable for modest loads. For higher loads other The build-in memory store is usually capable for modest loads. For higher loads other
@ -225,7 +226,7 @@ func NewRateLimiterMemoryStoreWithConfig(config RateLimiterMemoryStoreConfig) (s
// RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore // RateLimiterMemoryStoreConfig represents configuration for RateLimiterMemoryStore
type RateLimiterMemoryStoreConfig struct { type RateLimiterMemoryStoreConfig struct {
Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit. Rate rate.Limit // Rate of requests allowed to pass as req/s. For more info check out Limiter docs - https://pkg.go.dev/golang.org/x/time/rate#Limit.
Burst int // Burst additionally allows a number of requests to pass when rate limit is reached Burst int // Burst is maximum number of requests to pass at the same moment. It additionally allows a number of requests to pass when rate limit is reached.
ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up ExpiresIn time.Duration // ExpiresIn is the duration after that a rate limiter is cleaned up
} }

@ -10,10 +10,16 @@ import (
// Example for `fmt.Printf` // Example for `fmt.Printf`
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ // e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogStatus: true, // LogStatus: true,
// LogURI: true, // LogURI: true,
// LogError: true,
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { // LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// fmt.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status) // if v.Error == nil {
// fmt.Printf("REQUEST: uri: %v, status: %v\n", v.URI, v.Status)
// } else {
// fmt.Printf("REQUEST_ERROR: uri: %v, status: %v, err: %v\n", v.URI, v.Status, v.Error)
// }
// return nil // return nil
// }, // },
// })) // }))
@ -21,14 +27,23 @@ import (
// Example for Zerolog (https://github.com/rs/zerolog) // Example for Zerolog (https://github.com/rs/zerolog)
// logger := zerolog.New(os.Stdout) // logger := zerolog.New(os.Stdout)
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ // e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true, // LogURI: true,
// LogStatus: true, // LogStatus: true,
// LogError: true,
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { // LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// logger.Info(). // if v.Error == nil {
// Str("URI", v.URI). // logger.Info().
// Int("status", v.Status). // Str("URI", v.URI).
// Msg("request") // Int("status", v.Status).
// // Msg("request")
// } else {
// logger.Error().
// Err(v.Error).
// Str("URI", v.URI).
// Int("status", v.Status).
// Msg("request error")
// }
// return nil // return nil
// }, // },
// })) // }))
@ -36,29 +51,47 @@ import (
// Example for Zap (https://github.com/uber-go/zap) // Example for Zap (https://github.com/uber-go/zap)
// logger, _ := zap.NewProduction() // logger, _ := zap.NewProduction()
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ // e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true, // LogURI: true,
// LogStatus: true, // LogStatus: true,
// LogError: true,
// HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
// LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { // LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// logger.Info("request", // if v.Error == nil {
// zap.String("URI", v.URI), // logger.Info("request",
// zap.Int("status", v.Status), // zap.String("URI", v.URI),
// ) // zap.Int("status", v.Status),
// // )
// } else {
// logger.Error("request error",
// zap.String("URI", v.URI),
// zap.Int("status", v.Status),
// zap.Error(v.Error),
// )
// }
// return nil // return nil
// }, // },
// })) // }))
// //
// Example for Logrus (https://github.com/sirupsen/logrus) // Example for Logrus (https://github.com/sirupsen/logrus)
// log := logrus.New() // log := logrus.New()
// e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ // e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
// LogURI: true, // LogURI: true,
// LogStatus: true, // LogStatus: true,
// LogValuesFunc: func(c echo.Context, values middleware.RequestLoggerValues) error { // LogError: true,
// log.WithFields(logrus.Fields{ // HandleError: true, // forwards error to the global error handler, so it can decide appropriate status code
// "URI": values.URI, // LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
// "status": values.Status, // if v.Error == nil {
// }).Info("request") // log.WithFields(logrus.Fields{
// // "URI": v.URI,
// "status": v.Status,
// }).Info("request")
// } else {
// log.WithFields(logrus.Fields{
// "URI": v.URI,
// "status": v.Status,
// "error": v.Error,
// }).Error("request error")
// }
// return nil // return nil
// }, // },
// })) // }))
@ -74,6 +107,13 @@ type RequestLoggerConfig struct {
// Mandatory. // Mandatory.
LogValuesFunc func(c echo.Context, v RequestLoggerValues) error LogValuesFunc func(c echo.Context, v RequestLoggerValues) error
// HandleError instructs logger to call global error handler when next middleware/handler returns an error.
// This is useful when you have custom error handler that can decide to use different status codes.
//
// A side-effect of calling global error handler is that now Response has been committed and sent to the client
// and middlewares up in chain can not change Response status code or response body.
HandleError bool
// LogLatency instructs logger to record duration it took to execute rest of the handler chain (next(c) call). // LogLatency instructs logger to record duration it took to execute rest of the handler chain (next(c) call).
LogLatency bool LogLatency bool
// LogProtocol instructs logger to extract request protocol (i.e. `HTTP/1.1` or `HTTP/2`) // LogProtocol instructs logger to extract request protocol (i.e. `HTTP/1.1` or `HTTP/2`)
@ -217,6 +257,9 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
config.BeforeNextFunc(c) config.BeforeNextFunc(c)
} }
err := next(c) err := next(c)
if config.HandleError {
c.Error(err)
}
v := RequestLoggerValues{ v := RequestLoggerValues{
StartTime: start, StartTime: start,
@ -264,7 +307,9 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
} }
if config.LogStatus { if config.LogStatus {
v.Status = res.Status v.Status = res.Status
if err != nil { if err != nil && !config.HandleError {
// this block should not be executed in case of HandleError=true as the global error handler will decide
// the status code. In that case status code could be different from what err contains.
var httpErr *echo.HTTPError var httpErr *echo.HTTPError
if errors.As(err, &httpErr) { if errors.As(err, &httpErr) {
v.Status = httpErr.Code v.Status = httpErr.Code
@ -310,6 +355,9 @@ func (config RequestLoggerConfig) ToMiddleware() (echo.MiddlewareFunc, error) {
return errOnLog return errOnLog
} }
// in case of HandleError=true we are returning the error that we already have handled with global error handler
// this is deliberate as this error could be useful for upstream middlewares and default global error handler
// will ignore that error when it bubbles up in middleware chain.
return err return err
} }
}, nil }, nil

@ -33,7 +33,7 @@ func AddTrailingSlash() echo.MiddlewareFunc {
return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig) return AddTrailingSlashWithConfig(DefaultTrailingSlashConfig)
} }
// AddTrailingSlashWithConfig returns a AddTrailingSlash middleware with config. // AddTrailingSlashWithConfig returns an AddTrailingSlash middleware with config.
// See `AddTrailingSlash()`. // See `AddTrailingSlash()`.
func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc { func AddTrailingSlashWithConfig(config TrailingSlashConfig) echo.MiddlewareFunc {
// Defaults // Defaults

@ -2,6 +2,7 @@ package echo
import ( import (
"bytes" "bytes"
"fmt"
"net/http" "net/http"
) )
@ -141,6 +142,51 @@ func NewRouter(e *Echo) *Router {
} }
} }
// Routes returns the registered routes.
func (r *Router) Routes() []*Route {
routes := make([]*Route, 0, len(r.routes))
for _, v := range r.routes {
routes = append(routes, v)
}
return routes
}
// Reverse generates an URL from route name and provided parameters.
func (r *Router) Reverse(name string, params ...interface{}) string {
uri := new(bytes.Buffer)
ln := len(params)
n := 0
for _, route := range r.routes {
if route.Name == name {
for i, l := 0, len(route.Path); i < l; i++ {
if (route.Path[i] == ':' || route.Path[i] == '*') && n < ln {
for ; i < l && route.Path[i] != '/'; i++ {
}
uri.WriteString(fmt.Sprintf("%v", params[n]))
n++
}
if i < l {
uri.WriteByte(route.Path[i])
}
}
break
}
}
return uri.String()
}
func (r *Router) add(method, path, name string, h HandlerFunc) *Route {
r.Add(method, path, h)
route := &Route{
Method: method,
Path: path,
Name: name,
}
r.routes[method+path] = route
return route
}
// Add registers a new route for method and path with matching handler. // Add registers a new route for method and path with matching handler.
func (r *Router) Add(method, path string, h HandlerFunc) { func (r *Router) Add(method, path string, h HandlerFunc) {
// Validate path // Validate path

@ -22,7 +22,7 @@ indent_size = 4
[*.md] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
[*.{md,py,sh,yml,yaml,js,ts,vue,css}] [*.{md,py,sh,yml,yaml,cjs,js,ts,vue,css}]
max_line_length = 105 max_line_length = 105
[*.{yml,yaml,toml}] [*.{yml,yaml,toml}]
@ -36,7 +36,7 @@ insert_final_newline = ignore
max_line_length = 140 max_line_length = 140
indent_size = 2 indent_size = 2
[*.{js,ts,vue,css}] [*.{cjs,js,ts,vue,css}]
indent_size = 2 indent_size = 2
[Makefile] [Makefile]

@ -12,7 +12,7 @@
</a> </a>
<a href="https://github.com/lrstanley/girc/actions?query=workflow%3Atest+event%3Apush"> <a href="https://github.com/lrstanley/girc/actions?query=workflow%3Atest+event%3Apush">
<img title="GitHub Workflow Status (test @ master)" src="https://img.shields.io/github/workflow/status/lrstanley/girc/test/master?label=test&style=flat-square&event=push"> <img title="GitHub Workflow Status (test @ master)" src="https://img.shields.io/github/actions/workflow/status/lrstanley/girc/test.yml?branch=master&label=test&style=flat-square">
</a> </a>
<a href="https://codecov.io/gh/lrstanley/girc"> <a href="https://codecov.io/gh/lrstanley/girc">

@ -1,17 +0,0 @@
language: go
go:
- 1.3.x
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- "1.10.x"
- "1.11.x"
- "1.12.x"
- "1.13.x"
- "1.14.x"
- "1.15.x"
- "1.16.x"
- tip

@ -1,5 +1,50 @@
## Changelog ## Changelog
### [1.8.7](https://github.com/magiconair/properties/tree/v1.8.7) - 08 Dec 2022
* [PR #65](https://github.com/magiconair/properties/pull/65): Speedup Merge
Thanks to [@AdityaVallabh](https://github.com/AdityaVallabh) for the patch.
* [PR #66](https://github.com/magiconair/properties/pull/66): use github actions
### [1.8.6](https://github.com/magiconair/properties/tree/v1.8.6) - 23 Feb 2022
* [PR #57](https://github.com/magiconair/properties/pull/57):Fix "unreachable code" lint error
Thanks to [@ellie](https://github.com/ellie) for the patch.
* [PR #63](https://github.com/magiconair/properties/pull/63): Make TestMustGetParsedDuration backwards compatible
This patch ensures that the `TestMustGetParsedDuration` still works with `go1.3` to make the
author happy until it affects real users.
Thanks to [@maage](https://github.com/maage) for the patch.
### [1.8.5](https://github.com/magiconair/properties/tree/v1.8.5) - 24 Mar 2021
* [PR #55](https://github.com/magiconair/properties/pull/55): Fix: Encoding Bug in Comments
When reading comments \ are loaded correctly, but when writing they are then
replaced by \\. This leads to wrong comments when writing and reading multiple times.
Thanks to [@doxsch](https://github.com/doxsch) for the patch.
### [1.8.4](https://github.com/magiconair/properties/tree/v1.8.4) - 23 Sep 2020
* [PR #50](https://github.com/magiconair/properties/pull/50): enhance error message for circular references
Thanks to [@sriv](https://github.com/sriv) for the patch.
### [1.8.3](https://github.com/magiconair/properties/tree/v1.8.3) - 14 Sep 2020
* [PR #49](https://github.com/magiconair/properties/pull/49): Include the key in error message causing the circular reference
The change is include the key in the error message which is causing the circular
reference when parsing/loading the properties files.
Thanks to [@haroon-sheikh](https://github.com/haroon-sheikh) for the patch.
### [1.8.2](https://github.com/magiconair/properties/tree/v1.8.2) - 25 Aug 2020 ### [1.8.2](https://github.com/magiconair/properties/tree/v1.8.2) - 25 Aug 2020
* [PR #36](https://github.com/magiconair/properties/pull/36): Escape backslash on write * [PR #36](https://github.com/magiconair/properties/pull/36): Escape backslash on write

@ -1,4 +1,4 @@
// Copyright 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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.
@ -48,49 +48,49 @@ import (
// //
// Examples: // Examples:
// //
// // Field is ignored. // // Field is ignored.
// Field int `properties:"-"` // Field int `properties:"-"`
// //
// // Field is assigned value of 'Field'. // // Field is assigned value of 'Field'.
// Field int // Field int
// //
// // Field is assigned value of 'myName'. // // Field is assigned value of 'myName'.
// Field int `properties:"myName"` // Field int `properties:"myName"`
// //
// // Field is assigned value of key 'myName' and has a default // // Field is assigned value of key 'myName' and has a default
// // value 15 if the key does not exist. // // value 15 if the key does not exist.
// Field int `properties:"myName,default=15"` // Field int `properties:"myName,default=15"`
// //
// // Field is assigned value of key 'Field' and has a default // // Field is assigned value of key 'Field' and has a default
// // value 15 if the key does not exist. // // value 15 if the key does not exist.
// Field int `properties:",default=15"` // Field int `properties:",default=15"`
// //
// // Field is assigned value of key 'date' and the date // // Field is assigned value of key 'date' and the date
// // is in format 2006-01-02 // // is in format 2006-01-02
// Field time.Time `properties:"date,layout=2006-01-02"` // Field time.Time `properties:"date,layout=2006-01-02"`
// //
// // Field is assigned the non-empty and whitespace trimmed // // Field is assigned the non-empty and whitespace trimmed
// // values of key 'Field' split by commas. // // values of key 'Field' split by commas.
// Field []string // Field []string
// //
// // Field is assigned the non-empty and whitespace trimmed // // Field is assigned the non-empty and whitespace trimmed
// // values of key 'Field' split by commas and has a default // // values of key 'Field' split by commas and has a default
// // value ["a", "b", "c"] if the key does not exist. // // value ["a", "b", "c"] if the key does not exist.
// Field []string `properties:",default=a;b;c"` // Field []string `properties:",default=a;b;c"`
// //
// // Field is decoded recursively with "Field." as key prefix. // // Field is decoded recursively with "Field." as key prefix.
// Field SomeStruct // Field SomeStruct
// //
// // Field is decoded recursively with "myName." as key prefix. // // Field is decoded recursively with "myName." as key prefix.
// Field SomeStruct `properties:"myName"` // Field SomeStruct `properties:"myName"`
// //
// // Field is decoded recursively with "Field." as key prefix // // Field is decoded recursively with "Field." as key prefix
// // and the next dotted element of the key as map key. // // and the next dotted element of the key as map key.
// Field map[string]string // Field map[string]string
// //
// // Field is decoded recursively with "myName." as key prefix // // Field is decoded recursively with "myName." as key prefix
// // and the next dotted element of the key as map key. // // and the next dotted element of the key as map key.
// Field map[string]string `properties:"myName"` // Field map[string]string `properties:"myName"`
func (p *Properties) Decode(x interface{}) error { func (p *Properties) Decode(x interface{}) error {
t, v := reflect.TypeOf(x), reflect.ValueOf(x) t, v := reflect.TypeOf(x), reflect.ValueOf(x)
if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct { if t.Kind() != reflect.Ptr || v.Elem().Type().Kind() != reflect.Struct {

@ -1,4 +1,4 @@
// Copyright 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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.
@ -13,7 +13,7 @@
// //
// To load a single properties file use MustLoadFile(): // To load a single properties file use MustLoadFile():
// //
// p := properties.MustLoadFile(filename, properties.UTF8) // p := properties.MustLoadFile(filename, properties.UTF8)
// //
// To load multiple properties files use MustLoadFiles() // To load multiple properties files use MustLoadFiles()
// which loads the files in the given order and merges the // which loads the files in the given order and merges the
@ -23,25 +23,25 @@
// Filenames can contain environment variables which are expanded // Filenames can contain environment variables which are expanded
// before loading. // before loading.
// //
// f1 := "/etc/myapp/myapp.conf" // f1 := "/etc/myapp/myapp.conf"
// f2 := "/home/${USER}/myapp.conf" // f2 := "/home/${USER}/myapp.conf"
// p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true) // p := MustLoadFiles([]string{f1, f2}, properties.UTF8, true)
// //
// All of the different key/value delimiters ' ', ':' and '=' are // All of the different key/value delimiters ' ', ':' and '=' are
// supported as well as the comment characters '!' and '#' and // supported as well as the comment characters '!' and '#' and
// multi-line values. // multi-line values.
// //
// ! this is a comment // ! this is a comment
// # and so is this // # and so is this
// //
// # the following expressions are equal // # the following expressions are equal
// key value // key value
// key=value // key=value
// key:value // key:value
// key = value // key = value
// key : value // key : value
// key = val\ // key = val\
// ue // ue
// //
// Properties stores all comments preceding a key and provides // Properties stores all comments preceding a key and provides
// GetComments() and SetComments() methods to retrieve and // GetComments() and SetComments() methods to retrieve and
@ -55,62 +55,62 @@
// and malformed expressions are not allowed and cause an // and malformed expressions are not allowed and cause an
// error. Expansion of environment variables is supported. // error. Expansion of environment variables is supported.
// //
// # standard property // # standard property
// key = value // key = value
// //
// # property expansion: key2 = value // # property expansion: key2 = value
// key2 = ${key} // key2 = ${key}
// //
// # recursive expansion: key3 = value // # recursive expansion: key3 = value
// key3 = ${key2} // key3 = ${key2}
// //
// # circular reference (error) // # circular reference (error)
// key = ${key} // key = ${key}
// //
// # malformed expression (error) // # malformed expression (error)
// key = ${ke // key = ${ke
// //
// # refers to the users' home dir // # refers to the users' home dir
// home = ${HOME} // home = ${HOME}
// //
// # local key takes precedence over env var: u = foo // # local key takes precedence over env var: u = foo
// USER = foo // USER = foo
// u = ${USER} // u = ${USER}
// //
// The default property expansion format is ${key} but can be // The default property expansion format is ${key} but can be
// changed by setting different pre- and postfix values on the // changed by setting different pre- and postfix values on the
// Properties object. // Properties object.
// //
// p := properties.NewProperties() // p := properties.NewProperties()
// p.Prefix = "#[" // p.Prefix = "#["
// p.Postfix = "]#" // p.Postfix = "]#"
// //
// Properties provides convenience functions for getting typed // Properties provides convenience functions for getting typed
// values with default values if the key does not exist or the // values with default values if the key does not exist or the
// type conversion failed. // type conversion failed.
// //
// # Returns true if the value is either "1", "on", "yes" or "true" // # Returns true if the value is either "1", "on", "yes" or "true"
// # Returns false for every other value and the default value if // # Returns false for every other value and the default value if
// # the key does not exist. // # the key does not exist.
// v = p.GetBool("key", false) // v = p.GetBool("key", false)
// //
// # Returns the value if the key exists and the format conversion // # Returns the value if the key exists and the format conversion
// # was successful. Otherwise, the default value is returned. // # was successful. Otherwise, the default value is returned.
// v = p.GetInt64("key", 999) // v = p.GetInt64("key", 999)
// v = p.GetUint64("key", 999) // v = p.GetUint64("key", 999)
// v = p.GetFloat64("key", 123.0) // v = p.GetFloat64("key", 123.0)
// v = p.GetString("key", "def") // v = p.GetString("key", "def")
// v = p.GetDuration("key", 999) // v = p.GetDuration("key", 999)
// //
// As an alternative 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
// v = flag.Int("key", 999, "help message") // v = flag.Int("key", 999, "help message")
// flag.Parse() // flag.Parse()
// //
// # Merge p into the flag set // # Merge p into the flag set
// p.MustFlag(flag.CommandLine) // p.MustFlag(flag.CommandLine)
// //
// Properties provides several MustXXX() convenience functions // Properties provides several MustXXX() convenience functions
// which will terminate the app if an error occurs. The behavior // which will terminate the app if an error occurs. The behavior
@ -119,30 +119,30 @@
// of logging the error set a different ErrorHandler before // of logging the error set a different ErrorHandler before
// you use the Properties package. // you use the Properties package.
// //
// properties.ErrorHandler = properties.PanicHandler // properties.ErrorHandler = properties.PanicHandler
// //
// # Will panic instead of logging an error // # Will panic instead of logging an error
// p := properties.MustLoadFile("config.properties") // p := properties.MustLoadFile("config.properties")
// //
// You can also provide your own ErrorHandler function. The only requirement // You can also provide your own ErrorHandler function. The only requirement
// is that the error handler function must exit after handling the error. // is that the error handler function must exit after handling the error.
// //
// properties.ErrorHandler = func(err error) { // properties.ErrorHandler = func(err error) {
// fmt.Println(err) // fmt.Println(err)
// os.Exit(1) // os.Exit(1)
// } // }
// //
// # Will write to stdout and then exit // # Will write to stdout and then exit
// p := properties.MustLoadFile("config.properties") // p := properties.MustLoadFile("config.properties")
// //
// Properties can also be loaded into a struct via the `Decode` // Properties can also be loaded into a struct via the `Decode`
// method, e.g. // method, e.g.
// //
// type S struct { // type S struct {
// A string `properties:"a,default=foo"` // A string `properties:"a,default=foo"`
// D time.Duration `properties:"timeout,default=5s"` // D time.Duration `properties:"timeout,default=5s"`
// E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"` // E time.Time `properties:"expires,layout=2006-01-02,default=2015-01-01"`
// } // }
// //
// See `Decode()` method for the full documentation. // See `Decode()` method for the full documentation.
// //
@ -152,5 +152,4 @@
// http://en.wikipedia.org/wiki/.properties // http://en.wikipedia.org/wiki/.properties
// //
// http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29 // http://docs.oracle.com/javase/7/docs/api/java/util/Properties.html#load%28java.io.Reader%29
//
package properties package properties

@ -1,4 +1,4 @@
// Copyright 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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.
@ -10,8 +10,9 @@ import "flag"
// the respective key for flag.Flag.Name. // the respective key for flag.Flag.Name.
// //
// It's use is recommended with command line arguments as in: // It's use is recommended with command line arguments as in:
// flag.Parse() //
// p.MustFlag(flag.CommandLine) // flag.Parse()
// p.MustFlag(flag.CommandLine)
func (p *Properties) MustFlag(dst *flag.FlagSet) { func (p *Properties) MustFlag(dst *flag.FlagSet) {
m := make(map[string]*flag.Flag) m := make(map[string]*flag.Flag)
dst.VisitAll(func(f *flag.Flag) { dst.VisitAll(func(f *flag.Flag) {

@ -1,4 +1,4 @@
// Copyright 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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.
@ -700,22 +700,17 @@ func (p *Properties) Delete(key string) {
// Merge merges properties, comments and keys from other *Properties into p // Merge merges properties, comments and keys from other *Properties into p
func (p *Properties) Merge(other *Properties) { func (p *Properties) Merge(other *Properties) {
for _, k := range other.k {
if _, ok := p.m[k]; !ok {
p.k = append(p.k, k)
}
}
for k, v := range other.m { for k, v := range other.m {
p.m[k] = v p.m[k] = v
} }
for k, v := range other.c { for k, v := range other.c {
p.c[k] = v p.c[k] = v
} }
outer:
for _, otherKey := range other.k {
for _, key := range p.k {
if otherKey == key {
continue outer
}
}
p.k = append(p.k, otherKey)
}
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

@ -1,4 +1,4 @@
// Copyright 2018 Frank Schroeder. All rights reserved. // Copyright 2013-2022 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,7 +16,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
- id: tomljson - id: tomljson
@ -31,7 +35,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
- id: jsontoml - id: jsontoml
@ -46,7 +54,11 @@ builds:
mod_timestamp: '{{ .CommitTimestamp }}' mod_timestamp: '{{ .CommitTimestamp }}'
targets: targets:
- linux_amd64 - linux_amd64
- linux_arm64
- linux_arm
- windows_amd64 - windows_amd64
- windows_arm64
- windows_arm
- darwin_amd64 - darwin_amd64
- darwin_arm64 - darwin_arm64
universal_binaries: universal_binaries:

@ -140,6 +140,17 @@ fmt.Println(string(b))
[marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal [marshal]: https://pkg.go.dev/github.com/pelletier/go-toml/v2#Marshal
## Unstable API
This API does not yet follow the backward compatibility guarantees of this
library. They provide early access to features that may have rough edges or an
API subject to change.
### Parser
Parser is the unstable API that allows iterative parsing of a TOML document at
the AST level. See https://pkg.go.dev/github.com/pelletier/go-toml/v2/unstable.
## Benchmarks ## Benchmarks
Execution time speedup compared to other Go TOML libraries: Execution time speedup compared to other Go TOML libraries:

@ -5,6 +5,8 @@ import (
"math" "math"
"strconv" "strconv"
"time" "time"
"github.com/pelletier/go-toml/v2/unstable"
) )
func parseInteger(b []byte) (int64, error) { func parseInteger(b []byte) (int64, error) {
@ -32,7 +34,7 @@ func parseLocalDate(b []byte) (LocalDate, error) {
var date LocalDate var date LocalDate
if len(b) != 10 || b[4] != '-' || b[7] != '-' { if len(b) != 10 || b[4] != '-' || b[7] != '-' {
return date, newDecodeError(b, "dates are expected to have the format YYYY-MM-DD") return date, unstable.NewParserError(b, "dates are expected to have the format YYYY-MM-DD")
} }
var err error var err error
@ -53,7 +55,7 @@ func parseLocalDate(b []byte) (LocalDate, error) {
} }
if !isValidDate(date.Year, date.Month, date.Day) { if !isValidDate(date.Year, date.Month, date.Day) {
return LocalDate{}, newDecodeError(b, "impossible date") return LocalDate{}, unstable.NewParserError(b, "impossible date")
} }
return date, nil return date, nil
@ -64,7 +66,7 @@ func parseDecimalDigits(b []byte) (int, error) {
for i, c := range b { for i, c := range b {
if c < '0' || c > '9' { if c < '0' || c > '9' {
return 0, newDecodeError(b[i:i+1], "expected digit (0-9)") return 0, unstable.NewParserError(b[i:i+1], "expected digit (0-9)")
} }
v *= 10 v *= 10
v += int(c - '0') v += int(c - '0')
@ -97,7 +99,7 @@ func parseDateTime(b []byte) (time.Time, error) {
} else { } else {
const dateTimeByteLen = 6 const dateTimeByteLen = 6
if len(b) != dateTimeByteLen { if len(b) != dateTimeByteLen {
return time.Time{}, newDecodeError(b, "invalid date-time timezone") return time.Time{}, unstable.NewParserError(b, "invalid date-time timezone")
} }
var direction int var direction int
switch b[0] { switch b[0] {
@ -106,11 +108,11 @@ func parseDateTime(b []byte) (time.Time, error) {
case '+': case '+':
direction = +1 direction = +1
default: default:
return time.Time{}, newDecodeError(b[:1], "invalid timezone offset character") return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset character")
} }
if b[3] != ':' { if b[3] != ':' {
return time.Time{}, newDecodeError(b[3:4], "expected a : separator") return time.Time{}, unstable.NewParserError(b[3:4], "expected a : separator")
} }
hours, err := parseDecimalDigits(b[1:3]) hours, err := parseDecimalDigits(b[1:3])
@ -118,7 +120,7 @@ func parseDateTime(b []byte) (time.Time, error) {
return time.Time{}, err return time.Time{}, err
} }
if hours > 23 { if hours > 23 {
return time.Time{}, newDecodeError(b[:1], "invalid timezone offset hours") return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset hours")
} }
minutes, err := parseDecimalDigits(b[4:6]) minutes, err := parseDecimalDigits(b[4:6])
@ -126,7 +128,7 @@ func parseDateTime(b []byte) (time.Time, error) {
return time.Time{}, err return time.Time{}, err
} }
if minutes > 59 { if minutes > 59 {
return time.Time{}, newDecodeError(b[:1], "invalid timezone offset minutes") return time.Time{}, unstable.NewParserError(b[:1], "invalid timezone offset minutes")
} }
seconds := direction * (hours*3600 + minutes*60) seconds := direction * (hours*3600 + minutes*60)
@ -139,7 +141,7 @@ func parseDateTime(b []byte) (time.Time, error) {
} }
if len(b) > 0 { if len(b) > 0 {
return time.Time{}, newDecodeError(b, "extra bytes at the end of the timezone") return time.Time{}, unstable.NewParserError(b, "extra bytes at the end of the timezone")
} }
t := time.Date( t := time.Date(
@ -160,7 +162,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
const localDateTimeByteMinLen = 11 const localDateTimeByteMinLen = 11
if len(b) < localDateTimeByteMinLen { if len(b) < localDateTimeByteMinLen {
return dt, nil, newDecodeError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]") return dt, nil, unstable.NewParserError(b, "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]")
} }
date, err := parseLocalDate(b[:10]) date, err := parseLocalDate(b[:10])
@ -171,7 +173,7 @@ func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
sep := b[10] sep := b[10]
if sep != 'T' && sep != ' ' && sep != 't' { if sep != 'T' && sep != ' ' && sep != 't' {
return dt, nil, newDecodeError(b[10:11], "datetime separator is expected to be T or a space") return dt, nil, unstable.NewParserError(b[10:11], "datetime separator is expected to be T or a space")
} }
t, rest, err := parseLocalTime(b[11:]) t, rest, err := parseLocalTime(b[11:])
@ -195,7 +197,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
// check if b matches to have expected format HH:MM:SS[.NNNNNN] // check if b matches to have expected format HH:MM:SS[.NNNNNN]
const localTimeByteLen = 8 const localTimeByteLen = 8
if len(b) < localTimeByteLen { if len(b) < localTimeByteLen {
return t, nil, newDecodeError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]") return t, nil, unstable.NewParserError(b, "times are expected to have the format HH:MM:SS[.NNNNNN]")
} }
var err error var err error
@ -206,10 +208,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
} }
if t.Hour > 23 { if t.Hour > 23 {
return t, nil, newDecodeError(b[0:2], "hour cannot be greater 23") return t, nil, unstable.NewParserError(b[0:2], "hour cannot be greater 23")
} }
if b[2] != ':' { if b[2] != ':' {
return t, nil, newDecodeError(b[2:3], "expecting colon between hours and minutes") return t, nil, unstable.NewParserError(b[2:3], "expecting colon between hours and minutes")
} }
t.Minute, err = parseDecimalDigits(b[3:5]) t.Minute, err = parseDecimalDigits(b[3:5])
@ -217,10 +219,10 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
return t, nil, err return t, nil, err
} }
if t.Minute > 59 { if t.Minute > 59 {
return t, nil, newDecodeError(b[3:5], "minutes cannot be greater 59") return t, nil, unstable.NewParserError(b[3:5], "minutes cannot be greater 59")
} }
if b[5] != ':' { if b[5] != ':' {
return t, nil, newDecodeError(b[5:6], "expecting colon between minutes and seconds") return t, nil, unstable.NewParserError(b[5:6], "expecting colon between minutes and seconds")
} }
t.Second, err = parseDecimalDigits(b[6:8]) t.Second, err = parseDecimalDigits(b[6:8])
@ -229,7 +231,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
} }
if t.Second > 60 { if t.Second > 60 {
return t, nil, newDecodeError(b[6:8], "seconds cannot be greater 60") return t, nil, unstable.NewParserError(b[6:8], "seconds cannot be greater 60")
} }
b = b[8:] b = b[8:]
@ -242,7 +244,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
for i, c := range b[1:] { for i, c := range b[1:] {
if !isDigit(c) { if !isDigit(c) {
if i == 0 { if i == 0 {
return t, nil, newDecodeError(b[0:1], "need at least one digit after fraction point") return t, nil, unstable.NewParserError(b[0:1], "need at least one digit after fraction point")
} }
break break
} }
@ -266,7 +268,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
} }
if precision == 0 { if precision == 0 {
return t, nil, newDecodeError(b[:1], "nanoseconds need at least one digit") return t, nil, unstable.NewParserError(b[:1], "nanoseconds need at least one digit")
} }
t.Nanosecond = frac * nspow[precision] t.Nanosecond = frac * nspow[precision]
@ -289,24 +291,24 @@ func parseFloat(b []byte) (float64, error) {
} }
if cleaned[0] == '.' { if cleaned[0] == '.' {
return 0, newDecodeError(b, "float cannot start with a dot") return 0, unstable.NewParserError(b, "float cannot start with a dot")
} }
if cleaned[len(cleaned)-1] == '.' { if cleaned[len(cleaned)-1] == '.' {
return 0, newDecodeError(b, "float cannot end with a dot") return 0, unstable.NewParserError(b, "float cannot end with a dot")
} }
dotAlreadySeen := false dotAlreadySeen := false
for i, c := range cleaned { for i, c := range cleaned {
if c == '.' { if c == '.' {
if dotAlreadySeen { if dotAlreadySeen {
return 0, newDecodeError(b[i:i+1], "float can have at most one decimal point") return 0, unstable.NewParserError(b[i:i+1], "float can have at most one decimal point")
} }
if !isDigit(cleaned[i-1]) { if !isDigit(cleaned[i-1]) {
return 0, newDecodeError(b[i-1:i+1], "float decimal point must be preceded by a digit") return 0, unstable.NewParserError(b[i-1:i+1], "float decimal point must be preceded by a digit")
} }
if !isDigit(cleaned[i+1]) { if !isDigit(cleaned[i+1]) {
return 0, newDecodeError(b[i:i+2], "float decimal point must be followed by a digit") return 0, unstable.NewParserError(b[i:i+2], "float decimal point must be followed by a digit")
} }
dotAlreadySeen = true dotAlreadySeen = true
} }
@ -317,12 +319,12 @@ func parseFloat(b []byte) (float64, error) {
start = 1 start = 1
} }
if cleaned[start] == '0' && isDigit(cleaned[start+1]) { if cleaned[start] == '0' && isDigit(cleaned[start+1]) {
return 0, newDecodeError(b, "float integer part cannot have leading zeroes") return 0, unstable.NewParserError(b, "float integer part cannot have leading zeroes")
} }
f, err := strconv.ParseFloat(string(cleaned), 64) f, err := strconv.ParseFloat(string(cleaned), 64)
if err != nil { if err != nil {
return 0, newDecodeError(b, "unable to parse float: %w", err) return 0, unstable.NewParserError(b, "unable to parse float: %w", err)
} }
return f, nil return f, nil
@ -336,7 +338,7 @@ func parseIntHex(b []byte) (int64, error) {
i, err := strconv.ParseInt(string(cleaned), 16, 64) i, err := strconv.ParseInt(string(cleaned), 16, 64)
if err != nil { if err != nil {
return 0, newDecodeError(b, "couldn't parse hexadecimal number: %w", err) return 0, unstable.NewParserError(b, "couldn't parse hexadecimal number: %w", err)
} }
return i, nil return i, nil
@ -350,7 +352,7 @@ func parseIntOct(b []byte) (int64, error) {
i, err := strconv.ParseInt(string(cleaned), 8, 64) i, err := strconv.ParseInt(string(cleaned), 8, 64)
if err != nil { if err != nil {
return 0, newDecodeError(b, "couldn't parse octal number: %w", err) return 0, unstable.NewParserError(b, "couldn't parse octal number: %w", err)
} }
return i, nil return i, nil
@ -364,7 +366,7 @@ func parseIntBin(b []byte) (int64, error) {
i, err := strconv.ParseInt(string(cleaned), 2, 64) i, err := strconv.ParseInt(string(cleaned), 2, 64)
if err != nil { if err != nil {
return 0, newDecodeError(b, "couldn't parse binary number: %w", err) return 0, unstable.NewParserError(b, "couldn't parse binary number: %w", err)
} }
return i, nil return i, nil
@ -387,12 +389,12 @@ func parseIntDec(b []byte) (int64, error) {
} }
if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' { if len(cleaned) > startIdx+1 && cleaned[startIdx] == '0' {
return 0, newDecodeError(b, "leading zero not allowed on decimal number") return 0, unstable.NewParserError(b, "leading zero not allowed on decimal number")
} }
i, err := strconv.ParseInt(string(cleaned), 10, 64) i, err := strconv.ParseInt(string(cleaned), 10, 64)
if err != nil { if err != nil {
return 0, newDecodeError(b, "couldn't parse decimal number: %w", err) return 0, unstable.NewParserError(b, "couldn't parse decimal number: %w", err)
} }
return i, nil return i, nil
@ -409,11 +411,11 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {
} }
if b[start] == '_' { if b[start] == '_' {
return nil, newDecodeError(b[start:start+1], "number cannot start with underscore") return nil, unstable.NewParserError(b[start:start+1], "number cannot start with underscore")
} }
if b[len(b)-1] == '_' { if b[len(b)-1] == '_' {
return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")
} }
// fast path // fast path
@ -435,7 +437,7 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {
c := b[i] c := b[i]
if c == '_' { if c == '_' {
if !before { if !before {
return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")
} }
before = false before = false
} else { } else {
@ -449,11 +451,11 @@ func checkAndRemoveUnderscoresIntegers(b []byte) ([]byte, error) {
func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) { func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
if b[0] == '_' { if b[0] == '_' {
return nil, newDecodeError(b[0:1], "number cannot start with underscore") return nil, unstable.NewParserError(b[0:1], "number cannot start with underscore")
} }
if b[len(b)-1] == '_' { if b[len(b)-1] == '_' {
return nil, newDecodeError(b[len(b)-1:], "number cannot end with underscore") return nil, unstable.NewParserError(b[len(b)-1:], "number cannot end with underscore")
} }
// fast path // fast path
@ -476,10 +478,10 @@ func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
switch c { switch c {
case '_': case '_':
if !before { if !before {
return nil, newDecodeError(b[i-1:i+1], "number must have at least one digit between underscores") return nil, unstable.NewParserError(b[i-1:i+1], "number must have at least one digit between underscores")
} }
if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') { if i < len(b)-1 && (b[i+1] == 'e' || b[i+1] == 'E') {
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore before exponent") return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore before exponent")
} }
before = false before = false
case '+', '-': case '+', '-':
@ -488,15 +490,15 @@ func checkAndRemoveUnderscoresFloats(b []byte) ([]byte, error) {
before = false before = false
case 'e', 'E': case 'e', 'E':
if i < len(b)-1 && b[i+1] == '_' { if i < len(b)-1 && b[i+1] == '_' {
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after exponent") return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after exponent")
} }
cleaned = append(cleaned, c) cleaned = append(cleaned, c)
case '.': case '.':
if i < len(b)-1 && b[i+1] == '_' { if i < len(b)-1 && b[i+1] == '_' {
return nil, newDecodeError(b[i+1:i+2], "cannot have underscore after decimal point") return nil, unstable.NewParserError(b[i+1:i+2], "cannot have underscore after decimal point")
} }
if i > 0 && b[i-1] == '_' { if i > 0 && b[i-1] == '_' {
return nil, newDecodeError(b[i-1:i], "cannot have underscore before decimal point") return nil, unstable.NewParserError(b[i-1:i], "cannot have underscore before decimal point")
} }
cleaned = append(cleaned, c) cleaned = append(cleaned, c)
default: default:
@ -542,3 +544,7 @@ func daysIn(m int, year int) int {
func isLeap(year int) bool { func isLeap(year int) bool {
return year%4 == 0 && (year%100 != 0 || year%400 == 0) return year%4 == 0 && (year%100 != 0 || year%400 == 0)
} }
func isDigit(r byte) bool {
return r >= '0' && r <= '9'
}

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
"github.com/pelletier/go-toml/v2/unstable"
) )
// DecodeError represents an error encountered during the parsing or decoding // DecodeError represents an error encountered during the parsing or decoding
@ -55,25 +56,6 @@ func (s *StrictMissingError) String() string {
type Key []string type Key []string
// internal version of DecodeError that is used as the base to create a
// DecodeError with full context.
type decodeError struct {
highlight []byte
message string
key Key // optional
}
func (de *decodeError) Error() string {
return de.message
}
func newDecodeError(highlight []byte, format string, args ...interface{}) error {
return &decodeError{
highlight: highlight,
message: fmt.Errorf(format, args...).Error(),
}
}
// Error returns the error message contained in the DecodeError. // Error returns the error message contained in the DecodeError.
func (e *DecodeError) Error() string { func (e *DecodeError) Error() string {
return "toml: " + e.message return "toml: " + e.message
@ -103,13 +85,14 @@ func (e *DecodeError) Key() Key {
// //
// The function copies all bytes used in DecodeError, so that document and // The function copies all bytes used in DecodeError, so that document and
// highlight can be freely deallocated. // highlight can be freely deallocated.
//
//nolint:funlen //nolint:funlen
func wrapDecodeError(document []byte, de *decodeError) *DecodeError { func wrapDecodeError(document []byte, de *unstable.ParserError) *DecodeError {
offset := danger.SubsliceOffset(document, de.highlight) offset := danger.SubsliceOffset(document, de.Highlight)
errMessage := de.Error() errMessage := de.Error()
errLine, errColumn := positionAtEnd(document[:offset]) errLine, errColumn := positionAtEnd(document[:offset])
before, after := linesOfContext(document, de.highlight, offset, 3) before, after := linesOfContext(document, de.Highlight, offset, 3)
var buf strings.Builder var buf strings.Builder
@ -139,7 +122,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
buf.Write(before[0]) buf.Write(before[0])
} }
buf.Write(de.highlight) buf.Write(de.Highlight)
if len(after) > 0 { if len(after) > 0 {
buf.Write(after[0]) buf.Write(after[0])
@ -157,7 +140,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
buf.WriteString(strings.Repeat(" ", len(before[0]))) buf.WriteString(strings.Repeat(" ", len(before[0])))
} }
buf.WriteString(strings.Repeat("~", len(de.highlight))) buf.WriteString(strings.Repeat("~", len(de.Highlight)))
if len(errMessage) > 0 { if len(errMessage) > 0 {
buf.WriteString(" ") buf.WriteString(" ")
@ -182,7 +165,7 @@ func wrapDecodeError(document []byte, de *decodeError) *DecodeError {
message: errMessage, message: errMessage,
line: errLine, line: errLine,
column: errColumn, column: errColumn,
key: de.key, key: de.Key,
human: buf.String(), human: buf.String(),
} }
} }

@ -1,51 +0,0 @@
package ast
type Reference int
const InvalidReference Reference = -1
func (r Reference) Valid() bool {
return r != InvalidReference
}
type Builder struct {
tree Root
lastIdx int
}
func (b *Builder) Tree() *Root {
return &b.tree
}
func (b *Builder) NodeAt(ref Reference) *Node {
return b.tree.at(ref)
}
func (b *Builder) Reset() {
b.tree.nodes = b.tree.nodes[:0]
b.lastIdx = 0
}
func (b *Builder) Push(n Node) Reference {
b.lastIdx = len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n)
return Reference(b.lastIdx)
}
func (b *Builder) PushAndChain(n Node) Reference {
newIdx := len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n)
if b.lastIdx >= 0 {
b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx
}
b.lastIdx = newIdx
return Reference(b.lastIdx)
}
func (b *Builder) AttachChild(parent Reference, child Reference) {
b.tree.nodes[parent].child = int(child) - int(parent)
}
func (b *Builder) Chain(from Reference, to Reference) {
b.tree.nodes[from].next = int(to) - int(from)
}

@ -0,0 +1,42 @@
package characters
var invalidAsciiTable = [256]bool{
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
// 0x09 TAB
// 0x0A LF
0x0B: true,
0x0C: true,
// 0x0D CR
0x0E: true,
0x0F: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1A: true,
0x1B: true,
0x1C: true,
0x1D: true,
0x1E: true,
0x1F: true,
// 0x20 - 0x7E Printable ASCII characters
0x7F: true,
}
func InvalidAscii(b byte) bool {
return invalidAsciiTable[b]
}

@ -1,4 +1,4 @@
package toml package characters
import ( import (
"unicode/utf8" "unicode/utf8"
@ -32,7 +32,7 @@ func (u utf8Err) Zero() bool {
// 0x9 => tab, ok // 0x9 => tab, ok
// 0xA - 0x1F => invalid // 0xA - 0x1F => invalid
// 0x7F => invalid // 0x7F => invalid
func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) { func Utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
// Fast path. Check for and skip 8 bytes of ASCII characters per iteration. // Fast path. Check for and skip 8 bytes of ASCII characters per iteration.
offset := 0 offset := 0
for len(p) >= 8 { for len(p) >= 8 {
@ -48,7 +48,7 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
} }
for i, b := range p[:8] { for i, b := range p[:8] {
if invalidAscii(b) { if InvalidAscii(b) {
err.Index = offset + i err.Index = offset + i
err.Size = 1 err.Size = 1
return return
@ -62,7 +62,7 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
for i := 0; i < n; { for i := 0; i < n; {
pi := p[i] pi := p[i]
if pi < utf8.RuneSelf { if pi < utf8.RuneSelf {
if invalidAscii(pi) { if InvalidAscii(pi) {
err.Index = offset + i err.Index = offset + i
err.Size = 1 err.Size = 1
return return
@ -106,11 +106,11 @@ func utf8TomlValidAlreadyEscaped(p []byte) (err utf8Err) {
} }
// Return the size of the next rune if valid, 0 otherwise. // Return the size of the next rune if valid, 0 otherwise.
func utf8ValidNext(p []byte) int { func Utf8ValidNext(p []byte) int {
c := p[0] c := p[0]
if c < utf8.RuneSelf { if c < utf8.RuneSelf {
if invalidAscii(c) { if InvalidAscii(c) {
return 0 return 0
} }
return 1 return 1
@ -140,47 +140,6 @@ func utf8ValidNext(p []byte) int {
return size return size
} }
var invalidAsciiTable = [256]bool{
0x00: true,
0x01: true,
0x02: true,
0x03: true,
0x04: true,
0x05: true,
0x06: true,
0x07: true,
0x08: true,
// 0x09 TAB
// 0x0A LF
0x0B: true,
0x0C: true,
// 0x0D CR
0x0E: true,
0x0F: true,
0x10: true,
0x11: true,
0x12: true,
0x13: true,
0x14: true,
0x15: true,
0x16: true,
0x17: true,
0x18: true,
0x19: true,
0x1A: true,
0x1B: true,
0x1C: true,
0x1D: true,
0x1E: true,
0x1F: true,
// 0x20 - 0x7E Printable ASCII characters
0x7F: true,
}
func invalidAscii(b byte) bool {
return invalidAsciiTable[b]
}
// acceptRange gives the range of valid values for the second byte in a UTF-8 // acceptRange gives the range of valid values for the second byte in a UTF-8
// sequence. // sequence.
type acceptRange struct { type acceptRange struct {

@ -1,8 +1,6 @@
package tracker package tracker
import ( import "github.com/pelletier/go-toml/v2/unstable"
"github.com/pelletier/go-toml/v2/internal/ast"
)
// KeyTracker is a tracker that keeps track of the current Key as the AST is // KeyTracker is a tracker that keeps track of the current Key as the AST is
// walked. // walked.
@ -11,19 +9,19 @@ type KeyTracker struct {
} }
// UpdateTable sets the state of the tracker with the AST table node. // UpdateTable sets the state of the tracker with the AST table node.
func (t *KeyTracker) UpdateTable(node *ast.Node) { func (t *KeyTracker) UpdateTable(node *unstable.Node) {
t.reset() t.reset()
t.Push(node) t.Push(node)
} }
// UpdateArrayTable sets the state of the tracker with the AST array table node. // UpdateArrayTable sets the state of the tracker with the AST array table node.
func (t *KeyTracker) UpdateArrayTable(node *ast.Node) { func (t *KeyTracker) UpdateArrayTable(node *unstable.Node) {
t.reset() t.reset()
t.Push(node) t.Push(node)
} }
// Push the given key on the stack. // Push the given key on the stack.
func (t *KeyTracker) Push(node *ast.Node) { func (t *KeyTracker) Push(node *unstable.Node) {
it := node.Key() it := node.Key()
for it.Next() { for it.Next() {
t.k = append(t.k, string(it.Node().Data)) t.k = append(t.k, string(it.Node().Data))
@ -31,7 +29,7 @@ func (t *KeyTracker) Push(node *ast.Node) {
} }
// Pop key from stack. // Pop key from stack.
func (t *KeyTracker) Pop(node *ast.Node) { func (t *KeyTracker) Pop(node *unstable.Node) {
it := node.Key() it := node.Key()
for it.Next() { for it.Next() {
t.k = t.k[:len(t.k)-1] t.k = t.k[:len(t.k)-1]

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"sync" "sync"
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/unstable"
) )
type keyKind uint8 type keyKind uint8
@ -150,23 +150,23 @@ func (s *SeenTracker) setExplicitFlag(parentIdx int) {
// CheckExpression takes a top-level node and checks that it does not contain // CheckExpression takes a top-level node and checks that it does not contain
// keys that have been seen in previous calls, and validates that types are // keys that have been seen in previous calls, and validates that types are
// consistent. // consistent.
func (s *SeenTracker) CheckExpression(node *ast.Node) error { func (s *SeenTracker) CheckExpression(node *unstable.Node) error {
if s.entries == nil { if s.entries == nil {
s.reset() s.reset()
} }
switch node.Kind { switch node.Kind {
case ast.KeyValue: case unstable.KeyValue:
return s.checkKeyValue(node) return s.checkKeyValue(node)
case ast.Table: case unstable.Table:
return s.checkTable(node) return s.checkTable(node)
case ast.ArrayTable: case unstable.ArrayTable:
return s.checkArrayTable(node) return s.checkArrayTable(node)
default: default:
panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind))
} }
} }
func (s *SeenTracker) checkTable(node *ast.Node) error { func (s *SeenTracker) checkTable(node *unstable.Node) error {
if s.currentIdx >= 0 { if s.currentIdx >= 0 {
s.setExplicitFlag(s.currentIdx) s.setExplicitFlag(s.currentIdx)
} }
@ -219,7 +219,7 @@ func (s *SeenTracker) checkTable(node *ast.Node) error {
return nil return nil
} }
func (s *SeenTracker) checkArrayTable(node *ast.Node) error { func (s *SeenTracker) checkArrayTable(node *unstable.Node) error {
if s.currentIdx >= 0 { if s.currentIdx >= 0 {
s.setExplicitFlag(s.currentIdx) s.setExplicitFlag(s.currentIdx)
} }
@ -267,7 +267,7 @@ func (s *SeenTracker) checkArrayTable(node *ast.Node) error {
return nil return nil
} }
func (s *SeenTracker) checkKeyValue(node *ast.Node) error { func (s *SeenTracker) checkKeyValue(node *unstable.Node) error {
parentIdx := s.currentIdx parentIdx := s.currentIdx
it := node.Key() it := node.Key()
@ -297,26 +297,26 @@ func (s *SeenTracker) checkKeyValue(node *ast.Node) error {
value := node.Value() value := node.Value()
switch value.Kind { switch value.Kind {
case ast.InlineTable: case unstable.InlineTable:
return s.checkInlineTable(value) return s.checkInlineTable(value)
case ast.Array: case unstable.Array:
return s.checkArray(value) return s.checkArray(value)
} }
return nil return nil
} }
func (s *SeenTracker) checkArray(node *ast.Node) error { func (s *SeenTracker) checkArray(node *unstable.Node) error {
it := node.Children() it := node.Children()
for it.Next() { for it.Next() {
n := it.Node() n := it.Node()
switch n.Kind { switch n.Kind {
case ast.InlineTable: case unstable.InlineTable:
err := s.checkInlineTable(n) err := s.checkInlineTable(n)
if err != nil { if err != nil {
return err return err
} }
case ast.Array: case unstable.Array:
err := s.checkArray(n) err := s.checkArray(n)
if err != nil { if err != nil {
return err return err
@ -326,7 +326,7 @@ func (s *SeenTracker) checkArray(node *ast.Node) error {
return nil return nil
} }
func (s *SeenTracker) checkInlineTable(node *ast.Node) error { func (s *SeenTracker) checkInlineTable(node *unstable.Node) error {
if pool.New == nil { if pool.New == nil {
pool.New = func() interface{} { pool.New = func() interface{} {
return &SeenTracker{} return &SeenTracker{}

@ -4,6 +4,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"time" "time"
"github.com/pelletier/go-toml/v2/unstable"
) )
// LocalDate represents a calendar day in no specific timezone. // LocalDate represents a calendar day in no specific timezone.
@ -75,7 +77,7 @@ func (d LocalTime) MarshalText() ([]byte, error) {
func (d *LocalTime) UnmarshalText(b []byte) error { func (d *LocalTime) UnmarshalText(b []byte) error {
res, left, err := parseLocalTime(b) res, left, err := parseLocalTime(b)
if err == nil && len(left) != 0 { if err == nil && len(left) != 0 {
err = newDecodeError(left, "extra characters") err = unstable.NewParserError(left, "extra characters")
} }
if err != nil { if err != nil {
return err return err
@ -109,7 +111,7 @@ func (d LocalDateTime) MarshalText() ([]byte, error) {
func (d *LocalDateTime) UnmarshalText(data []byte) error { func (d *LocalDateTime) UnmarshalText(data []byte) error {
res, left, err := parseLocalDateTime(data) res, left, err := parseLocalDateTime(data)
if err == nil && len(left) != 0 { if err == nil && len(left) != 0 {
err = newDecodeError(left, "extra characters") err = unstable.NewParserError(left, "extra characters")
} }
if err != nil { if err != nil {
return err return err

@ -12,6 +12,8 @@ import (
"strings" "strings"
"time" "time"
"unicode" "unicode"
"github.com/pelletier/go-toml/v2/internal/characters"
) )
// Marshal serializes a Go value as a TOML document. // Marshal serializes a Go value as a TOML document.
@ -54,7 +56,7 @@ func NewEncoder(w io.Writer) *Encoder {
// This behavior can be controlled on an individual struct field basis with the // This behavior can be controlled on an individual struct field basis with the
// inline tag: // inline tag:
// //
// MyField `inline:"true"` // MyField `toml:",inline"`
func (enc *Encoder) SetTablesInline(inline bool) *Encoder { func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
enc.tablesInline = inline enc.tablesInline = inline
return enc return enc
@ -65,7 +67,7 @@ func (enc *Encoder) SetTablesInline(inline bool) *Encoder {
// //
// This behavior can be controlled on an individual struct field basis with the multiline tag: // This behavior can be controlled on an individual struct field basis with the multiline tag:
// //
// MyField `multiline:"true"` // MyField `multiline:"true"`
func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder { func (enc *Encoder) SetArraysMultiline(multiline bool) *Encoder {
enc.arraysMultiline = multiline enc.arraysMultiline = multiline
return enc return enc
@ -89,7 +91,7 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// //
// If v cannot be represented to TOML it returns an error. // If v cannot be represented to TOML it returns an error.
// //
// Encoding rules // # Encoding rules
// //
// A top level slice containing only maps or structs is encoded as [[table // A top level slice containing only maps or structs is encoded as [[table
// array]]. // array]].
@ -107,10 +109,30 @@ func (enc *Encoder) SetIndentTables(indent bool) *Encoder {
// a newline character or a single quote. In that case they are emitted as // a newline character or a single quote. In that case they are emitted as
// quoted strings. // quoted strings.
// //
// Unsigned integers larger than math.MaxInt64 cannot be encoded. Doing so
// results in an error. This rule exists because the TOML specification only
// requires parsers to support at least the 64 bits integer range. Allowing
// larger numbers would create non-standard TOML documents, which may not be
// readable (at best) by other implementations. To encode such numbers, a
// solution is a custom type that implements encoding.TextMarshaler.
//
// When encoding structs, fields are encoded in order of definition, with their // When encoding structs, fields are encoded in order of definition, with their
// exact name. // exact name.
// //
// Struct tags // Tables and array tables are separated by empty lines. However, consecutive
// subtables definitions are not. For example:
//
// [top1]
//
// [top2]
// [top2.child1]
//
// [[array]]
//
// [[array]]
// [array.child2]
//
// # Struct tags
// //
// The encoding of each public struct field can be customized by the format // The encoding of each public struct field can be customized by the format
// string in the "toml" key of the struct field's tag. This follows // string in the "toml" key of the struct field's tag. This follows
@ -303,7 +325,11 @@ func (enc *Encoder) encode(b []byte, ctx encoderCtx, v reflect.Value) ([]byte, e
b = append(b, "false"...) b = append(b, "false"...)
} }
case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint: case reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8, reflect.Uint:
b = strconv.AppendUint(b, v.Uint(), 10) x := v.Uint()
if x > uint64(math.MaxInt64) {
return nil, fmt.Errorf("toml: not encoding uint (%d) greater than max int64 (%d)", x, int64(math.MaxInt64))
}
b = strconv.AppendUint(b, x, 10)
case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int: case reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Int:
b = strconv.AppendInt(b, v.Int(), 10) b = strconv.AppendInt(b, v.Int(), 10)
default: default:
@ -322,13 +348,13 @@ func isNil(v reflect.Value) bool {
} }
} }
func shouldOmitEmpty(options valueOptions, v reflect.Value) bool {
return options.omitempty && isEmptyValue(v)
}
func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) { func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v reflect.Value) ([]byte, error) {
var err error var err error
if (ctx.options.omitempty || options.omitempty) && isEmptyValue(v) {
return b, nil
}
if !ctx.inline { if !ctx.inline {
b = enc.encodeComment(ctx.indent, options.comment, b) b = enc.encodeComment(ctx.indent, options.comment, b)
} }
@ -354,6 +380,8 @@ func (enc *Encoder) encodeKv(b []byte, ctx encoderCtx, options valueOptions, v r
func isEmptyValue(v reflect.Value) bool { func isEmptyValue(v reflect.Value) bool {
switch v.Kind() { switch v.Kind() {
case reflect.Struct:
return isEmptyStruct(v)
case reflect.Array, reflect.Map, reflect.Slice, reflect.String: case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0 return v.Len() == 0
case reflect.Bool: case reflect.Bool:
@ -370,6 +398,34 @@ func isEmptyValue(v reflect.Value) bool {
return false return false
} }
func isEmptyStruct(v reflect.Value) bool {
// TODO: merge with walkStruct and cache.
typ := v.Type()
for i := 0; i < typ.NumField(); i++ {
fieldType := typ.Field(i)
// only consider exported fields
if fieldType.PkgPath != "" {
continue
}
tag := fieldType.Tag.Get("toml")
// special field name to skip field
if tag == "-" {
continue
}
f := v.Field(i)
if !isEmptyValue(f) {
return false
}
}
return true
}
const literalQuote = '\'' const literalQuote = '\''
func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte { func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byte {
@ -383,7 +439,7 @@ func (enc *Encoder) encodeString(b []byte, v string, options valueOptions) []byt
func needsQuoting(v string) bool { func needsQuoting(v string) bool {
// TODO: vectorize // TODO: vectorize
for _, b := range []byte(v) { for _, b := range []byte(v) {
if b == '\'' || b == '\r' || b == '\n' || invalidAscii(b) { if b == '\'' || b == '\r' || b == '\n' || characters.InvalidAscii(b) {
return true return true
} }
} }
@ -399,7 +455,6 @@ func (enc *Encoder) encodeLiteralString(b []byte, v string) []byte {
return b return b
} }
//nolint:cyclop
func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte { func (enc *Encoder) encodeQuotedString(multiline bool, b []byte, v string) []byte {
stringQuote := `"` stringQuote := `"`
@ -746,7 +801,13 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
} }
ctx.skipTableHeader = false ctx.skipTableHeader = false
hasNonEmptyKV := false
for _, kv := range t.kvs { for _, kv := range t.kvs {
if shouldOmitEmpty(kv.Options, kv.Value) {
continue
}
hasNonEmptyKV = true
ctx.setKey(kv.Key) ctx.setKey(kv.Key)
b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value) b, err = enc.encodeKv(b, ctx, kv.Options, kv.Value)
@ -757,7 +818,20 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
b = append(b, '\n') b = append(b, '\n')
} }
first := true
for _, table := range t.tables { for _, table := range t.tables {
if shouldOmitEmpty(table.Options, table.Value) {
continue
}
if first {
first = false
if hasNonEmptyKV {
b = append(b, '\n')
}
} else {
b = append(b, "\n"...)
}
ctx.setKey(table.Key) ctx.setKey(table.Key)
ctx.options = table.Options ctx.options = table.Options
@ -766,8 +840,6 @@ func (enc *Encoder) encodeTable(b []byte, ctx encoderCtx, t table) ([]byte, erro
if err != nil { if err != nil {
return nil, err return nil, err
} }
b = append(b, '\n')
} }
return b, nil return b, nil
@ -780,6 +852,10 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
first := true first := true
for _, kv := range t.kvs { for _, kv := range t.kvs {
if shouldOmitEmpty(kv.Options, kv.Value) {
continue
}
if first { if first {
first = false first = false
} else { } else {
@ -795,7 +871,7 @@ func (enc *Encoder) encodeTableInline(b []byte, ctx encoderCtx, t table) ([]byte
} }
if len(t.tables) > 0 { if len(t.tables) > 0 {
panic("inline table cannot contain nested tables, online key-values") panic("inline table cannot contain nested tables, only key-values")
} }
b = append(b, "}"...) b = append(b, "}"...)
@ -894,6 +970,10 @@ func (enc *Encoder) encodeSliceAsArrayTable(b []byte, ctx encoderCtx, v reflect.
b = enc.encodeComment(ctx.indent, ctx.options.comment, b) b = enc.encodeComment(ctx.indent, ctx.options.comment, b)
for i := 0; i < v.Len(); i++ { for i := 0; i < v.Len(); i++ {
if i != 0 {
b = append(b, "\n"...)
}
b = append(b, scratch...) b = append(b, scratch...)
var err error var err error

@ -1,9 +1,9 @@
package toml package toml
import ( import (
"github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
"github.com/pelletier/go-toml/v2/internal/tracker" "github.com/pelletier/go-toml/v2/internal/tracker"
"github.com/pelletier/go-toml/v2/unstable"
) )
type strict struct { type strict struct {
@ -12,10 +12,10 @@ type strict struct {
// Tracks the current key being processed. // Tracks the current key being processed.
key tracker.KeyTracker key tracker.KeyTracker
missing []decodeError missing []unstable.ParserError
} }
func (s *strict) EnterTable(node *ast.Node) { func (s *strict) EnterTable(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@ -23,7 +23,7 @@ func (s *strict) EnterTable(node *ast.Node) {
s.key.UpdateTable(node) s.key.UpdateTable(node)
} }
func (s *strict) EnterArrayTable(node *ast.Node) { func (s *strict) EnterArrayTable(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@ -31,7 +31,7 @@ func (s *strict) EnterArrayTable(node *ast.Node) {
s.key.UpdateArrayTable(node) s.key.UpdateArrayTable(node)
} }
func (s *strict) EnterKeyValue(node *ast.Node) { func (s *strict) EnterKeyValue(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@ -39,7 +39,7 @@ func (s *strict) EnterKeyValue(node *ast.Node) {
s.key.Push(node) s.key.Push(node)
} }
func (s *strict) ExitKeyValue(node *ast.Node) { func (s *strict) ExitKeyValue(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
@ -47,27 +47,27 @@ func (s *strict) ExitKeyValue(node *ast.Node) {
s.key.Pop(node) s.key.Pop(node)
} }
func (s *strict) MissingTable(node *ast.Node) { func (s *strict) MissingTable(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
s.missing = append(s.missing, decodeError{ s.missing = append(s.missing, unstable.ParserError{
highlight: keyLocation(node), Highlight: keyLocation(node),
message: "missing table", Message: "missing table",
key: s.key.Key(), Key: s.key.Key(),
}) })
} }
func (s *strict) MissingField(node *ast.Node) { func (s *strict) MissingField(node *unstable.Node) {
if !s.Enabled { if !s.Enabled {
return return
} }
s.missing = append(s.missing, decodeError{ s.missing = append(s.missing, unstable.ParserError{
highlight: keyLocation(node), Highlight: keyLocation(node),
message: "missing field", Message: "missing field",
key: s.key.Key(), Key: s.key.Key(),
}) })
} }
@ -88,7 +88,7 @@ func (s *strict) Error(doc []byte) error {
return err return err
} }
func keyLocation(node *ast.Node) []byte { func keyLocation(node *unstable.Node) []byte {
k := node.Key() k := node.Key()
hasOne := k.Next() hasOne := k.Next()

@ -6,9 +6,9 @@ import (
"time" "time"
) )
var timeType = reflect.TypeOf(time.Time{}) var timeType = reflect.TypeOf((*time.Time)(nil)).Elem()
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem() var textMarshalerType = reflect.TypeOf((*encoding.TextMarshaler)(nil)).Elem()
var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem() var textUnmarshalerType = reflect.TypeOf((*encoding.TextUnmarshaler)(nil)).Elem()
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{}) var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}(nil))
var sliceInterfaceType = reflect.TypeOf([]interface{}{}) var sliceInterfaceType = reflect.TypeOf([]interface{}(nil))
var stringType = reflect.TypeOf("") var stringType = reflect.TypeOf("")

@ -12,16 +12,16 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/pelletier/go-toml/v2/internal/ast"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
"github.com/pelletier/go-toml/v2/internal/tracker" "github.com/pelletier/go-toml/v2/internal/tracker"
"github.com/pelletier/go-toml/v2/unstable"
) )
// Unmarshal deserializes a TOML document into a Go value. // Unmarshal deserializes a TOML document into a Go value.
// //
// It is a shortcut for Decoder.Decode() with the default options. // It is a shortcut for Decoder.Decode() with the default options.
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
p := parser{} p := unstable.Parser{}
p.Reset(data) p.Reset(data)
d := decoder{p: &p} d := decoder{p: &p}
@ -79,29 +79,29 @@ func (d *Decoder) DisallowUnknownFields() *Decoder {
// strict mode and a field is missing, a `toml.StrictMissingError` is // strict mode and a field is missing, a `toml.StrictMissingError` is
// returned. In any other case, this function returns a standard Go error. // returned. In any other case, this function returns a standard Go error.
// //
// Type mapping // # Type mapping
// //
// List of supported TOML types and their associated accepted Go types: // List of supported TOML types and their associated accepted Go types:
// //
// String -> string // String -> string
// Integer -> uint*, int*, depending on size // Integer -> uint*, int*, depending on size
// Float -> float*, depending on size // Float -> float*, depending on size
// Boolean -> bool // Boolean -> bool
// Offset Date-Time -> time.Time // Offset Date-Time -> time.Time
// Local Date-time -> LocalDateTime, time.Time // Local Date-time -> LocalDateTime, time.Time
// Local Date -> LocalDate, time.Time // Local Date -> LocalDate, time.Time
// Local Time -> LocalTime, time.Time // Local Time -> LocalTime, time.Time
// Array -> slice and array, depending on elements types // Array -> slice and array, depending on elements types
// Table -> map and struct // Table -> map and struct
// Inline Table -> same as Table // Inline Table -> same as Table
// Array of Tables -> same as Array and Table // Array of Tables -> same as Array and Table
func (d *Decoder) Decode(v interface{}) error { func (d *Decoder) Decode(v interface{}) error {
b, err := ioutil.ReadAll(d.r) b, err := ioutil.ReadAll(d.r)
if err != nil { if err != nil {
return fmt.Errorf("toml: %w", err) return fmt.Errorf("toml: %w", err)
} }
p := parser{} p := unstable.Parser{}
p.Reset(b) p.Reset(b)
dec := decoder{ dec := decoder{
p: &p, p: &p,
@ -115,7 +115,7 @@ func (d *Decoder) Decode(v interface{}) error {
type decoder struct { type decoder struct {
// Which parser instance in use for this decoding session. // Which parser instance in use for this decoding session.
p *parser p *unstable.Parser
// Flag indicating that the current expression is stashed. // Flag indicating that the current expression is stashed.
// If set to true, calling nextExpr will not actually pull a new expression // If set to true, calling nextExpr will not actually pull a new expression
@ -123,7 +123,7 @@ type decoder struct {
stashedExpr bool stashedExpr bool
// Skip expressions until a table is found. This is set to true when a // Skip expressions until a table is found. This is set to true when a
// table could not be create (missing field in map), so all KV expressions // table could not be created (missing field in map), so all KV expressions
// need to be skipped. // need to be skipped.
skipUntilTable bool skipUntilTable bool
@ -157,7 +157,7 @@ func (d *decoder) typeMismatchError(toml string, target reflect.Type) error {
return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target) return fmt.Errorf("toml: cannot decode TOML %s into a Go value of type %s", toml, target)
} }
func (d *decoder) expr() *ast.Node { func (d *decoder) expr() *unstable.Node {
return d.p.Expression() return d.p.Expression()
} }
@ -208,12 +208,12 @@ func (d *decoder) FromParser(v interface{}) error {
err := d.fromParser(r) err := d.fromParser(r)
if err == nil { if err == nil {
return d.strict.Error(d.p.data) return d.strict.Error(d.p.Data())
} }
var e *decodeError var e *unstable.ParserError
if errors.As(err, &e) { if errors.As(err, &e) {
return wrapDecodeError(d.p.data, e) return wrapDecodeError(d.p.Data(), e)
} }
return err return err
@ -234,16 +234,16 @@ func (d *decoder) fromParser(root reflect.Value) error {
Rules for the unmarshal code: Rules for the unmarshal code:
- The stack is used to keep track of which values need to be set where. - The stack is used to keep track of which values need to be set where.
- handle* functions <=> switch on a given ast.Kind. - handle* functions <=> switch on a given unstable.Kind.
- unmarshalX* functions need to unmarshal a node of kind X. - unmarshalX* functions need to unmarshal a node of kind X.
- An "object" is either a struct or a map. - An "object" is either a struct or a map.
*/ */
func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error { func (d *decoder) handleRootExpression(expr *unstable.Node, v reflect.Value) error {
var x reflect.Value var x reflect.Value
var err error var err error
if !(d.skipUntilTable && expr.Kind == ast.KeyValue) { if !(d.skipUntilTable && expr.Kind == unstable.KeyValue) {
err = d.seen.CheckExpression(expr) err = d.seen.CheckExpression(expr)
if err != nil { if err != nil {
return err return err
@ -251,16 +251,16 @@ func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error {
} }
switch expr.Kind { switch expr.Kind {
case ast.KeyValue: case unstable.KeyValue:
if d.skipUntilTable { if d.skipUntilTable {
return nil return nil
} }
x, err = d.handleKeyValue(expr, v) x, err = d.handleKeyValue(expr, v)
case ast.Table: case unstable.Table:
d.skipUntilTable = false d.skipUntilTable = false
d.strict.EnterTable(expr) d.strict.EnterTable(expr)
x, err = d.handleTable(expr.Key(), v) x, err = d.handleTable(expr.Key(), v)
case ast.ArrayTable: case unstable.ArrayTable:
d.skipUntilTable = false d.skipUntilTable = false
d.strict.EnterArrayTable(expr) d.strict.EnterArrayTable(expr)
x, err = d.handleArrayTable(expr.Key(), v) x, err = d.handleArrayTable(expr.Key(), v)
@ -269,7 +269,7 @@ func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error {
} }
if d.skipUntilTable { if d.skipUntilTable {
if expr.Kind == ast.Table || expr.Kind == ast.ArrayTable { if expr.Kind == unstable.Table || expr.Kind == unstable.ArrayTable {
d.strict.MissingTable(expr) d.strict.MissingTable(expr)
} }
} else if err == nil && x.IsValid() { } else if err == nil && x.IsValid() {
@ -279,14 +279,14 @@ func (d *decoder) handleRootExpression(expr *ast.Node, v reflect.Value) error {
return err return err
} }
func (d *decoder) handleArrayTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleArrayTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
if key.Next() { if key.Next() {
return d.handleArrayTablePart(key, v) return d.handleArrayTablePart(key, v)
} }
return d.handleKeyValues(v) return d.handleKeyValues(v)
} }
func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleArrayTableCollectionLast(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
switch v.Kind() { switch v.Kind() {
case reflect.Interface: case reflect.Interface:
elem := v.Elem() elem := v.Elem()
@ -339,21 +339,21 @@ func (d *decoder) handleArrayTableCollectionLast(key ast.Iterator, v reflect.Val
case reflect.Array: case reflect.Array:
idx := d.arrayIndex(true, v) idx := d.arrayIndex(true, v)
if idx >= v.Len() { if idx >= v.Len() {
return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx)
} }
elem := v.Index(idx) elem := v.Index(idx)
_, err := d.handleArrayTable(key, elem) _, err := d.handleArrayTable(key, elem)
return v, err return v, err
default:
return reflect.Value{}, d.typeMismatchError("array table", v.Type())
} }
return d.handleArrayTable(key, v)
} }
// When parsing an array table expression, each part of the key needs to be // When parsing an array table expression, each part of the key needs to be
// evaluated like a normal key, but if it returns a collection, it also needs to // evaluated like a normal key, but if it returns a collection, it also needs to
// point to the last element of the collection. Unless it is the last part of // point to the last element of the collection. Unless it is the last part of
// the key, then it needs to create a new element at the end. // the key, then it needs to create a new element at the end.
func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleArrayTableCollection(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
if key.IsLast() { if key.IsLast() {
return d.handleArrayTableCollectionLast(key, v) return d.handleArrayTableCollectionLast(key, v)
} }
@ -390,7 +390,7 @@ func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value)
case reflect.Array: case reflect.Array:
idx := d.arrayIndex(false, v) idx := d.arrayIndex(false, v)
if idx >= v.Len() { if idx >= v.Len() {
return v, fmt.Errorf("toml: cannot decode array table into %s at position %d", v.Type(), idx) return v, fmt.Errorf("%s at position %d", d.typeMismatchError("array table", v.Type()), idx)
} }
elem := v.Index(idx) elem := v.Index(idx)
_, err := d.handleArrayTable(key, elem) _, err := d.handleArrayTable(key, elem)
@ -400,7 +400,7 @@ func (d *decoder) handleArrayTableCollection(key ast.Iterator, v reflect.Value)
return d.handleArrayTable(key, v) return d.handleArrayTable(key, v)
} }
func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) { func (d *decoder) handleKeyPart(key unstable.Iterator, v reflect.Value, nextFn handlerFn, makeFn valueMakerFn) (reflect.Value, error) {
var rv reflect.Value var rv reflect.Value
// First, dispatch over v to make sure it is a valid object. // First, dispatch over v to make sure it is a valid object.
@ -483,7 +483,7 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
d.errorContext.Struct = t d.errorContext.Struct = t
d.errorContext.Field = path d.errorContext.Field = path
f := v.FieldByIndex(path) f := fieldByIndex(v, path)
x, err := nextFn(key, f) x, err := nextFn(key, f)
if err != nil || d.skipUntilTable { if err != nil || d.skipUntilTable {
return reflect.Value{}, err return reflect.Value{}, err
@ -518,7 +518,7 @@ func (d *decoder) handleKeyPart(key ast.Iterator, v reflect.Value, nextFn handle
// HandleArrayTablePart navigates the Go structure v using the key v. It is // HandleArrayTablePart navigates the Go structure v using the key v. It is
// only used for the prefix (non-last) parts of an array-table. When // only used for the prefix (non-last) parts of an array-table. When
// encountering a collection, it should go to the last element. // encountering a collection, it should go to the last element.
func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleArrayTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
var makeFn valueMakerFn var makeFn valueMakerFn
if key.IsLast() { if key.IsLast() {
makeFn = makeSliceInterface makeFn = makeSliceInterface
@ -530,10 +530,10 @@ func (d *decoder) handleArrayTablePart(key ast.Iterator, v reflect.Value) (refle
// HandleTable returns a reference when it has checked the next expression but // HandleTable returns a reference when it has checked the next expression but
// cannot handle it. // cannot handle it.
func (d *decoder) handleTable(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleTable(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
if v.Kind() == reflect.Slice { if v.Kind() == reflect.Slice {
if v.Len() == 0 { if v.Len() == 0 {
return reflect.Value{}, newDecodeError(key.Node().Data, "cannot store a table in a slice") return reflect.Value{}, unstable.NewParserError(key.Node().Data, "cannot store a table in a slice")
} }
elem := v.Index(v.Len() - 1) elem := v.Index(v.Len() - 1)
x, err := d.handleTable(key, elem) x, err := d.handleTable(key, elem)
@ -560,7 +560,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
var rv reflect.Value var rv reflect.Value
for d.nextExpr() { for d.nextExpr() {
expr := d.expr() expr := d.expr()
if expr.Kind != ast.KeyValue { if expr.Kind != unstable.KeyValue {
// Stash the expression so that fromParser can just loop and use // Stash the expression so that fromParser can just loop and use
// the right handler. // the right handler.
// We could just recurse ourselves here, but at least this gives a // We could just recurse ourselves here, but at least this gives a
@ -587,7 +587,7 @@ func (d *decoder) handleKeyValues(v reflect.Value) (reflect.Value, error) {
} }
type ( type (
handlerFn func(key ast.Iterator, v reflect.Value) (reflect.Value, error) handlerFn func(key unstable.Iterator, v reflect.Value) (reflect.Value, error)
valueMakerFn func() reflect.Value valueMakerFn func() reflect.Value
) )
@ -599,11 +599,11 @@ func makeSliceInterface() reflect.Value {
return reflect.MakeSlice(sliceInterfaceType, 0, 16) return reflect.MakeSlice(sliceInterfaceType, 0, 16)
} }
func (d *decoder) handleTablePart(key ast.Iterator, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleTablePart(key unstable.Iterator, v reflect.Value) (reflect.Value, error) {
return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface) return d.handleKeyPart(key, v, d.handleTable, makeMapStringInterface)
} }
func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, error) { func (d *decoder) tryTextUnmarshaler(node *unstable.Node, v reflect.Value) (bool, error) {
// Special case for time, because we allow to unmarshal to it from // Special case for time, because we allow to unmarshal to it from
// different kind of AST nodes. // different kind of AST nodes.
if v.Type() == timeType { if v.Type() == timeType {
@ -613,7 +613,7 @@ func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, err
if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) { if v.CanAddr() && v.Addr().Type().Implements(textUnmarshalerType) {
err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data) err := v.Addr().Interface().(encoding.TextUnmarshaler).UnmarshalText(node.Data)
if err != nil { if err != nil {
return false, newDecodeError(d.p.Raw(node.Raw), "%w", err) return false, unstable.NewParserError(d.p.Raw(node.Raw), "%w", err)
} }
return true, nil return true, nil
@ -622,7 +622,7 @@ func (d *decoder) tryTextUnmarshaler(node *ast.Node, v reflect.Value) (bool, err
return false, nil return false, nil
} }
func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error { func (d *decoder) handleValue(value *unstable.Node, v reflect.Value) error {
for v.Kind() == reflect.Ptr { for v.Kind() == reflect.Ptr {
v = initAndDereferencePointer(v) v = initAndDereferencePointer(v)
} }
@ -633,32 +633,32 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
} }
switch value.Kind { switch value.Kind {
case ast.String: case unstable.String:
return d.unmarshalString(value, v) return d.unmarshalString(value, v)
case ast.Integer: case unstable.Integer:
return d.unmarshalInteger(value, v) return d.unmarshalInteger(value, v)
case ast.Float: case unstable.Float:
return d.unmarshalFloat(value, v) return d.unmarshalFloat(value, v)
case ast.Bool: case unstable.Bool:
return d.unmarshalBool(value, v) return d.unmarshalBool(value, v)
case ast.DateTime: case unstable.DateTime:
return d.unmarshalDateTime(value, v) return d.unmarshalDateTime(value, v)
case ast.LocalDate: case unstable.LocalDate:
return d.unmarshalLocalDate(value, v) return d.unmarshalLocalDate(value, v)
case ast.LocalTime: case unstable.LocalTime:
return d.unmarshalLocalTime(value, v) return d.unmarshalLocalTime(value, v)
case ast.LocalDateTime: case unstable.LocalDateTime:
return d.unmarshalLocalDateTime(value, v) return d.unmarshalLocalDateTime(value, v)
case ast.InlineTable: case unstable.InlineTable:
return d.unmarshalInlineTable(value, v) return d.unmarshalInlineTable(value, v)
case ast.Array: case unstable.Array:
return d.unmarshalArray(value, v) return d.unmarshalArray(value, v)
default: default:
panic(fmt.Errorf("handleValue not implemented for %s", value.Kind)) panic(fmt.Errorf("handleValue not implemented for %s", value.Kind))
} }
} }
func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalArray(array *unstable.Node, v reflect.Value) error {
switch v.Kind() { switch v.Kind() {
case reflect.Slice: case reflect.Slice:
if v.IsNil() { if v.IsNil() {
@ -729,7 +729,7 @@ func (d *decoder) unmarshalArray(array *ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalInlineTable(itable *unstable.Node, v reflect.Value) error {
// Make sure v is an initialized object. // Make sure v is an initialized object.
switch v.Kind() { switch v.Kind() {
case reflect.Map: case reflect.Map:
@ -746,7 +746,7 @@ func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error
} }
return d.unmarshalInlineTable(itable, elem) return d.unmarshalInlineTable(itable, elem)
default: default:
return newDecodeError(itable.Data, "cannot store inline table in Go type %s", v.Kind()) return unstable.NewParserError(itable.Data, "cannot store inline table in Go type %s", v.Kind())
} }
it := itable.Children() it := itable.Children()
@ -765,7 +765,7 @@ func (d *decoder) unmarshalInlineTable(itable *ast.Node, v reflect.Value) error
return nil return nil
} }
func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalDateTime(value *unstable.Node, v reflect.Value) error {
dt, err := parseDateTime(value.Data) dt, err := parseDateTime(value.Data)
if err != nil { if err != nil {
return err return err
@ -775,7 +775,7 @@ func (d *decoder) unmarshalDateTime(value *ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalDate(value *unstable.Node, v reflect.Value) error {
ld, err := parseLocalDate(value.Data) ld, err := parseLocalDate(value.Data)
if err != nil { if err != nil {
return err return err
@ -792,28 +792,28 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalLocalTime(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalTime(value *unstable.Node, v reflect.Value) error {
lt, rest, err := parseLocalTime(value.Data) lt, rest, err := parseLocalTime(value.Data)
if err != nil { if err != nil {
return err return err
} }
if len(rest) > 0 { if len(rest) > 0 {
return newDecodeError(rest, "extra characters at the end of a local time") return unstable.NewParserError(rest, "extra characters at the end of a local time")
} }
v.Set(reflect.ValueOf(lt)) v.Set(reflect.ValueOf(lt))
return nil return nil
} }
func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalDateTime(value *unstable.Node, v reflect.Value) error {
ldt, rest, err := parseLocalDateTime(value.Data) ldt, rest, err := parseLocalDateTime(value.Data)
if err != nil { if err != nil {
return err return err
} }
if len(rest) > 0 { if len(rest) > 0 {
return newDecodeError(rest, "extra characters at the end of a local date time") return unstable.NewParserError(rest, "extra characters at the end of a local date time")
} }
if v.Type() == timeType { if v.Type() == timeType {
@ -828,7 +828,7 @@ func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error
return nil return nil
} }
func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalBool(value *unstable.Node, v reflect.Value) error {
b := value.Data[0] == 't' b := value.Data[0] == 't'
switch v.Kind() { switch v.Kind() {
@ -837,13 +837,13 @@ func (d *decoder) unmarshalBool(value *ast.Node, v reflect.Value) error {
case reflect.Interface: case reflect.Interface:
v.Set(reflect.ValueOf(b)) v.Set(reflect.ValueOf(b))
default: default:
return newDecodeError(value.Data, "cannot assign boolean to a %t", b) return unstable.NewParserError(value.Data, "cannot assign boolean to a %t", b)
} }
return nil return nil
} }
func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalFloat(value *unstable.Node, v reflect.Value) error {
f, err := parseFloat(value.Data) f, err := parseFloat(value.Data)
if err != nil { if err != nil {
return err return err
@ -854,13 +854,13 @@ func (d *decoder) unmarshalFloat(value *ast.Node, v reflect.Value) error {
v.SetFloat(f) v.SetFloat(f)
case reflect.Float32: case reflect.Float32:
if f > math.MaxFloat32 { if f > math.MaxFloat32 {
return newDecodeError(value.Data, "number %f does not fit in a float32", f) return unstable.NewParserError(value.Data, "number %f does not fit in a float32", f)
} }
v.SetFloat(f) v.SetFloat(f)
case reflect.Interface: case reflect.Interface:
v.Set(reflect.ValueOf(f)) v.Set(reflect.ValueOf(f))
default: default:
return newDecodeError(value.Data, "float cannot be assigned to %s", v.Kind()) return unstable.NewParserError(value.Data, "float cannot be assigned to %s", v.Kind())
} }
return nil return nil
@ -886,7 +886,7 @@ func init() {
} }
} }
func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalInteger(value *unstable.Node, v reflect.Value) error {
i, err := parseInteger(value.Data) i, err := parseInteger(value.Data)
if err != nil { if err != nil {
return err return err
@ -967,20 +967,20 @@ func (d *decoder) unmarshalInteger(value *ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalString(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalString(value *unstable.Node, v reflect.Value) error {
switch v.Kind() { switch v.Kind() {
case reflect.String: case reflect.String:
v.SetString(string(value.Data)) v.SetString(string(value.Data))
case reflect.Interface: case reflect.Interface:
v.Set(reflect.ValueOf(string(value.Data))) v.Set(reflect.ValueOf(string(value.Data)))
default: default:
return newDecodeError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind()) return unstable.NewParserError(d.p.Raw(value.Raw), "cannot store TOML string into a Go %s", v.Kind())
} }
return nil return nil
} }
func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValue(expr *unstable.Node, v reflect.Value) (reflect.Value, error) {
d.strict.EnterKeyValue(expr) d.strict.EnterKeyValue(expr)
v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v) v, err := d.handleKeyValueInner(expr.Key(), expr.Value(), v)
@ -994,7 +994,7 @@ func (d *decoder) handleKeyValue(expr *ast.Node, v reflect.Value) (reflect.Value
return v, err return v, err
} }
func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValueInner(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) {
if key.Next() { if key.Next() {
// Still scoping the key // Still scoping the key
return d.handleKeyValuePart(key, value, v) return d.handleKeyValuePart(key, value, v)
@ -1004,7 +1004,7 @@ func (d *decoder) handleKeyValueInner(key ast.Iterator, value *ast.Node, v refle
return reflect.Value{}, d.handleValue(value, v) return reflect.Value{}, d.handleValue(value, v)
} }
func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflect.Value) (reflect.Value, error) { func (d *decoder) handleKeyValuePart(key unstable.Iterator, value *unstable.Node, v reflect.Value) (reflect.Value, error) {
// contains the replacement for v // contains the replacement for v
var rv reflect.Value var rv reflect.Value
@ -1071,7 +1071,7 @@ func (d *decoder) handleKeyValuePart(key ast.Iterator, value *ast.Node, v reflec
d.errorContext.Struct = t d.errorContext.Struct = t
d.errorContext.Field = path d.errorContext.Field = path
f := v.FieldByIndex(path) f := fieldByIndex(v, path)
x, err := d.handleKeyValueInner(key, value, f) x, err := d.handleKeyValueInner(key, value, f)
if err != nil { if err != nil {
return reflect.Value{}, err return reflect.Value{}, err
@ -1135,6 +1135,21 @@ func initAndDereferencePointer(v reflect.Value) reflect.Value {
return elem return elem
} }
// Same as reflect.Value.FieldByIndex, but creates pointers if needed.
func fieldByIndex(v reflect.Value, path []int) reflect.Value {
for i, x := range path {
v = v.Field(x)
if i < len(path)-1 && v.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
}
}
return v
}
type fieldPathsMap = map[string][]int type fieldPathsMap = map[string][]int
var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap var globalFieldPathsCache atomic.Value // map[danger.TypeID]fieldPathsMap
@ -1192,7 +1207,14 @@ func forEachField(t reflect.Type, path []int, do func(name string, path []int))
} }
if f.Anonymous && name == "" { if f.Anonymous && name == "" {
forEachField(f.Type, fieldPath, do) t2 := f.Type
if t2.Kind() == reflect.Ptr {
t2 = t2.Elem()
}
if t2.Kind() == reflect.Struct {
forEachField(t2, fieldPath, do)
}
continue continue
} }

@ -1,4 +1,4 @@
package ast package unstable
import ( import (
"fmt" "fmt"
@ -7,14 +7,17 @@ import (
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
) )
// Iterator starts uninitialized, you need to call Next() first. // Iterator over a sequence of nodes.
//
// Starts uninitialized, you need to call Next() first.
// //
// For example: // For example:
// //
// it := n.Children() // it := n.Children()
// for it.Next() { // for it.Next() {
// it.Node() // n := it.Node()
// } // // do something with n
// }
type Iterator struct { type Iterator struct {
started bool started bool
node *Node node *Node
@ -32,42 +35,31 @@ func (c *Iterator) Next() bool {
} }
// IsLast returns true if the current node of the iterator is the last // IsLast returns true if the current node of the iterator is the last
// one. Subsequent call to Next() will return false. // one. Subsequent calls to Next() will return false.
func (c *Iterator) IsLast() bool { func (c *Iterator) IsLast() bool {
return c.node.next == 0 return c.node.next == 0
} }
// Node returns a copy of the node pointed at by the iterator. // Node returns a pointer to the node pointed at by the iterator.
func (c *Iterator) Node() *Node { func (c *Iterator) Node() *Node {
return c.node return c.node
} }
// Root contains a full AST. // Node in a TOML expression AST.
// //
// It is immutable once constructed with Builder. // Depending on Kind, its sequence of children should be interpreted
type Root struct { // differently.
nodes []Node //
} // - Array have one child per element in the array.
// - InlineTable have one child per key-value in the table (each of kind
// Iterator over the top level nodes. // InlineTable).
func (r *Root) Iterator() Iterator { // - KeyValue have at least two children. The first one is the value. The rest
it := Iterator{} // make a potentially dotted key.
if len(r.nodes) > 0 { // - Table and ArrayTable's children represent a dotted key (same as
it.node = &r.nodes[0] // KeyValue, but without the first node being the value).
} //
return it // When relevant, Raw describes the range of bytes this node is refering to in
} // the input document. Use Parser.Raw() to retrieve the actual bytes.
func (r *Root) at(idx Reference) *Node {
return &r.nodes[idx]
}
// Arrays have one child per element in the array. InlineTables have
// one child per key-value pair in the table. KeyValues have at least
// two children. The first one is the value. The rest make a
// potentially dotted key. Table and Array table have one child per
// element of the key they represent (same as KeyValue, but without
// the last node being the value).
type Node struct { type Node struct {
Kind Kind Kind Kind
Raw Range // Raw bytes from the input. Raw Range // Raw bytes from the input.
@ -80,13 +72,13 @@ type Node struct {
child int // 0 if no child child int // 0 if no child
} }
// Range of bytes in the document.
type Range struct { type Range struct {
Offset uint32 Offset uint32
Length uint32 Length uint32
} }
// Next returns a copy of the next node, or an invalid Node if there // Next returns a pointer to the next node, or nil if there is no next node.
// is no next node.
func (n *Node) Next() *Node { func (n *Node) Next() *Node {
if n.next == 0 { if n.next == 0 {
return nil return nil
@ -96,9 +88,9 @@ func (n *Node) Next() *Node {
return (*Node)(danger.Stride(ptr, size, n.next)) return (*Node)(danger.Stride(ptr, size, n.next))
} }
// Child returns a copy of the first child node of this node. Other // Child returns a pointer to the first child node of this node. Other children
// children can be accessed calling Next on the first child. Returns // can be accessed calling Next on the first child. Returns an nil if this Node
// an invalid Node if there is none. // has no child.
func (n *Node) Child() *Node { func (n *Node) Child() *Node {
if n.child == 0 { if n.child == 0 {
return nil return nil
@ -113,9 +105,9 @@ func (n *Node) Valid() bool {
return n != nil return n != nil
} }
// Key returns the child nodes making the Key on a supported // Key returns the children nodes making the Key on a supported node. Panics
// node. Panics otherwise. They are guaranteed to be all be of the // otherwise. They are guaranteed to be all be of the Kind Key. A simple key
// Kind Key. A simple key would return just one element. // would return just one element.
func (n *Node) Key() Iterator { func (n *Node) Key() Iterator {
switch n.Kind { switch n.Kind {
case KeyValue: case KeyValue:

@ -0,0 +1,71 @@
package unstable
// root contains a full AST.
//
// It is immutable once constructed with Builder.
type root struct {
nodes []Node
}
// Iterator over the top level nodes.
func (r *root) Iterator() Iterator {
it := Iterator{}
if len(r.nodes) > 0 {
it.node = &r.nodes[0]
}
return it
}
func (r *root) at(idx reference) *Node {
return &r.nodes[idx]
}
type reference int
const invalidReference reference = -1
func (r reference) Valid() bool {
return r != invalidReference
}
type builder struct {
tree root
lastIdx int
}
func (b *builder) Tree() *root {
return &b.tree
}
func (b *builder) NodeAt(ref reference) *Node {
return b.tree.at(ref)
}
func (b *builder) Reset() {
b.tree.nodes = b.tree.nodes[:0]
b.lastIdx = 0
}
func (b *builder) Push(n Node) reference {
b.lastIdx = len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n)
return reference(b.lastIdx)
}
func (b *builder) PushAndChain(n Node) reference {
newIdx := len(b.tree.nodes)
b.tree.nodes = append(b.tree.nodes, n)
if b.lastIdx >= 0 {
b.tree.nodes[b.lastIdx].next = newIdx - b.lastIdx
}
b.lastIdx = newIdx
return reference(b.lastIdx)
}
func (b *builder) AttachChild(parent reference, child reference) {
b.tree.nodes[parent].child = int(child) - int(parent)
}
func (b *builder) Chain(from reference, to reference) {
b.tree.nodes[from].next = int(to) - int(from)
}

@ -0,0 +1,3 @@
// Package unstable provides APIs that do not meet the backward compatibility
// guarantees yet.
package unstable

@ -1,25 +1,26 @@
package ast package unstable
import "fmt" import "fmt"
// Kind represents the type of TOML structure contained in a given Node.
type Kind int type Kind int
const ( const (
// meta // Meta
Invalid Kind = iota Invalid Kind = iota
Comment Comment
Key Key
// top level structures // Top level structures
Table Table
ArrayTable ArrayTable
KeyValue KeyValue
// containers values // Containers values
Array Array
InlineTable InlineTable
// values // Values
String String
Bool Bool
Float Float
@ -30,6 +31,7 @@ const (
DateTime DateTime
) )
// String implementation of fmt.Stringer.
func (k Kind) String() string { func (k Kind) String() string {
switch k { switch k {
case Invalid: case Invalid:

@ -1,50 +1,108 @@
package toml package unstable
import ( import (
"bytes" "bytes"
"fmt"
"unicode" "unicode"
"github.com/pelletier/go-toml/v2/internal/ast" "github.com/pelletier/go-toml/v2/internal/characters"
"github.com/pelletier/go-toml/v2/internal/danger" "github.com/pelletier/go-toml/v2/internal/danger"
) )
type parser struct { // ParserError describes an error relative to the content of the document.
builder ast.Builder //
ref ast.Reference // It cannot outlive the instance of Parser it refers to, and may cause panics
// if the parser is reset.
type ParserError struct {
Highlight []byte
Message string
Key []string // optional
}
// Error is the implementation of the error interface.
func (e *ParserError) Error() string {
return e.Message
}
// NewParserError is a convenience function to create a ParserError
//
// Warning: Highlight needs to be a subslice of Parser.data, so only slices
// returned by Parser.Raw are valid candidates.
func NewParserError(highlight []byte, format string, args ...interface{}) error {
return &ParserError{
Highlight: highlight,
Message: fmt.Errorf(format, args...).Error(),
}
}
// Parser scans over a TOML-encoded document and generates an iterative AST.
//
// To prime the Parser, first reset it with the contents of a TOML document.
// Then, process all top-level expressions sequentially. See Example.
//
// Don't forget to check Error() after you're done parsing.
//
// Each top-level expression needs to be fully processed before calling
// NextExpression() again. Otherwise, calls to various Node methods may panic if
// the parser has moved on the next expression.
//
// For performance reasons, go-toml doesn't make a copy of the input bytes to
// the parser. Make sure to copy all the bytes you need to outlive the slice
// given to the parser.
//
// The parser doesn't provide nodes for comments yet, nor for whitespace.
type Parser struct {
data []byte data []byte
builder builder
ref reference
left []byte left []byte
err error err error
first bool first bool
} }
func (p *parser) Range(b []byte) ast.Range { // Data returns the slice provided to the last call to Reset.
return ast.Range{ func (p *Parser) Data() []byte {
return p.data
}
// Range returns a range description that corresponds to a given slice of the
// input. If the argument is not a subslice of the parser input, this function
// panics.
func (p *Parser) Range(b []byte) Range {
return Range{
Offset: uint32(danger.SubsliceOffset(p.data, b)), Offset: uint32(danger.SubsliceOffset(p.data, b)),
Length: uint32(len(b)), Length: uint32(len(b)),
} }
} }
func (p *parser) Raw(raw ast.Range) []byte { // Raw returns the slice corresponding to the bytes in the given range.
func (p *Parser) Raw(raw Range) []byte {
return p.data[raw.Offset : raw.Offset+raw.Length] return p.data[raw.Offset : raw.Offset+raw.Length]
} }
func (p *parser) Reset(b []byte) { // Reset brings the parser to its initial state for a given input. It wipes an
// reuses internal storage to reduce allocation.
func (p *Parser) Reset(b []byte) {
p.builder.Reset() p.builder.Reset()
p.ref = ast.InvalidReference p.ref = invalidReference
p.data = b p.data = b
p.left = b p.left = b
p.err = nil p.err = nil
p.first = true p.first = true
} }
//nolint:cyclop // NextExpression parses the next top-level expression. If an expression was
func (p *parser) NextExpression() bool { // successfully parsed, it returns true. If the parser is at the end of the
// document or an error occurred, it returns false.
//
// Retrieve the parsed expression with Expression().
func (p *Parser) NextExpression() bool {
if len(p.left) == 0 || p.err != nil { if len(p.left) == 0 || p.err != nil {
return false return false
} }
p.builder.Reset() p.builder.Reset()
p.ref = ast.InvalidReference p.ref = invalidReference
for { for {
if len(p.left) == 0 || p.err != nil { if len(p.left) == 0 || p.err != nil {
@ -73,15 +131,18 @@ func (p *parser) NextExpression() bool {
} }
} }
func (p *parser) Expression() *ast.Node { // Expression returns a pointer to the node representing the last successfully
// parsed expresion.
func (p *Parser) Expression() *Node {
return p.builder.NodeAt(p.ref) return p.builder.NodeAt(p.ref)
} }
func (p *parser) Error() error { // Error returns any error that has occured during parsing.
func (p *Parser) Error() error {
return p.err return p.err
} }
func (p *parser) parseNewline(b []byte) ([]byte, error) { func (p *Parser) parseNewline(b []byte) ([]byte, error) {
if b[0] == '\n' { if b[0] == '\n' {
return b[1:], nil return b[1:], nil
} }
@ -91,14 +152,14 @@ func (p *parser) parseNewline(b []byte) ([]byte, error) {
return rest, err return rest, err
} }
return nil, newDecodeError(b[0:1], "expected newline but got %#U", b[0]) return nil, NewParserError(b[0:1], "expected newline but got %#U", b[0])
} }
func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseExpression(b []byte) (reference, []byte, error) {
// expression = ws [ comment ] // expression = ws [ comment ]
// expression =/ ws keyval ws [ comment ] // expression =/ ws keyval ws [ comment ]
// expression =/ ws table ws [ comment ] // expression =/ ws table ws [ comment ]
ref := ast.InvalidReference ref := invalidReference
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
@ -136,7 +197,7 @@ func (p *parser) parseExpression(b []byte) (ast.Reference, []byte, error) {
return ref, b, nil return ref, b, nil
} }
func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseTable(b []byte) (reference, []byte, error) {
// table = std-table / array-table // table = std-table / array-table
if len(b) > 1 && b[1] == '[' { if len(b) > 1 && b[1] == '[' {
return p.parseArrayTable(b) return p.parseArrayTable(b)
@ -145,12 +206,12 @@ func (p *parser) parseTable(b []byte) (ast.Reference, []byte, error) {
return p.parseStdTable(b) return p.parseStdTable(b)
} }
func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseArrayTable(b []byte) (reference, []byte, error) {
// array-table = array-table-open key array-table-close // array-table = array-table-open key array-table-close
// array-table-open = %x5B.5B ws ; [[ Double left square bracket // array-table-open = %x5B.5B ws ; [[ Double left square bracket
// array-table-close = ws %x5D.5D ; ]] Double right square bracket // array-table-close = ws %x5D.5D ; ]] Double right square bracket
ref := p.builder.Push(ast.Node{ ref := p.builder.Push(Node{
Kind: ast.ArrayTable, Kind: ArrayTable,
}) })
b = b[2:] b = b[2:]
@ -174,12 +235,12 @@ func (p *parser) parseArrayTable(b []byte) (ast.Reference, []byte, error) {
return ref, b, err return ref, b, err
} }
func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseStdTable(b []byte) (reference, []byte, error) {
// std-table = std-table-open key std-table-close // std-table = std-table-open key std-table-close
// std-table-open = %x5B ws ; [ Left square bracket // std-table-open = %x5B ws ; [ Left square bracket
// std-table-close = ws %x5D ; ] Right square bracket // std-table-close = ws %x5D ; ] Right square bracket
ref := p.builder.Push(ast.Node{ ref := p.builder.Push(Node{
Kind: ast.Table, Kind: Table,
}) })
b = b[1:] b = b[1:]
@ -199,15 +260,15 @@ func (p *parser) parseStdTable(b []byte) (ast.Reference, []byte, error) {
return ref, b, err return ref, b, err
} }
func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseKeyval(b []byte) (reference, []byte, error) {
// keyval = key keyval-sep val // keyval = key keyval-sep val
ref := p.builder.Push(ast.Node{ ref := p.builder.Push(Node{
Kind: ast.KeyValue, Kind: KeyValue,
}) })
key, b, err := p.parseKey(b) key, b, err := p.parseKey(b)
if err != nil { if err != nil {
return ast.InvalidReference, nil, err return invalidReference, nil, err
} }
// keyval-sep = ws %x3D ws ; = // keyval-sep = ws %x3D ws ; =
@ -215,12 +276,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
if len(b) == 0 { if len(b) == 0 {
return ast.InvalidReference, nil, newDecodeError(b, "expected = after a key, but the document ends there") return invalidReference, nil, NewParserError(b, "expected = after a key, but the document ends there")
} }
b, err = expect('=', b) b, err = expect('=', b)
if err != nil { if err != nil {
return ast.InvalidReference, nil, err return invalidReference, nil, err
} }
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
@ -237,12 +298,12 @@ func (p *parser) parseKeyval(b []byte) (ast.Reference, []byte, error) {
} }
//nolint:cyclop,funlen //nolint:cyclop,funlen
func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseVal(b []byte) (reference, []byte, error) {
// val = string / boolean / array / inline-table / date-time / float / integer // val = string / boolean / array / inline-table / date-time / float / integer
ref := ast.InvalidReference ref := invalidReference
if len(b) == 0 { if len(b) == 0 {
return ref, nil, newDecodeError(b, "expected value, not eof") return ref, nil, NewParserError(b, "expected value, not eof")
} }
var err error var err error
@ -259,8 +320,8 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) {
} }
if err == nil { if err == nil {
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(Node{
Kind: ast.String, Kind: String,
Raw: p.Range(raw), Raw: p.Range(raw),
Data: v, Data: v,
}) })
@ -277,8 +338,8 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) {
} }
if err == nil { if err == nil {
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(Node{
Kind: ast.String, Kind: String,
Raw: p.Range(raw), Raw: p.Range(raw),
Data: v, Data: v,
}) })
@ -287,22 +348,22 @@ func (p *parser) parseVal(b []byte) (ast.Reference, []byte, error) {
return ref, b, err return ref, b, err
case 't': case 't':
if !scanFollowsTrue(b) { if !scanFollowsTrue(b) {
return ref, nil, newDecodeError(atmost(b, 4), "expected 'true'") return ref, nil, NewParserError(atmost(b, 4), "expected 'true'")
} }
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(Node{
Kind: ast.Bool, Kind: Bool,
Data: b[:4], Data: b[:4],
}) })
return ref, b[4:], nil return ref, b[4:], nil
case 'f': case 'f':
if !scanFollowsFalse(b) { if !scanFollowsFalse(b) {
return ref, nil, newDecodeError(atmost(b, 5), "expected 'false'") return ref, nil, NewParserError(atmost(b, 5), "expected 'false'")
} }
ref = p.builder.Push(ast.Node{ ref = p.builder.Push(Node{
Kind: ast.Bool, Kind: Bool,
Data: b[:5], Data: b[:5],
}) })
@ -324,7 +385,7 @@ func atmost(b []byte, n int) []byte {
return b[:n] return b[:n]
} }
func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
v, rest, err := scanLiteralString(b) v, rest, err := scanLiteralString(b)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -333,19 +394,19 @@ func (p *parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) {
return v, v[1 : len(v)-1], rest, nil return v, v[1 : len(v)-1], rest, nil
} }
func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) {
// inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close // inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close
// inline-table-open = %x7B ws ; { // inline-table-open = %x7B ws ; {
// inline-table-close = ws %x7D ; } // inline-table-close = ws %x7D ; }
// inline-table-sep = ws %x2C ws ; , Comma // inline-table-sep = ws %x2C ws ; , Comma
// inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] // inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ]
parent := p.builder.Push(ast.Node{ parent := p.builder.Push(Node{
Kind: ast.InlineTable, Kind: InlineTable,
}) })
first := true first := true
var child ast.Reference var child reference
b = b[1:] b = b[1:]
@ -356,7 +417,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) {
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
if len(b) == 0 { if len(b) == 0 {
return parent, nil, newDecodeError(previousB[:1], "inline table is incomplete") return parent, nil, NewParserError(previousB[:1], "inline table is incomplete")
} }
if b[0] == '}' { if b[0] == '}' {
@ -371,7 +432,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) {
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
} }
var kv ast.Reference var kv reference
kv, b, err = p.parseKeyval(b) kv, b, err = p.parseKeyval(b)
if err != nil { if err != nil {
@ -394,7 +455,7 @@ func (p *parser) parseInlineTable(b []byte) (ast.Reference, []byte, error) {
} }
//nolint:funlen,cyclop //nolint:funlen,cyclop
func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseValArray(b []byte) (reference, []byte, error) {
// array = array-open [ array-values ] ws-comment-newline array-close // array = array-open [ array-values ] ws-comment-newline array-close
// array-open = %x5B ; [ // array-open = %x5B ; [
// array-close = %x5D ; ] // array-close = %x5D ; ]
@ -405,13 +466,13 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
arrayStart := b arrayStart := b
b = b[1:] b = b[1:]
parent := p.builder.Push(ast.Node{ parent := p.builder.Push(Node{
Kind: ast.Array, Kind: Array,
}) })
first := true first := true
var lastChild ast.Reference var lastChild reference
var err error var err error
for len(b) > 0 { for len(b) > 0 {
@ -421,7 +482,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
} }
if len(b) == 0 { if len(b) == 0 {
return parent, nil, newDecodeError(arrayStart[:1], "array is incomplete") return parent, nil, NewParserError(arrayStart[:1], "array is incomplete")
} }
if b[0] == ']' { if b[0] == ']' {
@ -430,7 +491,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
if b[0] == ',' { if b[0] == ',' {
if first { if first {
return parent, nil, newDecodeError(b[0:1], "array cannot start with comma") return parent, nil, NewParserError(b[0:1], "array cannot start with comma")
} }
b = b[1:] b = b[1:]
@ -439,7 +500,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
return parent, nil, err return parent, nil, err
} }
} else if !first { } else if !first {
return parent, nil, newDecodeError(b[0:1], "array elements must be separated by commas") return parent, nil, NewParserError(b[0:1], "array elements must be separated by commas")
} }
// TOML allows trailing commas in arrays. // TOML allows trailing commas in arrays.
@ -447,7 +508,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
break break
} }
var valueRef ast.Reference var valueRef reference
valueRef, b, err = p.parseVal(b) valueRef, b, err = p.parseVal(b)
if err != nil { if err != nil {
return parent, nil, err return parent, nil, err
@ -472,7 +533,7 @@ func (p *parser) parseValArray(b []byte) (ast.Reference, []byte, error) {
return parent, rest, err return parent, rest, err
} }
func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) { func (p *Parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error) {
for len(b) > 0 { for len(b) > 0 {
var err error var err error
b = p.parseWhitespace(b) b = p.parseWhitespace(b)
@ -501,7 +562,7 @@ func (p *parser) parseOptionalWhitespaceCommentNewline(b []byte) ([]byte, error)
return b, nil return b, nil
} }
func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) { func (p *Parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte, error) {
token, rest, err := scanMultilineLiteralString(b) token, rest, err := scanMultilineLiteralString(b)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
@ -520,7 +581,7 @@ func (p *parser) parseMultilineLiteralString(b []byte) ([]byte, []byte, []byte,
} }
//nolint:funlen,gocognit,cyclop //nolint:funlen,gocognit,cyclop
func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) { func (p *Parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, error) {
// ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body // ml-basic-string = ml-basic-string-delim [ newline ] ml-basic-body
// ml-basic-string-delim // ml-basic-string-delim
// ml-basic-string-delim = 3quotation-mark // ml-basic-string-delim = 3quotation-mark
@ -551,11 +612,11 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
if !escaped { if !escaped {
str := token[startIdx:endIdx] str := token[startIdx:endIdx]
verr := utf8TomlValidAlreadyEscaped(str) verr := characters.Utf8TomlValidAlreadyEscaped(str)
if verr.Zero() { if verr.Zero() {
return token, str, rest, nil return token, str, rest, nil
} }
return nil, nil, nil, newDecodeError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8")
} }
var builder bytes.Buffer var builder bytes.Buffer
@ -635,13 +696,13 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
builder.WriteRune(x) builder.WriteRune(x)
i += 8 i += 8
default: default:
return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c)
} }
i++ i++
} else { } else {
size := utf8ValidNext(token[i:]) size := characters.Utf8ValidNext(token[i:])
if size == 0 { if size == 0 {
return nil, nil, nil, newDecodeError(token[i:i+1], "invalid character %#U", c) return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c)
} }
builder.Write(token[i : i+size]) builder.Write(token[i : i+size])
i += size i += size
@ -651,7 +712,7 @@ func (p *parser) parseMultilineBasicString(b []byte) ([]byte, []byte, []byte, er
return token, builder.Bytes(), rest, nil return token, builder.Bytes(), rest, nil
} }
func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseKey(b []byte) (reference, []byte, error) {
// key = simple-key / dotted-key // key = simple-key / dotted-key
// simple-key = quoted-key / unquoted-key // simple-key = quoted-key / unquoted-key
// //
@ -662,11 +723,11 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
// dot-sep = ws %x2E ws ; . Period // dot-sep = ws %x2E ws ; . Period
raw, key, b, err := p.parseSimpleKey(b) raw, key, b, err := p.parseSimpleKey(b)
if err != nil { if err != nil {
return ast.InvalidReference, nil, err return invalidReference, nil, err
} }
ref := p.builder.Push(ast.Node{ ref := p.builder.Push(Node{
Kind: ast.Key, Kind: Key,
Raw: p.Range(raw), Raw: p.Range(raw),
Data: key, Data: key,
}) })
@ -681,8 +742,8 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
return ref, nil, err return ref, nil, err
} }
p.builder.PushAndChain(ast.Node{ p.builder.PushAndChain(Node{
Kind: ast.Key, Kind: Key,
Raw: p.Range(raw), Raw: p.Range(raw),
Data: key, Data: key,
}) })
@ -694,9 +755,9 @@ func (p *parser) parseKey(b []byte) (ast.Reference, []byte, error) {
return ref, b, nil return ref, b, nil
} }
func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) { func (p *Parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) {
if len(b) == 0 { if len(b) == 0 {
return nil, nil, nil, newDecodeError(b, "expected key but found none") return nil, nil, nil, NewParserError(b, "expected key but found none")
} }
// simple-key = quoted-key / unquoted-key // simple-key = quoted-key / unquoted-key
@ -711,12 +772,12 @@ func (p *parser) parseSimpleKey(b []byte) (raw, key, rest []byte, err error) {
key, rest = scanUnquotedKey(b) key, rest = scanUnquotedKey(b)
return key, key, rest, nil return key, key, rest, nil
default: default:
return nil, nil, nil, newDecodeError(b[0:1], "invalid character at start of key: %c", b[0]) return nil, nil, nil, NewParserError(b[0:1], "invalid character at start of key: %c", b[0])
} }
} }
//nolint:funlen,cyclop //nolint:funlen,cyclop
func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) { func (p *Parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
// basic-string = quotation-mark *basic-char quotation-mark // basic-string = quotation-mark *basic-char quotation-mark
// quotation-mark = %x22 ; " // quotation-mark = %x22 ; "
// basic-char = basic-unescaped / escaped // basic-char = basic-unescaped / escaped
@ -744,11 +805,11 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
// validate the string and return a direct reference to the buffer. // validate the string and return a direct reference to the buffer.
if !escaped { if !escaped {
str := token[startIdx:endIdx] str := token[startIdx:endIdx]
verr := utf8TomlValidAlreadyEscaped(str) verr := characters.Utf8TomlValidAlreadyEscaped(str)
if verr.Zero() { if verr.Zero() {
return token, str, rest, nil return token, str, rest, nil
} }
return nil, nil, nil, newDecodeError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8") return nil, nil, nil, NewParserError(str[verr.Index:verr.Index+verr.Size], "invalid UTF-8")
} }
i := startIdx i := startIdx
@ -795,13 +856,13 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
builder.WriteRune(x) builder.WriteRune(x)
i += 8 i += 8
default: default:
return nil, nil, nil, newDecodeError(token[i:i+1], "invalid escaped character %#U", c) return nil, nil, nil, NewParserError(token[i:i+1], "invalid escaped character %#U", c)
} }
i++ i++
} else { } else {
size := utf8ValidNext(token[i:]) size := characters.Utf8ValidNext(token[i:])
if size == 0 { if size == 0 {
return nil, nil, nil, newDecodeError(token[i:i+1], "invalid character %#U", c) return nil, nil, nil, NewParserError(token[i:i+1], "invalid character %#U", c)
} }
builder.Write(token[i : i+size]) builder.Write(token[i : i+size])
i += size i += size
@ -813,7 +874,7 @@ func (p *parser) parseBasicString(b []byte) ([]byte, []byte, []byte, error) {
func hexToRune(b []byte, length int) (rune, error) { func hexToRune(b []byte, length int) (rune, error) {
if len(b) < length { if len(b) < length {
return -1, newDecodeError(b, "unicode point needs %d character, not %d", length, len(b)) return -1, NewParserError(b, "unicode point needs %d character, not %d", length, len(b))
} }
b = b[:length] b = b[:length]
@ -828,19 +889,19 @@ func hexToRune(b []byte, length int) (rune, error) {
case 'A' <= c && c <= 'F': case 'A' <= c && c <= 'F':
d = uint32(c - 'A' + 10) d = uint32(c - 'A' + 10)
default: default:
return -1, newDecodeError(b[i:i+1], "non-hex character") return -1, NewParserError(b[i:i+1], "non-hex character")
} }
r = r*16 + d r = r*16 + d
} }
if r > unicode.MaxRune || 0xD800 <= r && r < 0xE000 { if r > unicode.MaxRune || 0xD800 <= r && r < 0xE000 {
return -1, newDecodeError(b, "escape sequence is invalid Unicode code point") return -1, NewParserError(b, "escape sequence is invalid Unicode code point")
} }
return rune(r), nil return rune(r), nil
} }
func (p *parser) parseWhitespace(b []byte) []byte { func (p *Parser) parseWhitespace(b []byte) []byte {
// ws = *wschar // ws = *wschar
// wschar = %x20 ; Space // wschar = %x20 ; Space
// wschar =/ %x09 ; Horizontal tab // wschar =/ %x09 ; Horizontal tab
@ -850,24 +911,24 @@ func (p *parser) parseWhitespace(b []byte) []byte {
} }
//nolint:cyclop //nolint:cyclop
func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, error) { func (p *Parser) parseIntOrFloatOrDateTime(b []byte) (reference, []byte, error) {
switch b[0] { switch b[0] {
case 'i': case 'i':
if !scanFollowsInf(b) { if !scanFollowsInf(b) {
return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'inf'") return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'inf'")
} }
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: ast.Float, Kind: Float,
Data: b[:3], Data: b[:3],
}), b[3:], nil }), b[3:], nil
case 'n': case 'n':
if !scanFollowsNan(b) { if !scanFollowsNan(b) {
return ast.InvalidReference, nil, newDecodeError(atmost(b, 3), "expected 'nan'") return invalidReference, nil, NewParserError(atmost(b, 3), "expected 'nan'")
} }
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: ast.Float, Kind: Float,
Data: b[:3], Data: b[:3],
}), b[3:], nil }), b[3:], nil
case '+', '-': case '+', '-':
@ -898,7 +959,7 @@ func (p *parser) parseIntOrFloatOrDateTime(b []byte) (ast.Reference, []byte, err
return p.scanIntOrFloat(b) return p.scanIntOrFloat(b)
} }
func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { func (p *Parser) scanDateTime(b []byte) (reference, []byte, error) {
// scans for contiguous characters in [0-9T:Z.+-], and up to one space if // scans for contiguous characters in [0-9T:Z.+-], and up to one space if
// followed by a digit. // followed by a digit.
hasDate := false hasDate := false
@ -941,30 +1002,30 @@ byteLoop:
} }
} }
var kind ast.Kind var kind Kind
if hasTime { if hasTime {
if hasDate { if hasDate {
if hasTz { if hasTz {
kind = ast.DateTime kind = DateTime
} else { } else {
kind = ast.LocalDateTime kind = LocalDateTime
} }
} else { } else {
kind = ast.LocalTime kind = LocalTime
} }
} else { } else {
kind = ast.LocalDate kind = LocalDate
} }
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: kind, Kind: kind,
Data: b[:i], Data: b[:i],
}), b[i:], nil }), b[i:], nil
} }
//nolint:funlen,gocognit,cyclop //nolint:funlen,gocognit,cyclop
func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) { func (p *Parser) scanIntOrFloat(b []byte) (reference, []byte, error) {
i := 0 i := 0
if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' && b[1] != 'E' { if len(b) > 2 && b[0] == '0' && b[1] != '.' && b[1] != 'e' && b[1] != 'E' {
@ -990,8 +1051,8 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) {
} }
} }
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: ast.Integer, Kind: Integer,
Data: b[:i], Data: b[:i],
}), b[i:], nil }), b[i:], nil
} }
@ -1013,40 +1074,40 @@ func (p *parser) scanIntOrFloat(b []byte) (ast.Reference, []byte, error) {
if c == 'i' { if c == 'i' {
if scanFollowsInf(b[i:]) { if scanFollowsInf(b[i:]) {
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: ast.Float, Kind: Float,
Data: b[:i+3], Data: b[:i+3],
}), b[i+3:], nil }), b[i+3:], nil
} }
return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'i' while scanning for a number") return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'i' while scanning for a number")
} }
if c == 'n' { if c == 'n' {
if scanFollowsNan(b[i:]) { if scanFollowsNan(b[i:]) {
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: ast.Float, Kind: Float,
Data: b[:i+3], Data: b[:i+3],
}), b[i+3:], nil }), b[i+3:], nil
} }
return ast.InvalidReference, nil, newDecodeError(b[i:i+1], "unexpected character 'n' while scanning for a number") return invalidReference, nil, NewParserError(b[i:i+1], "unexpected character 'n' while scanning for a number")
} }
break break
} }
if i == 0 { if i == 0 {
return ast.InvalidReference, b, newDecodeError(b, "incomplete number") return invalidReference, b, NewParserError(b, "incomplete number")
} }
kind := ast.Integer kind := Integer
if isFloat { if isFloat {
kind = ast.Float kind = Float
} }
return p.builder.Push(ast.Node{ return p.builder.Push(Node{
Kind: kind, Kind: kind,
Data: b[:i], Data: b[:i],
}), b[i:], nil }), b[i:], nil
@ -1075,11 +1136,11 @@ func isValidBinaryRune(r byte) bool {
func expect(x byte, b []byte) ([]byte, error) { func expect(x byte, b []byte) ([]byte, error) {
if len(b) == 0 { if len(b) == 0 {
return nil, newDecodeError(b, "expected character %c but the document ended here", x) return nil, NewParserError(b, "expected character %c but the document ended here", x)
} }
if b[0] != x { if b[0] != x {
return nil, newDecodeError(b[0:1], "expected character %c", x) return nil, NewParserError(b[0:1], "expected character %c", x)
} }
return b[1:], nil return b[1:], nil

@ -1,4 +1,6 @@
package toml package unstable
import "github.com/pelletier/go-toml/v2/internal/characters"
func scanFollows(b []byte, pattern string) bool { func scanFollows(b []byte, pattern string) bool {
n := len(pattern) n := len(pattern)
@ -54,16 +56,16 @@ func scanLiteralString(b []byte) ([]byte, []byte, error) {
case '\'': case '\'':
return b[:i+1], b[i+1:], nil return b[:i+1], b[i+1:], nil
case '\n', '\r': case '\n', '\r':
return nil, nil, newDecodeError(b[i:i+1], "literal strings cannot have new lines") return nil, nil, NewParserError(b[i:i+1], "literal strings cannot have new lines")
} }
size := utf8ValidNext(b[i:]) size := characters.Utf8ValidNext(b[i:])
if size == 0 { if size == 0 {
return nil, nil, newDecodeError(b[i:i+1], "invalid character") return nil, nil, NewParserError(b[i:i+1], "invalid character")
} }
i += size i += size
} }
return nil, nil, newDecodeError(b[len(b):], "unterminated literal string") return nil, nil, NewParserError(b[len(b):], "unterminated literal string")
} }
func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) { func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) {
@ -98,39 +100,39 @@ func scanMultilineLiteralString(b []byte) ([]byte, []byte, error) {
i++ i++
if i < len(b) && b[i] == '\'' { if i < len(b) && b[i] == '\'' {
return nil, nil, newDecodeError(b[i-3:i+1], "''' not allowed in multiline literal string") return nil, nil, NewParserError(b[i-3:i+1], "''' not allowed in multiline literal string")
} }
return b[:i], b[i:], nil return b[:i], b[i:], nil
} }
case '\r': case '\r':
if len(b) < i+2 { if len(b) < i+2 {
return nil, nil, newDecodeError(b[len(b):], `need a \n after \r`) return nil, nil, NewParserError(b[len(b):], `need a \n after \r`)
} }
if b[i+1] != '\n' { if b[i+1] != '\n' {
return nil, nil, newDecodeError(b[i:i+2], `need a \n after \r`) return nil, nil, NewParserError(b[i:i+2], `need a \n after \r`)
} }
i += 2 // skip the \n i += 2 // skip the \n
continue continue
} }
size := utf8ValidNext(b[i:]) size := characters.Utf8ValidNext(b[i:])
if size == 0 { if size == 0 {
return nil, nil, newDecodeError(b[i:i+1], "invalid character") return nil, nil, NewParserError(b[i:i+1], "invalid character")
} }
i += size i += size
} }
return nil, nil, newDecodeError(b[len(b):], `multiline literal string not terminated by '''`) return nil, nil, NewParserError(b[len(b):], `multiline literal string not terminated by '''`)
} }
func scanWindowsNewline(b []byte) ([]byte, []byte, error) { func scanWindowsNewline(b []byte) ([]byte, []byte, error) {
const lenCRLF = 2 const lenCRLF = 2
if len(b) < lenCRLF { if len(b) < lenCRLF {
return nil, nil, newDecodeError(b, "windows new line expected") return nil, nil, NewParserError(b, "windows new line expected")
} }
if b[1] != '\n' { if b[1] != '\n' {
return nil, nil, newDecodeError(b, `windows new line should be \r\n`) return nil, nil, NewParserError(b, `windows new line should be \r\n`)
} }
return b[:lenCRLF], b[lenCRLF:], nil return b[:lenCRLF], b[lenCRLF:], nil
@ -165,11 +167,11 @@ func scanComment(b []byte) ([]byte, []byte, error) {
if i+1 < len(b) && b[i+1] == '\n' { if i+1 < len(b) && b[i+1] == '\n' {
return b[:i+1], b[i+1:], nil return b[:i+1], b[i+1:], nil
} }
return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment") return nil, nil, NewParserError(b[i:i+1], "invalid character in comment")
} }
size := utf8ValidNext(b[i:]) size := characters.Utf8ValidNext(b[i:])
if size == 0 { if size == 0 {
return nil, nil, newDecodeError(b[i:i+1], "invalid character in comment") return nil, nil, NewParserError(b[i:i+1], "invalid character in comment")
} }
i += size i += size
@ -192,17 +194,17 @@ func scanBasicString(b []byte) ([]byte, bool, []byte, error) {
case '"': case '"':
return b[:i+1], escaped, b[i+1:], nil return b[:i+1], escaped, b[i+1:], nil
case '\n', '\r': case '\n', '\r':
return nil, escaped, nil, newDecodeError(b[i:i+1], "basic strings cannot have new lines") return nil, escaped, nil, NewParserError(b[i:i+1], "basic strings cannot have new lines")
case '\\': case '\\':
if len(b) < i+2 { if len(b) < i+2 {
return nil, escaped, nil, newDecodeError(b[i:i+1], "need a character after \\") return nil, escaped, nil, NewParserError(b[i:i+1], "need a character after \\")
} }
escaped = true escaped = true
i++ // skip the next character i++ // skip the next character
} }
} }
return nil, escaped, nil, newDecodeError(b[len(b):], `basic string not terminated by "`) return nil, escaped, nil, NewParserError(b[len(b):], `basic string not terminated by "`)
} }
func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) { func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) {
@ -243,27 +245,27 @@ func scanMultilineBasicString(b []byte) ([]byte, bool, []byte, error) {
i++ i++
if i < len(b) && b[i] == '"' { if i < len(b) && b[i] == '"' {
return nil, escaped, nil, newDecodeError(b[i-3:i+1], `""" not allowed in multiline basic string`) return nil, escaped, nil, NewParserError(b[i-3:i+1], `""" not allowed in multiline basic string`)
} }
return b[:i], escaped, b[i:], nil return b[:i], escaped, b[i:], nil
} }
case '\\': case '\\':
if len(b) < i+2 { if len(b) < i+2 {
return nil, escaped, nil, newDecodeError(b[len(b):], "need a character after \\") return nil, escaped, nil, NewParserError(b[len(b):], "need a character after \\")
} }
escaped = true escaped = true
i++ // skip the next character i++ // skip the next character
case '\r': case '\r':
if len(b) < i+2 { if len(b) < i+2 {
return nil, escaped, nil, newDecodeError(b[len(b):], `need a \n after \r`) return nil, escaped, nil, NewParserError(b[len(b):], `need a \n after \r`)
} }
if b[i+1] != '\n' { if b[i+1] != '\n' {
return nil, escaped, nil, newDecodeError(b[i:i+2], `need a \n after \r`) return nil, escaped, nil, NewParserError(b[i:i+2], `need a \n after \r`)
} }
i++ // skip the \n i++ // skip the \n
} }
} }
return nil, escaped, nil, newDecodeError(b[len(b):], `multiline basic string not terminated by """`) return nil, escaped, nil, NewParserError(b[len(b):], `multiline basic string not terminated by """`)
} }

@ -7,4 +7,4 @@ in [ICU](http://icu-project.org/)'s implementation.
## Documentation and Usage ## Documentation and Usage
See [pkgdoc](http://go.pkgdoc.org/github.com/saintfish/chardet) See [pkgdoc](https://pkg.go.dev/github.com/saintfish/chardet)

@ -38,3 +38,37 @@ func (api *Client) SendAuthRevokeContext(ctx context.Context, token string) (*Au
return api.authRequest(ctx, "auth.revoke", values) return api.authRequest(ctx, "auth.revoke", values)
} }
type listTeamsResponse struct {
Teams []Team `json:"teams"`
SlackResponse
}
type ListTeamsParameters struct {
Limit int
Cursor string
}
// ListTeams returns all workspaces a token can access.
// More info: https://api.slack.com/methods/admin.teams.list
func (api *Client) ListTeams(params ListTeamsParameters) ([]Team, string, error) {
return api.ListTeamsContext(context.Background(), params)
}
// ListTeams returns all workspaces a token can access with a custom context.
func (api *Client) ListTeamsContext(ctx context.Context, params ListTeamsParameters) ([]Team, string, error) {
values := url.Values{
"token": {api.token},
}
if params.Cursor != "" {
values.Add("cursor", params.Cursor)
}
response := &listTeamsResponse{}
err := api.postMethod(ctx, "auth.teams.list", values, response)
if err != nil {
return nil, "", err
}
return response.Teams, response.ResponseMetadata.Cursor, response.Err()
}

@ -112,6 +112,10 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error {
e = &TimePickerBlockElement{} e = &TimePickerBlockElement{}
case "plain_text_input": case "plain_text_input":
e = &PlainTextInputBlockElement{} e = &PlainTextInputBlockElement{}
case "email_text_input":
e = &EmailTextInputBlockElement{}
case "url_text_input":
e = &URLTextInputBlockElement{}
case "static_select", "external_select", "users_select", "conversations_select", "channels_select": case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
e = &SelectBlockElement{} e = &SelectBlockElement{}
case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select": case "multi_static_select", "multi_external_select", "multi_users_select", "multi_conversations_select", "multi_channels_select":
@ -122,6 +126,8 @@ func (b *InputBlock) UnmarshalJSON(data []byte) error {
e = &OverflowBlockElement{} e = &OverflowBlockElement{}
case "radio_buttons": case "radio_buttons":
e = &RadioButtonsBlockElement{} e = &RadioButtonsBlockElement{}
case "number_input":
e = &NumberInputBlockElement{}
default: default:
return errors.New("unsupported block element type") return errors.New("unsupported block element type")
} }
@ -186,12 +192,18 @@ func (b *BlockElements) UnmarshalJSON(data []byte) error {
blockElement = &TimePickerBlockElement{} blockElement = &TimePickerBlockElement{}
case "plain_text_input": case "plain_text_input":
blockElement = &PlainTextInputBlockElement{} blockElement = &PlainTextInputBlockElement{}
case "email_text_input":
blockElement = &EmailTextInputBlockElement{}
case "url_text_input":
blockElement = &URLTextInputBlockElement{}
case "checkboxes": case "checkboxes":
blockElement = &CheckboxGroupsBlockElement{} blockElement = &CheckboxGroupsBlockElement{}
case "radio_buttons": case "radio_buttons":
blockElement = &RadioButtonsBlockElement{} blockElement = &RadioButtonsBlockElement{}
case "static_select", "external_select", "users_select", "conversations_select", "channels_select": case "static_select", "external_select", "users_select", "conversations_select", "channels_select":
blockElement = &SelectBlockElement{} blockElement = &SelectBlockElement{}
case "number_input":
blockElement = &NumberInputBlockElement{}
default: default:
return fmt.Errorf("unsupported block element type %v", blockElementType) return fmt.Errorf("unsupported block element type %v", blockElementType)
} }

@ -11,6 +11,9 @@ const (
METTimepicker MessageElementType = "timepicker" METTimepicker MessageElementType = "timepicker"
METPlainTextInput MessageElementType = "plain_text_input" METPlainTextInput MessageElementType = "plain_text_input"
METRadioButtons MessageElementType = "radio_buttons" METRadioButtons MessageElementType = "radio_buttons"
METEmailTextInput MessageElementType = "email_text_input"
METURLTextInput MessageElementType = "url_text_input"
METNumber MessageElementType = "number_input"
MixedElementImage MixedElementType = "mixed_image" MixedElementImage MixedElementType = "mixed_image"
MixedElementText MixedElementType = "mixed_text" MixedElementText MixedElementType = "mixed_text"
@ -389,6 +392,64 @@ func NewTimePickerBlockElement(actionID string) *TimePickerBlockElement {
} }
} }
// EmailTextInputBlockElement creates a field where a user can enter email
// data.
// email-text-input elements are currently only available in modals.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#email
type EmailTextInputBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
InitialValue string `json:"initial_value,omitempty"`
DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
FocusOnLoad bool `json:"focus_on_load,omitempty"`
}
// ElementType returns the type of the Element
func (s EmailTextInputBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewEmailTextInputBlockElement returns an instance of a plain-text input
// element
func NewEmailTextInputBlockElement(placeholder *TextBlockObject, actionID string) *EmailTextInputBlockElement {
return &EmailTextInputBlockElement{
Type: METEmailTextInput,
ActionID: actionID,
Placeholder: placeholder,
}
}
// URLTextInputBlockElement creates a field where a user can enter url data.
//
// url-text-input elements are currently only available in modals.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#url
type URLTextInputBlockElement struct {
Type MessageElementType `json:"type"`
ActionID string `json:"action_id,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
InitialValue string `json:"initial_value,omitempty"`
DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
FocusOnLoad bool `json:"focus_on_load,omitempty"`
}
// ElementType returns the type of the Element
func (s URLTextInputBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewURLTextInputBlockElement returns an instance of a plain-text input
// element
func NewURLTextInputBlockElement(placeholder *TextBlockObject, actionID string) *URLTextInputBlockElement {
return &URLTextInputBlockElement{
Type: METURLTextInput,
ActionID: actionID,
Placeholder: placeholder,
}
}
// PlainTextInputBlockElement creates a field where a user can enter freeform // PlainTextInputBlockElement creates a field where a user can enter freeform
// data. // data.
// Plain-text input elements are currently only available in modals. // Plain-text input elements are currently only available in modals.
@ -475,3 +536,34 @@ func NewRadioButtonsBlockElement(actionID string, options ...*OptionBlockObject)
Options: options, Options: options,
} }
} }
// NumberInputBlockElement creates a field where a user can enter number
// data.
// Number input elements are currently only available in modals.
//
// More Information: https://api.slack.com/reference/block-kit/block-elements#number
type NumberInputBlockElement struct {
Type MessageElementType `json:"type"`
IsDecimalAllowed bool `json:"is_decimal_allowed"`
ActionID string `json:"action_id,omitempty"`
Placeholder *TextBlockObject `json:"placeholder,omitempty"`
InitialValue string `json:"initial_value,omitempty"`
MinValue string `json:"min_value,omitempty"`
MaxValue string `json:"max_value,omitempty"`
DispatchActionConfig *DispatchActionConfig `json:"dispatch_action_config,omitempty"`
}
// ElementType returns the type of the Element
func (s NumberInputBlockElement) ElementType() MessageElementType {
return s.Type
}
// NewNumberInputBlockElement returns an instance of a number input element
func NewNumberInputBlockElement(placeholder *TextBlockObject, actionID string, isDecimalAllowed bool) *NumberInputBlockElement {
return &NumberInputBlockElement{
Type: METNumber,
ActionID: actionID,
Placeholder: placeholder,
IsDecimalAllowed: isDecimalAllowed,
}
}

@ -327,17 +327,17 @@ func NewRichTextSectionUserGroupElement(usergroupID string) *RichTextSectionUser
type RichTextSectionDateElement struct { type RichTextSectionDateElement struct {
Type RichTextSectionElementType `json:"type"` Type RichTextSectionElementType `json:"type"`
Timestamp string `json:"timestamp"` Timestamp JSONTime `json:"timestamp"`
} }
func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType { func (r RichTextSectionDateElement) RichTextSectionElementType() RichTextSectionElementType {
return r.Type return r.Type
} }
func NewRichTextSectionDateElement(timestamp string) *RichTextSectionDateElement { func NewRichTextSectionDateElement(timestamp int64) *RichTextSectionDateElement {
return &RichTextSectionDateElement{ return &RichTextSectionDateElement{
Type: RTSEDate, Type: RTSEDate,
Timestamp: timestamp, Timestamp: JSONTime(timestamp),
} }
} }

@ -2,6 +2,7 @@ package slack
import ( import (
"context" "context"
"errors"
"net/url" "net/url"
"strconv" "strconv"
"strings" "strings"
@ -71,6 +72,7 @@ type GetConversationsForUserParameters struct {
Types []string Types []string
Limit int Limit int
ExcludeArchived bool ExcludeArchived bool
TeamID string
} }
type responseMetaData struct { type responseMetaData struct {
@ -137,6 +139,10 @@ func (api *Client) GetConversationsForUserContext(ctx context.Context, params *G
if params.ExcludeArchived { if params.ExcludeArchived {
values.Add("exclude_archived", "true") values.Add("exclude_archived", "true")
} }
if params.TeamID != "" {
values.Add("team_id", params.TeamID)
}
response := struct { response := struct {
Channels []Channel `json:"channels"` Channels []Channel `json:"channels"`
ResponseMetaData responseMetaData `json:"response_metadata"` ResponseMetaData responseMetaData `json:"response_metadata"`
@ -337,17 +343,26 @@ func (api *Client) CloseConversationContext(ctx context.Context, channelID strin
return response.NoOp, response.AlreadyClosed, response.Err() return response.NoOp, response.AlreadyClosed, response.Err()
} }
type CreateConversationParams struct {
ChannelName string
IsPrivate bool
TeamID string
}
// CreateConversation initiates a public or private channel-based conversation // CreateConversation initiates a public or private channel-based conversation
func (api *Client) CreateConversation(channelName string, isPrivate bool) (*Channel, error) { func (api *Client) CreateConversation(params CreateConversationParams) (*Channel, error) {
return api.CreateConversationContext(context.Background(), channelName, isPrivate) return api.CreateConversationContext(context.Background(), params)
} }
// CreateConversationContext initiates a public or private channel-based conversation with a custom context // CreateConversationContext initiates a public or private channel-based conversation with a custom context
func (api *Client) CreateConversationContext(ctx context.Context, channelName string, isPrivate bool) (*Channel, error) { func (api *Client) CreateConversationContext(ctx context.Context, params CreateConversationParams) (*Channel, error) {
values := url.Values{ values := url.Values{
"token": {api.token}, "token": {api.token},
"name": {channelName}, "name": {params.ChannelName},
"is_private": {strconv.FormatBool(isPrivate)}, "is_private": {strconv.FormatBool(params.IsPrivate)},
}
if params.TeamID != "" {
values.Set("team_id", params.TeamID)
} }
response, err := api.channelRequest(ctx, "conversations.create", values) response, err := api.channelRequest(ctx, "conversations.create", values)
if err != nil { if err != nil {
@ -357,17 +372,33 @@ func (api *Client) CreateConversationContext(ctx context.Context, channelName st
return &response.Channel, nil return &response.Channel, nil
} }
// GetConversationInfoInput Defines the parameters of a GetConversationInfo and GetConversationInfoContext function
type GetConversationInfoInput struct {
ChannelID string
IncludeLocale bool
IncludeNumMembers bool
}
// GetConversationInfo retrieves information about a conversation // GetConversationInfo retrieves information about a conversation
func (api *Client) GetConversationInfo(channelID string, includeLocale bool) (*Channel, error) { func (api *Client) GetConversationInfo(input *GetConversationInfoInput) (*Channel, error) {
return api.GetConversationInfoContext(context.Background(), channelID, includeLocale) return api.GetConversationInfoContext(context.Background(), input)
} }
// GetConversationInfoContext retrieves information about a conversation with a custom context // GetConversationInfoContext retrieves information about a conversation with a custom context
func (api *Client) GetConversationInfoContext(ctx context.Context, channelID string, includeLocale bool) (*Channel, error) { func (api *Client) GetConversationInfoContext(ctx context.Context, input *GetConversationInfoInput) (*Channel, error) {
if input == nil {
return nil, errors.New("GetConversationInfoInput must not be nil")
}
if input.ChannelID == "" {
return nil, errors.New("ChannelID must be defined")
}
values := url.Values{ values := url.Values{
"token": {api.token}, "token": {api.token},
"channel": {channelID}, "channel": {input.ChannelID},
"include_locale": {strconv.FormatBool(includeLocale)}, "include_locale": {strconv.FormatBool(input.IncludeLocale)},
"include_num_members": {strconv.FormatBool(input.IncludeNumMembers)},
} }
response, err := api.channelRequest(ctx, "conversations.info", values) response, err := api.channelRequest(ctx, "conversations.info", values)
if err != nil { if err != nil {
@ -398,13 +429,14 @@ func (api *Client) LeaveConversationContext(ctx context.Context, channelID strin
} }
type GetConversationRepliesParameters struct { type GetConversationRepliesParameters struct {
ChannelID string ChannelID string
Timestamp string Timestamp string
Cursor string Cursor string
Inclusive bool Inclusive bool
Latest string Latest string
Limit int Limit int
Oldest string Oldest string
IncludeAllMetadata bool
} }
// GetConversationReplies retrieves a thread of messages posted to a conversation // GetConversationReplies retrieves a thread of messages posted to a conversation
@ -436,6 +468,11 @@ func (api *Client) GetConversationRepliesContext(ctx context.Context, params *Ge
} else { } else {
values.Add("inclusive", "0") values.Add("inclusive", "0")
} }
if params.IncludeAllMetadata {
values.Add("include_all_metadata", "1")
} else {
values.Add("include_all_metadata", "0")
}
response := struct { response := struct {
SlackResponse SlackResponse
HasMore bool `json:"has_more"` HasMore bool `json:"has_more"`

@ -2,6 +2,7 @@ package slack
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"net/url" "net/url"
@ -145,6 +146,58 @@ type ListFilesParameters struct {
Cursor string Cursor string
} }
type UploadFileV2Parameters struct {
File string
FileSize int
Content string
Reader io.Reader
Filename string
Title string
InitialComment string
Channel string
ThreadTimestamp string
AltTxt string
SnippetText string
}
type getUploadURLExternalParameters struct {
altText string
fileSize int
fileName string
snippetText string
}
type getUploadURLExternalResponse struct {
UploadURL string `json:"upload_url"`
FileID string `json:"file_id"`
SlackResponse
}
type uploadToURLParameters struct {
UploadURL string
Reader io.Reader
File string
Content string
Filename string
}
type FileSummary struct {
ID string `json:"id"`
Title string `json:"title"`
}
type completeUploadExternalParameters struct {
title string
channel string
initialComment string
threadTimestamp string
}
type completeUploadExternalResponse struct {
SlackResponse
Files []FileSummary `json:"files"`
}
type fileResponseFull struct { type fileResponseFull struct {
File `json:"file"` File `json:"file"`
Paging `json:"paging"` Paging `json:"paging"`
@ -416,3 +469,129 @@ func (api *Client) ShareFilePublicURLContext(ctx context.Context, fileID string)
} }
return &response.File, response.Comments, &response.Paging, nil return &response.File, response.Comments, &response.Paging, nil
} }
// getUploadURLExternal gets a URL and fileID from slack which can later be used to upload a file
func (api *Client) getUploadURLExternal(ctx context.Context, params getUploadURLExternalParameters) (*getUploadURLExternalResponse, error) {
values := url.Values{
"token": {api.token},
"filename": {params.fileName},
"length": {strconv.Itoa(params.fileSize)},
}
if params.altText != "" {
values.Add("initial_comment", params.altText)
}
if params.snippetText != "" {
values.Add("thread_ts", params.snippetText)
}
response := &getUploadURLExternalResponse{}
err := api.postMethod(ctx, "files.getUploadURLExternal", values, response)
if err != nil {
return nil, err
}
return response, response.Err()
}
// uploadToURL uploads the file to the provided URL using post method
func (api *Client) uploadToURL(ctx context.Context, params uploadToURLParameters) (err error) {
values := url.Values{}
if params.Content != "" {
values.Add("content", params.Content)
values.Add("token", api.token)
err = postForm(ctx, api.httpclient, params.UploadURL, values, nil, api)
} else if params.File != "" {
err = postLocalWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.File, "file", api.token, values, nil, api)
} else if params.Reader != nil {
err = postWithMultipartResponse(ctx, api.httpclient, params.UploadURL, params.Filename, "file", api.token, values, params.Reader, nil, api)
}
return err
}
// completeUploadExternal once files are uploaded, this completes the upload and shares it to the specified channel
func (api *Client) completeUploadExternal(ctx context.Context, fileID string, params completeUploadExternalParameters) (file *completeUploadExternalResponse, err error) {
request := []FileSummary{{ID: fileID, Title: params.title}}
requestBytes, err := json.Marshal(request)
if err != nil {
return nil, err
}
values := url.Values{
"token": {api.token},
"files": {string(requestBytes)},
"channel_id": {params.channel},
}
if params.initialComment != "" {
values.Add("initial_comment", params.initialComment)
}
if params.threadTimestamp != "" {
values.Add("thread_ts", params.threadTimestamp)
}
response := &completeUploadExternalResponse{}
err = api.postMethod(ctx, "files.completeUploadExternal", values, response)
if err != nil {
return nil, err
}
if response.Err() != nil {
return nil, response.Err()
}
return response, nil
}
// UploadFileV2 uploads file to a given slack channel using 3 steps -
// 1. Get an upload URL using files.getUploadURLExternal API
// 2. Send the file as a post to the URL provided by slack
// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal
func (api *Client) UploadFileV2(params UploadFileV2Parameters) (*FileSummary, error) {
return api.UploadFileV2Context(context.Background(), params)
}
// UploadFileV2 uploads file to a given slack channel using 3 steps with a custom context -
// 1. Get an upload URL using files.getUploadURLExternal API
// 2. Send the file as a post to the URL provided by slack
// 3. Complete the upload and share it to the specified channel using files.completeUploadExternal
func (api *Client) UploadFileV2Context(ctx context.Context, params UploadFileV2Parameters) (file *FileSummary, err error) {
if params.Filename == "" {
return nil, fmt.Errorf("file.upload.v2: filename cannot be empty")
}
if params.FileSize == 0 {
return nil, fmt.Errorf("file.upload.v2: file size cannot be 0")
}
if params.Channel == "" {
return nil, fmt.Errorf("file.upload.v2: channel cannot be empty")
}
u, err := api.getUploadURLExternal(ctx, getUploadURLExternalParameters{
altText: params.AltTxt,
fileName: params.Filename,
fileSize: params.FileSize,
snippetText: params.SnippetText,
})
if err != nil {
return nil, err
}
err = api.uploadToURL(ctx, uploadToURLParameters{
UploadURL: u.UploadURL,
Reader: params.Reader,
File: params.File,
Content: params.Content,
Filename: params.Filename,
})
if err != nil {
return nil, err
}
c, err := api.completeUploadExternal(ctx, u.FileID, completeUploadExternalParameters{
title: params.Title,
channel: params.Channel,
initialComment: params.InitialComment,
threadTimestamp: params.ThreadTimestamp,
})
if err != nil {
return nil, err
}
if len(c.Files) != 1 {
return nil, fmt.Errorf("file.upload.v2: something went wrong; received %d files instead of 1", len(c.Files))
}
return &c.Files[0], nil
}

@ -307,6 +307,9 @@ type responseParser func(*http.Response) error
func newJSONParser(dst interface{}) responseParser { func newJSONParser(dst interface{}) responseParser {
return func(resp *http.Response) error { return func(resp *http.Response) error {
if dst == nil {
return nil
}
return json.NewDecoder(resp.Body).Decode(dst) return json.NewDecoder(resp.Body).Decode(dst)
} }
} }

@ -24,6 +24,26 @@ type TeamInfo struct {
Icon map[string]interface{} `json:"icon"` Icon map[string]interface{} `json:"icon"`
} }
type TeamProfileResponse struct {
Profile TeamProfile `json:"profile"`
SlackResponse
}
type TeamProfile struct {
Fields []TeamProfileField `json:"fields"`
}
type TeamProfileField struct {
ID string `json:"id"`
Ordering int `json:"ordering"`
Label string `json:"label"`
Hint string `json:"hint"`
Type string `json:"type"`
PossibleValues []string `json:"possible_values"`
IsHidden bool `json:"is_hidden"`
Options map[string]bool `json:"options"`
}
type LoginResponse struct { type LoginResponse struct {
Logins []Login `json:"logins"` Logins []Login `json:"logins"`
Paging `json:"paging"` Paging `json:"paging"`
@ -95,11 +115,41 @@ func (api *Client) accessLogsRequest(ctx context.Context, path string, values ur
return response, response.Err() return response, response.Err()
} }
func (api *Client) teamProfileRequest(ctx context.Context, client httpClient, path string, values url.Values) (*TeamProfileResponse, error) {
response := &TeamProfileResponse{}
err := api.postMethod(ctx, path, values, response)
if err != nil {
return nil, err
}
return response, response.Err()
}
// GetTeamInfo gets the Team Information of the user // GetTeamInfo gets the Team Information of the user
func (api *Client) GetTeamInfo() (*TeamInfo, error) { func (api *Client) GetTeamInfo() (*TeamInfo, error) {
return api.GetTeamInfoContext(context.Background()) return api.GetTeamInfoContext(context.Background())
} }
// GetOtherTeamInfoContext gets Team information for any team with a custom context
func (api *Client) GetOtherTeamInfoContext(ctx context.Context, team string) (*TeamInfo, error) {
if team == "" {
return api.GetTeamInfoContext(ctx)
}
values := url.Values{
"token": {api.token},
}
values.Add("team", team)
response, err := api.teamRequest(ctx, "team.info", values)
if err != nil {
return nil, err
}
return &response.Team, nil
}
// GetOtherTeamInfo gets Team information for any team
func (api *Client) GetOtherTeamInfo(team string) (*TeamInfo, error) {
return api.GetOtherTeamInfoContext(context.Background(), team)
}
// GetTeamInfoContext gets the Team Information of the user with a custom context // GetTeamInfoContext gets the Team Information of the user with a custom context
func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) { func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
values := url.Values{ values := url.Values{
@ -113,6 +163,25 @@ func (api *Client) GetTeamInfoContext(ctx context.Context) (*TeamInfo, error) {
return &response.Team, nil return &response.Team, nil
} }
// GetTeamProfile gets the Team Profile settings of the user
func (api *Client) GetTeamProfile() (*TeamProfile, error) {
return api.GetTeamProfileContext(context.Background())
}
// GetTeamProfileContext gets the Team Profile settings of the user with a custom context
func (api *Client) GetTeamProfileContext(ctx context.Context) (*TeamProfile, error) {
values := url.Values{
"token": {api.token},
}
response, err := api.teamProfileRequest(ctx, api.httpclient, "team.profile.get", values)
if err != nil {
return nil, err
}
return &response.Profile, nil
}
// GetAccessLogs retrieves a page of logins according to the parameters given // GetAccessLogs retrieves a page of logins according to the parameters given
func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) { func (api *Client) GetAccessLogs(params AccessLogParameters) ([]Login, *Paging, error) {
return api.GetAccessLogsContext(context.Background(), params) return api.GetAccessLogsContext(context.Background(), params)

@ -17,30 +17,38 @@ const (
// UserProfile contains all the information details of a given user // UserProfile contains all the information details of a given user
type UserProfile struct { type UserProfile struct {
FirstName string `json:"first_name"` FirstName string `json:"first_name"`
LastName string `json:"last_name"` LastName string `json:"last_name"`
RealName string `json:"real_name"` RealName string `json:"real_name"`
RealNameNormalized string `json:"real_name_normalized"` RealNameNormalized string `json:"real_name_normalized"`
DisplayName string `json:"display_name"` DisplayName string `json:"display_name"`
DisplayNameNormalized string `json:"display_name_normalized"` DisplayNameNormalized string `json:"display_name_normalized"`
Email string `json:"email"` Email string `json:"email"`
Skype string `json:"skype"` Skype string `json:"skype"`
Phone string `json:"phone"` Phone string `json:"phone"`
Image24 string `json:"image_24"` Image24 string `json:"image_24"`
Image32 string `json:"image_32"` Image32 string `json:"image_32"`
Image48 string `json:"image_48"` Image48 string `json:"image_48"`
Image72 string `json:"image_72"` Image72 string `json:"image_72"`
Image192 string `json:"image_192"` Image192 string `json:"image_192"`
Image512 string `json:"image_512"` Image512 string `json:"image_512"`
ImageOriginal string `json:"image_original"` ImageOriginal string `json:"image_original"`
Title string `json:"title"` Title string `json:"title"`
BotID string `json:"bot_id,omitempty"` BotID string `json:"bot_id,omitempty"`
ApiAppID string `json:"api_app_id,omitempty"` ApiAppID string `json:"api_app_id,omitempty"`
StatusText string `json:"status_text,omitempty"` StatusText string `json:"status_text,omitempty"`
StatusEmoji string `json:"status_emoji,omitempty"` StatusEmoji string `json:"status_emoji,omitempty"`
StatusExpiration int `json:"status_expiration"` StatusEmojiDisplayInfo []UserProfileStatusEmojiDisplayInfo `json:"status_emoji_display_info,omitempty"`
Team string `json:"team"` StatusExpiration int `json:"status_expiration"`
Fields UserProfileCustomFields `json:"fields"` Team string `json:"team"`
Fields UserProfileCustomFields `json:"fields"`
}
type UserProfileStatusEmojiDisplayInfo struct {
EmojiName string `json:"emoji_name"`
DisplayAlias string `json:"display_alias,omitempty"`
DisplayURL string `json:"display_url,omitempty"`
Unicode string `json:"unicode,omitempty"`
} }
// UserProfileCustomFields represents user profile's custom fields. // UserProfileCustomFields represents user profile's custom fields.
@ -556,6 +564,55 @@ func (api *Client) SetUserRealNameContextWithUser(ctx context.Context, user, rea
return response.Err() return response.Err()
} }
// SetUserCustomFields sets Custom Profile fields on the provided users account. Due to the non-repeating elements
// within the request, a map fields is required. The key in the map signifies the field that will be updated.
//
// Note: You may need to change the way the custom field is populated within the Profile section of the Admin Console from
// SCIM or User Entered to API.
//
// See GetTeamProfile for information to retrieve possible fields for your account.
func (api *Client) SetUserCustomFields(userID string, customFields map[string]UserProfileCustomField) error {
return api.SetUserCustomFieldsContext(context.Background(), userID, customFields)
}
// SetUserCustomFieldsContext will set a users custom profile field with context.
//
// For more information see SetUserCustomFields
func (api *Client) SetUserCustomFieldsContext(ctx context.Context, userID string, customFields map[string]UserProfileCustomField) error {
// Convert data to data type with custom marshall / unmarshall
// For more information, see UserProfileCustomFields definition.
updateFields := UserProfileCustomFields{}
updateFields.SetMap(customFields)
// This anonymous struct is needed to set the fields level of the request data. The base struct for
// UserProfileCustomFields has an unexported variable named fields that does not contain a struct tag,
// which has resulted in this configuration.
profile, err := json.Marshal(&struct {
Fields UserProfileCustomFields `json:"fields"`
}{
Fields: updateFields,
})
if err != nil {
return err
}
values := url.Values{
"token": {api.token},
"user": {userID},
"profile": {string(profile)},
}
response := &userResponseFull{}
if err := postForm(ctx, api.httpclient, APIURL+"users.profile.set", values, response, api); err != nil {
return err
}
return response.Err()
}
// SetUserCustomStatus will set a custom status and emoji for the currently // SetUserCustomStatus will set a custom status and emoji for the currently
// authenticated user. If statusEmoji is "" and statusText is not, the Slack API // authenticated user. If statusEmoji is "" and statusText is not, the Slack API
// will automatically set it to ":speech_balloon:". Otherwise, if both are "" // will automatically set it to ":speech_balloon:". Otherwise, if both are ""

@ -21,8 +21,8 @@ type WebhookMessage struct {
Parse string `json:"parse,omitempty"` Parse string `json:"parse,omitempty"`
Blocks *Blocks `json:"blocks,omitempty"` Blocks *Blocks `json:"blocks,omitempty"`
ResponseType string `json:"response_type,omitempty"` ResponseType string `json:"response_type,omitempty"`
ReplaceOriginal bool `json:"replace_original,omitempty"` ReplaceOriginal bool `json:"replace_original"`
DeleteOriginal bool `json:"delete_original,omitempty"` DeleteOriginal bool `json:"delete_original"`
ReplyBroadcast bool `json:"reply_broadcast,omitempty"` ReplyBroadcast bool `json:"reply_broadcast,omitempty"`
} }

@ -1,7 +1,7 @@
package slack package slack
// reactionItem is a lighter-weight item than is returned by the reactions list. // ReactionItem is a lighter-weight item than is returned by the reactions list.
type reactionItem struct { type ReactionItem struct {
Type string `json:"type"` Type string `json:"type"`
Channel string `json:"channel,omitempty"` Channel string `json:"channel,omitempty"`
File string `json:"file,omitempty"` File string `json:"file,omitempty"`
@ -9,17 +9,17 @@ type reactionItem struct {
Timestamp string `json:"ts,omitempty"` Timestamp string `json:"ts,omitempty"`
} }
type reactionEvent struct { type ReactionEvent struct {
Type string `json:"type"` Type string `json:"type"`
User string `json:"user"` User string `json:"user"`
ItemUser string `json:"item_user"` ItemUser string `json:"item_user"`
Item reactionItem `json:"item"` Item ReactionItem `json:"item"`
Reaction string `json:"reaction"` Reaction string `json:"reaction"`
EventTimestamp string `json:"event_ts"` EventTimestamp string `json:"event_ts"`
} }
// ReactionAddedEvent represents the Reaction added event // ReactionAddedEvent represents the Reaction added event
type ReactionAddedEvent reactionEvent type ReactionAddedEvent ReactionEvent
// ReactionRemovedEvent represents the Reaction removed event // ReactionRemovedEvent represents the Reaction removed event
type ReactionRemovedEvent reactionEvent type ReactionRemovedEvent ReactionEvent

@ -19,10 +19,10 @@ type (
} }
) )
type WorkflowStepCompletedRequestOption func(opt WorkflowStepCompletedRequest) error type WorkflowStepCompletedRequestOption func(opt *WorkflowStepCompletedRequest) error
func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption { func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) WorkflowStepCompletedRequestOption {
return func(opt WorkflowStepCompletedRequest) error { return func(opt *WorkflowStepCompletedRequest) error {
if len(outputs) > 0 { if len(outputs) > 0 {
opt.Outputs = outputs opt.Outputs = outputs
} }
@ -33,7 +33,7 @@ func WorkflowStepCompletedRequestOptionOutput(outputs map[string]string) Workflo
// WorkflowStepCompleted indicates step is completed // WorkflowStepCompleted indicates step is completed
func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error { func (api *Client) WorkflowStepCompleted(workflowStepExecuteID string, options ...WorkflowStepCompletedRequestOption) error {
// More information: https://api.slack.com/methods/workflows.stepCompleted // More information: https://api.slack.com/methods/workflows.stepCompleted
r := WorkflowStepCompletedRequest{ r := &WorkflowStepCompletedRequest{
WorkflowStepExecuteID: workflowStepExecuteID, WorkflowStepExecuteID: workflowStepExecuteID,
} }
for _, option := range options { for _, option := range options {

@ -1,26 +0,0 @@
sudo: false
language: go
arch:
- amd64
- ppc64e
go:
- "1.14"
- "1.15"
- "1.16"
- tip
os:
- linux
- osx
matrix:
allow_failures:
- go: tip
fast_finish: true
script:
- go build -v ./...
- go test -count=1 -cover -race -v ./...
- go vet ./...
- FILES=$(gofmt -s -l . zipfs sftpfs mem tarfs); if [[ -n "${FILES}" ]]; then echo "You have go format errors; gofmt your changes"; exit 1; fi

@ -2,7 +2,7 @@
A FileSystem Abstraction System for Go A FileSystem Abstraction System for Go
[![Build Status](https://travis-ci.org/spf13/afero.svg)](https://travis-ci.org/spf13/afero) [![Build status](https://ci.appveyor.com/api/projects/status/github/spf13/afero?branch=master&svg=true)](https://ci.appveyor.com/project/spf13/afero) [![GoDoc](https://godoc.org/github.com/spf13/afero?status.svg)](https://godoc.org/github.com/spf13/afero) [![Join the chat at https://gitter.im/spf13/afero](https://badges.gitter.im/Dev%20Chat.svg)](https://gitter.im/spf13/afero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Test](https://github.com/spf13/afero/actions/workflows/test.yml/badge.svg)](https://github.com/spf13/afero/actions/workflows/test.yml) [![GoDoc](https://godoc.org/github.com/spf13/afero?status.svg)](https://godoc.org/github.com/spf13/afero) [![Join the chat at https://gitter.im/spf13/afero](https://badges.gitter.im/Dev%20Chat.svg)](https://gitter.im/spf13/afero?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Overview # Overview

@ -103,8 +103,8 @@ type Fs interface {
var ( var (
ErrFileClosed = errors.New("File is closed") ErrFileClosed = errors.New("File is closed")
ErrOutOfRange = errors.New("Out of range") ErrOutOfRange = errors.New("out of range")
ErrTooLarge = errors.New("Too large") ErrTooLarge = errors.New("too large")
ErrFileNotFound = os.ErrNotExist ErrFileNotFound = os.ErrNotExist
ErrFileExists = os.ErrExist ErrFileExists = os.ErrExist
ErrDestinationExists = os.ErrExist ErrDestinationExists = os.ErrExist

@ -1,3 +1,5 @@
# This currently does nothing. We have moved to GitHub action, but this is kept
# until spf13 has disabled this project in AppVeyor.
version: '{build}' version: '{build}'
clone_folder: C:\gopath\src\github.com\spf13\afero clone_folder: C:\gopath\src\github.com\spf13\afero
environment: environment:
@ -6,10 +8,3 @@ build_script:
- cmd: >- - cmd: >-
go version go version
go env
go get -v github.com/spf13/afero/...
go build -v github.com/spf13/afero/...
test_script:
- cmd: go test -count=1 -cover -race -v github.com/spf13/afero/...

@ -1,6 +1,7 @@
package afero package afero
import ( import (
"io/fs"
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
@ -8,7 +9,10 @@ import (
"time" "time"
) )
var _ Lstater = (*BasePathFs)(nil) var (
_ Lstater = (*BasePathFs)(nil)
_ fs.ReadDirFile = (*BasePathFile)(nil)
)
// The BasePathFs restricts all operations to a given path within an Fs. // The BasePathFs restricts all operations to a given path within an Fs.
// The given file name to the operations on this Fs will be prepended with // The given file name to the operations on this Fs will be prepended with
@ -33,6 +37,14 @@ func (f *BasePathFile) Name() string {
return strings.TrimPrefix(sourcename, filepath.Clean(f.path)) return strings.TrimPrefix(sourcename, filepath.Clean(f.path))
} }
func (f *BasePathFile) ReadDir(n int) ([]fs.DirEntry, error) {
if rdf, ok := f.File.(fs.ReadDirFile); ok {
return rdf.ReadDir(n)
}
return readDirFile{f.File}.ReadDir(n)
}
func NewBasePathFs(source Fs, path string) Fs { func NewBasePathFs(source Fs, path string) Fs {
return &BasePathFs{source: source, path: path} return &BasePathFs{source: source, path: path}
} }

@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:build aix || darwin || openbsd || freebsd || netbsd || dragonfly
// +build aix darwin openbsd freebsd netbsd dragonfly // +build aix darwin openbsd freebsd netbsd dragonfly
package afero package afero

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save