Initial commit
This commit is contained in:
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
.idea/**
|
||||||
|
.vscode/**
|
||||||
|
.vstags
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
|
**/*.bak
|
||||||
|
**/*.tmp
|
||||||
|
data/config.json
|
||||||
|
data/whitelist.json
|
||||||
|
data/blacklist.json
|
||||||
|
buny-jabber-bot
|
||||||
51
Design.txt
Normal file
51
Design.txt
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
Oсобенности реализации клиентской части.
|
||||||
|
|
||||||
|
Общие положения
|
||||||
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Nick для room-ов един и не может задаваться на per-conference основе. На этом завязаны отсылка ping-ов и определение
|
||||||
|
pong-ов, как минимум. Хотя формально джойниться можно с любым ником :) но что-то не хочется потом распутывать этот
|
||||||
|
клубок.
|
||||||
|
|
||||||
|
|
||||||
|
XEP-0410 MUC Self-Ping (Schrödinger's Chat)
|
||||||
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
xep-0410 реализован не польностью. Согласно https://xmpp.org/extensions/xep-0410.html, существует минимум 2 варианта
|
||||||
|
реализации обнаружения, что киент оотвалился от чатика.
|
||||||
|
|
||||||
|
* С двухступенчатым пингом. Когда с jid-а локального пнгуется jid в конференции. Ответ доставляется на один из клиентов.
|
||||||
|
Который в свою очередь должен ответить на этот ответ (отвечайте на мои ответы!), результат этого ответа передаётся
|
||||||
|
оригинальному запросившему клиенту.
|
||||||
|
|
||||||
|
* С серверной орптимизацией. (оптимизация анонсируется в ответе на запрос disco#info MUC-а)
|
||||||
|
В этом случае пинг будет одноступенчатым и нам сразу же должен по идее вернуться result (то бишь pong)
|
||||||
|
|
||||||
|
Двухступенчатая реализация вызывает вопросы. Во-первых, своей двухступенчатастью, а во-вторых, ответ на начальный пинг
|
||||||
|
доставляется на один из клиентов (не на все!), а если этот клиент испытывает сетевые проблемы, то получается, страдают
|
||||||
|
все клиенты. В нашем случае предполагается, что если клиентов больше одного, то он не должен своим состоянием аффектить
|
||||||
|
других клиентов.
|
||||||
|
|
||||||
|
И... тут есть другой момент - двухступенчатый пинг гораздо сложнее в реализации на клиентской стороне, т.к. надо
|
||||||
|
обрабатывать значительно бОльшее количество кейсов сочетаний ошибок, которые могут возникнуть в ходе пинга.
|
||||||
|
|
||||||
|
Вобщем, поддерживаем только второй вариант пинга, который с серверной оптимизацией.
|
||||||
|
|
||||||
|
N.B. Но на IQ get ping с xmlns="urn:xmpp:ping" из первого шага мы всё-таки будем отвечать обычным result-ом (понгом, то
|
||||||
|
бишь)
|
||||||
|
|
||||||
|
N.B. Каждый пинг комнаты происходит в отдельной goroutne-ке, так что заходить в огромное количеств комнат (>1k)
|
||||||
|
не рекомендуется
|
||||||
|
|
||||||
|
|
||||||
|
xep-0199 XMPP Ping
|
||||||
|
------------------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Cуществует несколько видов пинга:
|
||||||
|
* Server-To-Client Pings
|
||||||
|
* Client-To-Server Pings
|
||||||
|
* Server-To-Server Pings
|
||||||
|
* Client-to-Client Pings
|
||||||
|
* Component-to-Client Pings
|
||||||
|
|
||||||
|
Явным образом я реализовываю с2s. Плюс частично Component-to-Client в рамках N.B. по xep-0410.
|
||||||
20
LICENSE.txt
Normal file
20
LICENSE.txt
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Copyright (c) 2022 Sergei Fedosoff <eleksir@gmail.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
21
Makefile
Normal file
21
Makefile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/env gmake -f
|
||||||
|
|
||||||
|
GOOPTS=CGO_ENABLED=0
|
||||||
|
BUILDOPTS=-ldflags="-s -w" -a -gcflags=all=-l -trimpath
|
||||||
|
|
||||||
|
all: clean build
|
||||||
|
|
||||||
|
build:
|
||||||
|
${GOOPTS} go build ${BUILDOPTS} -o "buny-jabber-bot" collection.go types.go globals.go lib.go event_parser.go \
|
||||||
|
buny.go main.go
|
||||||
|
|
||||||
|
clean:
|
||||||
|
go clean
|
||||||
|
|
||||||
|
upgrade:
|
||||||
|
rm -rf vendor
|
||||||
|
go get -d -u -t ./...
|
||||||
|
go mod tidy
|
||||||
|
go mod vendor
|
||||||
|
|
||||||
|
# vim: set ft=make noet ai ts=4 sw=4 sts=4:
|
||||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# Buny Jabber Bot
|
||||||
|
|
||||||
|
Простой бот, работающий по протоколу xmpp. Его задача - банить абьюзеров чата.
|
||||||
|
|
||||||
|
## Что он может?
|
||||||
|
|
||||||
|
Имеет возможность соединяться с сервером по протоколу xmpp как с шифрованием, так и без. Шифрование работает по
|
||||||
|
классическим механикам (шифрованный канал связи, без возможности коммуникации в незашифрованном виде), без поддержки
|
||||||
|
механизма start tls. Что-то в гошке или на стороне сервера при выборе StartTLS не работает.
|
||||||
|
|
||||||
|
Имеет возможность заносить пользователей в бан-лист согласно заданным в конфиге правилам.
|
||||||
|
|
||||||
|
## Что он не может?
|
||||||
|
|
||||||
|
* Не может заходить в комнаты, защищённые паролем.
|
||||||
|
* Не умеет разгадывать капчу.
|
||||||
|
|
||||||
|
## Как заставить его работать?
|
||||||
|
|
||||||
|
Этот бот автоматизирует уборку человеческой грязи. Самый важный вопрос, кторый следует себе задать еред его
|
||||||
|
использоованием - а не забаню ли я кого-то не того?
|
||||||
|
|
||||||
|
Технически, бот собирается гошкой 1.20 поод платформу linux. Работоспособность на других платформах не тестировалась.
|
||||||
|
|
||||||
|
Чтобы получить готовые бинарники достаточно выполнить команду:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
в итоге получится бинарник **buny-jabber-bot**, которому нужен конфиг data/config.json для работы.
|
||||||
|
|
||||||
|
А также понадобятся **data/blacklist.json** и **data/whitelist.json** в которых списки правил, согласно которым *точно*
|
||||||
|
надо банить злодеев и списки пользователей, которых *точно* не надо банить.
|
||||||
|
|
||||||
|
### Замечание
|
||||||
|
|
||||||
|
Очевидным образом в комнате банить может только админ. Поэтому боту придётся выдать админские права. А комнату сделать
|
||||||
|
не анонимной (то есть, чтобы как минимум админы могли видеть реальный jid, потому что в бан отправляется именно он).
|
||||||
|
|
||||||
|
## Как работает бан?
|
||||||
|
|
||||||
|
Никакой магии, всё по стандарту, согласно [xep-0045](https://xmpp.org/extensions/xep-0045.html#ban).
|
||||||
|
|
||||||
|
Серверу направляется команда изменения участия (affiliation) зашедшего jid-а и задаётся значение outcast, на что сервер
|
||||||
|
помещает этот jid в список изгнанных из комнаты (бан-лист конкретной комнаты, в которой присутствует бот) и выгоняет
|
||||||
|
этого jid-а.
|
||||||
|
|
||||||
|
### В чём тонкость?
|
||||||
|
|
||||||
|
Ровно в том, что для исполнения действия, боту надо заметить presence участника, а presence посылается
|
||||||
|
при изменении состояния клиента. То есть, если jid уже был в комнате на момент захода бота в неё, то его не забанят
|
||||||
|
(во всяком случае моментально). Самый простой способ поместить таких персонажей в бан - просто кикнуть их. Ихний
|
||||||
|
affiliation сменится на none, бот заметит presence и, если jid подпадает под правила чёрного списка, то отправит этот
|
||||||
|
jid в бан.
|
||||||
|
|
||||||
|
Но, простите, ведь "по правилам", при заходе клиента в комнату ему присылается presence всех участников, чтобы он мог
|
||||||
|
построить локальный ростер комнаты и начать отслеживать изменения состояния участников конференции. Почему бы в этот
|
||||||
|
момент не банить злодеев?
|
||||||
|
|
||||||
|
Да, так делать можно, однако пока этого нет. Дело в том, что presence самого себя участнику приходит последним (после
|
||||||
|
presence-ов тех, кто находится в комнате на момент появления в ней бота), и, согласно этому событию, можно считать, что
|
||||||
|
клиент зашёл в комнату успешно и формально *только после этого*, клиент может что-то пытаться делать в этой комнате.
|
||||||
|
Соответственно, чтобы кого-то банить исходя из списка presense-ов, который прилетает на входе, необходимо реализовать
|
||||||
|
"одноразовую" очередь тех, кого имеет смысл забанить и разгрести её "когда будет можно". Эта задача факультативная,
|
||||||
|
возможно, она будет решена потом.
|
||||||
|
|
||||||
|
## Как работает белый список?
|
||||||
|
|
||||||
|
Белый список - это список jid-ов, которых банить нельзя ни при каких обстоятельствах. В нём всегда неявно присутсвует
|
||||||
|
сам бот.
|
||||||
|
|
||||||
|
### Можно ли почистить ban-list?
|
||||||
|
|
||||||
|
Да, бывают ситуации, когда по ошибке бот может забанить кого-то не того или, если не настроен белый список вообще ни в
|
||||||
|
каком виде, то будет банить всех входящих. И если вдруг произошло так, что в бан попали все админы комнаты, то разбанить
|
||||||
|
их может либо овнер (никто же не даст овнера боту), либо тот, кто зайдёт в комнату из-под аккаунта бота и разбанить всех
|
||||||
|
от его имени.
|
||||||
|
|
||||||
|
В далёких планах есть идея релизовать очистку бан-листа при запуске бота с настройкой `"wipe_bans": true`` в
|
||||||
|
whitelist.json для канала; кроме того, есть планы такое реализовать и в качестве команды, когда у бота появятся команды.
|
||||||
|
|
||||||
|
## Дисклеймер
|
||||||
|
|
||||||
|
Весь дисклеймер в файле LICENSE.txt :)
|
||||||
|
|
||||||
|
## Благодарности
|
||||||
|
|
||||||
|
Во-первых `сообществу`, без него не было бы чятиков.
|
||||||
|
|
||||||
|
Во-вторых `batman46(new)` из конференций на `jabber.ru`, за наблюдения и за некоторые идеи, воплощённыые в этом боте.
|
||||||
|
|
||||||
|
В-третьих `U2` из конференции `radio@conference.jabber.ru` за мысли, идеи и за позитивный настрой.
|
||||||
16
Story.txt
Normal file
16
Story.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
Конечно, очень печально, что приходится развивать свои навыки программизмов на программах с такой задачей. Но жизнь не
|
||||||
|
всегда радует своими проявлениями.
|
||||||
|
|
||||||
|
История бота довольно банальна: к нам в чятик стал набергать один товарищ и нести чушь про политоту. Если его банить
|
||||||
|
классическими средствами, он просто создавал новый аккаунт и снова набегал гадить.
|
||||||
|
|
||||||
|
И таким образом родилась идея как-то автоматизировать его бан.
|
||||||
|
|
||||||
|
Поскольку сама идея достойна мема, то боту было дано имя buny. Как отсылка к комикс-стрипу buni, к персонажу
|
||||||
|
banhammer-тян и как отсылка к персонажу по имени Бани из сериала про Сейлор Мун (если верить Википедии, то Бани на самом
|
||||||
|
деле это Usagi Tsukino, но у русских переводчиков своё видение мира).
|
||||||
|
|
||||||
|
Jid-ом бота стал buny@jabber.ru, а девизом - надпись из школьного туалета (исковерканный девиз из Сейлор Мун):
|
||||||
|
"Облегчающая сила Луны, помоги мне!".
|
||||||
|
|
||||||
|
С портретом, традиционно, заминка.
|
||||||
143
buny.go
Normal file
143
buny.go
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buny(v xmpp.Presence) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Если у presence-а есть JID и presence из одной из комнат, в которой мы есть и если его домен в чёрном
|
||||||
|
// списке, заносим пидора в список outcast-ов
|
||||||
|
if v.JID != "" {
|
||||||
|
// На всякий случай: себя никогда не баним, явным образом
|
||||||
|
if v.JID == talk.JID() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
room := strings.SplitN(v.From, "/", 2)[0]
|
||||||
|
|
||||||
|
if room == "" {
|
||||||
|
log.Infof("We got empty room in repsence event, which kinda strange: %s", spew.Sdump(v))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
goodJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||||
|
|
||||||
|
// Обрабатываем правила белого списка
|
||||||
|
for _, good := range whiteList.Whitelist {
|
||||||
|
// Глобальный белый список
|
||||||
|
if good.RoomName == "" {
|
||||||
|
for _, person := range good.Jid {
|
||||||
|
if person == goodJid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Белый список для конкретных каналов
|
||||||
|
if good.RoomName == room {
|
||||||
|
for _, person := range good.Jid {
|
||||||
|
if person == goodJid {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
evilJid := strings.SplitN(v.JID, "/", 2)[0]
|
||||||
|
|
||||||
|
// Обрабатываем правила чёрного списка
|
||||||
|
for _, cRoom := range roomsConnected {
|
||||||
|
if cRoom == room {
|
||||||
|
for _, bEntry := range blackList.Blacklist {
|
||||||
|
|
||||||
|
// Обработаем правила гобального чёрного списка
|
||||||
|
if bEntry.RoomName == "" {
|
||||||
|
for _, jid_regexp := range bEntry.JidRe {
|
||||||
|
re, err := regexp.Compile(jid_regexp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Incorrect regexp in blacklist: %s, skipping", jid_regexp)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if re.Match([]byte(evilJid)) {
|
||||||
|
log.Infof("Hammer falls on %s", v.JID)
|
||||||
|
// https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением
|
||||||
|
ban := "<item affiliation='outcast' jid='" + evilJid + "'>"
|
||||||
|
ban += "<reason />"
|
||||||
|
ban += "</item>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformationQuery(
|
||||||
|
talk.JID(),
|
||||||
|
room,
|
||||||
|
"ban1",
|
||||||
|
xmpp.IQTypeSet,
|
||||||
|
"http://jabber.org/protocol/muc#admin",
|
||||||
|
ban,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to ban user: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработаем правила конкретного канала, комнаты конференции
|
||||||
|
if bEntry.RoomName == room {
|
||||||
|
for _, jid_regexp := range bEntry.JidRe {
|
||||||
|
re, err := regexp.Compile(jid_regexp)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Incorrect regexp in blacklist: %s, skipping", jid_regexp)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if re.Match([]byte(evilJid)) {
|
||||||
|
log.Infof("Hammer falls on %s", v.JID)
|
||||||
|
// https://xmpp.org/extensions/xep-0045.html#ban баним вот таким сообщением
|
||||||
|
ban := "<item affiliation='outcast' jid='" + evilJid + "'>"
|
||||||
|
ban += "<reason />"
|
||||||
|
ban += "</item>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformationQuery(
|
||||||
|
talk.JID(),
|
||||||
|
room,
|
||||||
|
"ban1",
|
||||||
|
xmpp.IQTypeSet,
|
||||||
|
"http://jabber.org/protocol/muc#admin",
|
||||||
|
ban,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to ban user: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
69
collection.go
Normal file
69
collection.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collection это sync.Map-ка, хранящая в себе произвольные итемы
|
||||||
|
type Collection struct {
|
||||||
|
items sync.Map
|
||||||
|
close chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Item представляет собой интерфейс для хранения произвольных данных
|
||||||
|
type item struct {
|
||||||
|
data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCollection создаёт инстанс коллекции
|
||||||
|
func NewCollection() *Collection {
|
||||||
|
c := &Collection{
|
||||||
|
close: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get достаёт данные по заданному ключу из коллекции
|
||||||
|
func (collection *Collection) Get(key interface{}) (interface{}, bool) {
|
||||||
|
obj, exists := collection.items.Load(key)
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
item := obj.(item)
|
||||||
|
|
||||||
|
return item.data, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set сохраняет данные с заданным ключом в коллекцию
|
||||||
|
func (collection *Collection) Set(key interface{}, value interface{}) {
|
||||||
|
collection.items.Store(key, item{
|
||||||
|
data: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Range применяет функцию f ко всем ключам в коллекции
|
||||||
|
func (collection *Collection) Range(f func(key, value interface{}) bool) {
|
||||||
|
fn := func(key, value interface{}) bool {
|
||||||
|
item := value.(item)
|
||||||
|
|
||||||
|
return f(key, item.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.items.Range(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete удаляет ключ и значение из коллекции данных
|
||||||
|
func (collection *Collection) Delete(key interface{}) {
|
||||||
|
collection.items.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close очищает и высвобождает ресурсы, занятые коллекцией
|
||||||
|
func (collection *Collection) Close() {
|
||||||
|
collection.close <- struct{}{}
|
||||||
|
collection.items = sync.Map{}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
15
data/sample_blacklist.json
Normal file
15
data/sample_blacklist.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"blacklist": [
|
||||||
|
{
|
||||||
|
# Пустой список означет глобальные баны, которые применяются ко всем комнатам, где присутствует бот
|
||||||
|
"room_name": "",
|
||||||
|
# Список регулярок JID-ов, которых надо банить
|
||||||
|
"jid_re": [
|
||||||
|
"^[Mm]ary@server.tld/resource1$",
|
||||||
|
"^[cC]hris(topher)?@",
|
||||||
|
"^(his|her)_cat@sweet.home/.*$",
|
||||||
|
"^(her|his)_dog@.*\\.street/.*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
56
data/sample_config.json
Normal file
56
data/sample_config.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
# Фронт-энд бота
|
||||||
|
"jabber": {
|
||||||
|
# Если не задан, будет localhost
|
||||||
|
"server": "jabber.tld",
|
||||||
|
# Если не задано - 5222 для нешифрованного соединения или для starttls и 5223 для голого (старорежимного) ssl
|
||||||
|
"port": 5222,
|
||||||
|
# Включить шифрование
|
||||||
|
"ssl": true,
|
||||||
|
# Это более современный протокол и как раз на нём работет большинство jabber-серверов, порт 5222,
|
||||||
|
# jabber.ru по этому протоколу как-то странно работает, если его включить, я получаю
|
||||||
|
# tls: first record does not look like a TLS handshake
|
||||||
|
"starttls" : true,
|
||||||
|
# Не проверяем сертификаты по-умолчанию.
|
||||||
|
# Вам правда хочется всё это проверять? Там по идее могут быть всякие Lets Encrypt, которые хоть и работают, но
|
||||||
|
# создают кучу проблем
|
||||||
|
"ssl_verify": false,
|
||||||
|
# Позволять авторизацию логин-паролем по незашифрованному каналу связи
|
||||||
|
"insecureallowunencryptedauth": false,
|
||||||
|
# Таймаут установления соединения с сервером, в секундах. 10 секунд, если не задано.
|
||||||
|
"connection_timeout": 10,
|
||||||
|
# Задержка перед пере-коннекчиванием. 3 секунды, если не задано.
|
||||||
|
"reconnect_delay": 3,
|
||||||
|
# Промежуток между пингами сервера, для определения его живости. По-умолчанию 60 секунд.
|
||||||
|
# Пинги посылаются, если в течение этого времени не было активности со стороны сервера.
|
||||||
|
"server_ping_delay": 60,
|
||||||
|
# Промежуток между пингами muc-ов. По-умолчанию 900 секунд. (Согласно рекомендации в xep-0410.)
|
||||||
|
# Пинги посылаются, если в течение этого времени не было активности со стороны muc-а.
|
||||||
|
"muc_ping_delay": 900,
|
||||||
|
# Сколько ждать перед переджоиванием к комнате. По-умолчанию, 3 секунды.
|
||||||
|
"muc_rejoin_delay": 3,
|
||||||
|
# Ping-to-ping splay delay. Варьируемая задержка между пингами, чтобы не набирать высокий message rate.
|
||||||
|
# По-умолчанию, 3 секунды. Под капотом преобразуются в миллисекунды. Важна для пингов MUC-ов, особенно,
|
||||||
|
# если их МНОГО.
|
||||||
|
"ping_splay_delay": 3,
|
||||||
|
# Ник бота, с которым он будет заходить в комнаты
|
||||||
|
"nick": "buny",
|
||||||
|
"resource" : "buny bot",
|
||||||
|
# Имя пользователя для авторизации на сервере
|
||||||
|
"user": "buny@jabber.tld",
|
||||||
|
# Пароль для авторизации
|
||||||
|
"password": "secret",
|
||||||
|
# Jabber-каналы, к которым бот попробует присоединиться при старте
|
||||||
|
"channels": [
|
||||||
|
"my_channel@conference.jabber.tld",
|
||||||
|
"another_channel@conference.jabber.tld"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
# Многословность логов. Если не задано, то info. Debug - ДЕЙСТВИТЕЛЬНО вербозный уровень логгирования.
|
||||||
|
# Есть ещё trace :)
|
||||||
|
"loglevel": "debug",
|
||||||
|
|
||||||
|
# Если не задан, логи идут в STDOUT
|
||||||
|
"log": "/var/log/echo-jabber-bot/echo-jabber-bot.log",
|
||||||
|
}
|
||||||
24
data/sample_whitelist.json
Normal file
24
data/sample_whitelist.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Пример белого списка
|
||||||
|
{
|
||||||
|
"whitlist": [
|
||||||
|
{
|
||||||
|
# Если room_name пустой или его нет, это список jid-ов, которых мы не баним ни на одном канале, нигде и никогда
|
||||||
|
"room_name": "",
|
||||||
|
# Собственно, сам список
|
||||||
|
"jid": [
|
||||||
|
"me@jabber.tld",
|
||||||
|
"myself@xmpp.tld",
|
||||||
|
"iren@example.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
# А этот список относится к конференции earth@conference.jabber.tld
|
||||||
|
"room_name": "earth@conference.jabber.tld",
|
||||||
|
"jid": [
|
||||||
|
"meow@street.net",
|
||||||
|
"bzzz@hive.org",
|
||||||
|
"god@heaven.org"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
567
event_parser.go
Normal file
567
event_parser.go
Normal file
@@ -0,0 +1,567 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseEvent(e interface{}) {
|
||||||
|
lastServerActivity = time.Now().Unix()
|
||||||
|
|
||||||
|
switch v := e.(type) {
|
||||||
|
// Сообщение в чяти
|
||||||
|
case xmpp.Chat:
|
||||||
|
log.Debugf("Looks like message, ChatType: %s, From: %s, Subject: %s Text: %s",
|
||||||
|
v.Type, v.Remote, v.Subject, v.Text)
|
||||||
|
|
||||||
|
if config.NoEcho {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Топик чятика присылается в виде сообщения с subject, но без text
|
||||||
|
// В то же время сообщения от людей приходят с пустым subject, но с заполненным text
|
||||||
|
if v.Text != "" {
|
||||||
|
// Чятики бывают групповые и не групповые, от этого зависит Remote, куда направлять сообщение
|
||||||
|
switch v.Type { //nolint:whitespace
|
||||||
|
|
||||||
|
// Групповой чятик
|
||||||
|
case "groupchat":
|
||||||
|
log.Debugf("Message from public chat: %s", v.Text)
|
||||||
|
// dest := strings.SplitN(v.Remote, "/", 2)[0]
|
||||||
|
nick := strings.SplitN(v.Remote, "/", 2)[1]
|
||||||
|
|
||||||
|
if nick == config.Jabber.Nick {
|
||||||
|
log.Debug("Skipping message from myself")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lastActivity = lastServerActivity
|
||||||
|
|
||||||
|
if muc, _ := strings.CutSuffix(v.Remote, "/"); muc != "" {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Приватный чятик
|
||||||
|
case "chat":
|
||||||
|
// Здесь у нас может быть 2 вида From:
|
||||||
|
// slackware-current@conference.jabber.ru/eleksir - это если сообщение из публичного чятика
|
||||||
|
// eleksir@jabber.ru/array.lan - это если мы работаем через ростер
|
||||||
|
log.Debugf("Private message: %s", v.Text)
|
||||||
|
|
||||||
|
lastActivity = lastServerActivity
|
||||||
|
|
||||||
|
// Внезапно, ошибки. По идее они должны ассоциироваться с отправляемыми сообщениями, но по ходу это
|
||||||
|
// не реализовано, поэтому мы получаем ошибки в форме отдельного чятика
|
||||||
|
case "error":
|
||||||
|
// Это ошибка со стороны сервера, видимо, в ответ на наши действия и поскольку такие вещи происходят
|
||||||
|
// асинхронно, связать их с конкретными нашими действиями довольно сложно.
|
||||||
|
log.Warn(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IQ-сообщение - пинг, понг, что-то ещё...
|
||||||
|
case xmpp.IQ:
|
||||||
|
// По правилам IQ обязательно должна содержать ID, причём не пустой
|
||||||
|
if v.ID == "" {
|
||||||
|
log.Info("Got an IQ stanza with empty id, discarding")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch v.Type {
|
||||||
|
case xmpp.IQTypeGet:
|
||||||
|
log.Debug("Looks like IQ get query")
|
||||||
|
|
||||||
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
|
if slices.Contains(roomsConnected, muc) {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var iqStruct jabberSimpleIqGetQuery
|
||||||
|
var parseError bool
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
|
switch iqStruct.Xmlns { //nolint:whitespace
|
||||||
|
|
||||||
|
// Запрос номера версии приложения
|
||||||
|
case "jabber:iq:version":
|
||||||
|
log.Infof("Got IQ get request for version from %s", v.From)
|
||||||
|
answer := "<query xmlns=\"jabber:iq:version\">"
|
||||||
|
answer += "<name>buny-jabber-bot</name>"
|
||||||
|
answer += "<version>1.с_чем-то</version>"
|
||||||
|
answer += "<os>линупс</os>"
|
||||||
|
answer += "</query>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send verion info to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
// Нам прислали попингуй
|
||||||
|
case "urn:xmpp:ping":
|
||||||
|
log.Infof("Got IQ get request for pong from %s", v.From)
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
"",
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
// У нас запросили время последней активности. В нашем случае это либо время запуска клиента, либо время
|
||||||
|
// последней фразы в чяти.
|
||||||
|
case "jabber:iq:last":
|
||||||
|
log.Infof("Got IQ get last activity time request from %s", v.From)
|
||||||
|
|
||||||
|
answer := fmt.Sprintf(
|
||||||
|
"<query xmlns=\"jabber:iq:last\" seconds=\"%d\" />",
|
||||||
|
time.Now().Unix()-lastActivity,
|
||||||
|
)
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send last activity time to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
// Запрос на список поддерживаемых фич - версия, локальное время, итд
|
||||||
|
case xmpp.XMPPNS_DISCO_INFO:
|
||||||
|
log.Infof("Got IQ get disco#info request from %s", v.From)
|
||||||
|
|
||||||
|
answer := "<query xmlns=\"http://jabber.org/protocol/disco#info\">"
|
||||||
|
answer += "<feature var=\"jabber:iq:version\" />"
|
||||||
|
answer += "<feature var=\"urn:xmpp:time\" />"
|
||||||
|
answer += "<feature var=\"urn:xmpp:ping\" />"
|
||||||
|
answer += "<feature var=\"jabber:iq:last\" />"
|
||||||
|
answer += "<feature var=\"http://jabber.org/protocol/disco#info\" />"
|
||||||
|
answer += "</query>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send disco#info to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
// Спрашивают список команд, чтобы поуправлять этим клиентом
|
||||||
|
case xmpp.XMPPNS_DISCO_ITEMS:
|
||||||
|
log.Infof("Got IQ get disco#items request from %s, answer service unavailable", v.From)
|
||||||
|
|
||||||
|
answer := "<query xmlns=\"http://jabber.org/protocol/disco#info\" "
|
||||||
|
answer += "node=\"http://jabber.org/protocol/commands\" />"
|
||||||
|
answer += "<error type=\"cancel\">"
|
||||||
|
answer += "<service-unavailable xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
|
||||||
|
answer += "</error>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeError,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send disco#items to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
// Запрашивают что-то, о чём мы не имеем представления
|
||||||
|
default:
|
||||||
|
log.Info("Got an unknown IQ get request, discarding")
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parseError = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Попробуем распарсить входящий запрос как urn:xmpp:time
|
||||||
|
if parseError {
|
||||||
|
var iqStruct jabberTimeIqGetQuery
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
|
switch iqStruct.Xmlns {
|
||||||
|
case "urn:xmpp:time":
|
||||||
|
log.Infof("Got IQ get time request from %s", v.From)
|
||||||
|
|
||||||
|
answer := fmt.Sprintf(
|
||||||
|
"<time xmlns=\"urn:xmpp:time\"><tzo>+00:00</tzo><utc>%s</utc></time>",
|
||||||
|
time.Now().UTC().Format(time.RFC3339),
|
||||||
|
)
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send urn:xmpp:time to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Info("Got bogus IQ get time request, discarding")
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parseError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Попробуем распарсить входящий запрос как http://jabber.org/protocol/pubsub
|
||||||
|
if parseError {
|
||||||
|
var iqStruct jabberPubsubIQGetQuery
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
|
switch iqStruct.Xmlns {
|
||||||
|
case xmpp.XMPPNS_PUBSUB:
|
||||||
|
log.Infof("Got IQ get pubsub request from %s, answer feature unimplemented", v.From)
|
||||||
|
|
||||||
|
answer := "<error type=\"cancel\">"
|
||||||
|
answer += "<feature-not-implemented xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
|
||||||
|
answer += "<unsupported xmlns=\"http://jabber.org/protocol/pubsub#errors\" feature=\"subscribe\" />"
|
||||||
|
answer += "</error>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeError,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf(
|
||||||
|
"Unable to send pubsub feature unimplemented to jabber server: id=%s, err=%s",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Info("Got unknown IQ get pubsub something request, discarding")
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
parseError = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
parseError = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Попробуем распарсить входящий запрос как urn:xmpp:ping он приходит при MUC Self-Ping (Schrödinger's Chat)
|
||||||
|
// Предполагается, что такие респонсы должны приходить только для пинга без серверной оптимизации.
|
||||||
|
// Хотя идейно мы не поддерживаем работу без серверной оптимизации, но на пинг ответим, нам несложно.
|
||||||
|
if parseError {
|
||||||
|
var iqStruct jabberIqPing
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqStruct); err == nil {
|
||||||
|
log.Debugf("Got IQ get request (actually, response) for MUC Self-Ping from %s", v.From)
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeResult,
|
||||||
|
"",
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send pong to jabber server: id=%s, err=%s", id, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Не знаю, как парсить... залоггируем это дело и пойдём дальше
|
||||||
|
if parseError {
|
||||||
|
log.Infof("Does not look like parsable via iqStruct")
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
case xmpp.IQTypeResult:
|
||||||
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
|
if slices.Contains(roomsConnected, muc) {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Похоже на pong от сервера (по стандарту в ответе нету query, но go-xmpp нам подсовывает это)
|
||||||
|
case v.From == config.Jabber.Server && v.To == talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||||
|
log.Debugf("Got S2C pong answer from %s to %s", v.From, v.To)
|
||||||
|
serverPingTimestampRx = time.Now().Unix()
|
||||||
|
|
||||||
|
// Похоже на понг второй стадии xep-0410 MUC-Ping-а, который у нас не реализован
|
||||||
|
case v.To == talk.JID() && string(v.Query) == "<XMLElement></XMLElement>":
|
||||||
|
mucNameMatch := slices.Contains(roomsConnected, v.From)
|
||||||
|
|
||||||
|
if mucNameMatch {
|
||||||
|
log.Debugf("Got server-optimized MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||||
|
} else {
|
||||||
|
mucNickMatch := false
|
||||||
|
|
||||||
|
for _, room := range roomsConnected {
|
||||||
|
mucNick := fmt.Sprintf("%s/%s", room, config.Jabber.Nick)
|
||||||
|
|
||||||
|
if v.From == mucNick {
|
||||||
|
mucNickMatch = true
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mucNickMatch {
|
||||||
|
log.Debugf("Got 2-nd stage MUC pong answer (xep-0410) from %s to %s", v.From, v.To)
|
||||||
|
// Поскольку никакой логики у нас на этот счёт не предусмотрено, то просто пропускаем ответ
|
||||||
|
} else {
|
||||||
|
log.Infof("Got unknown pong from %s to %s", v.From, v.To)
|
||||||
|
log.Debug(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
log.Info("Got an IQ result. Dunno how deal with it, discarding")
|
||||||
|
log.Debug(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Этот бот не управляется со стороны сервера, поэтому все попытки порулить игнорируем
|
||||||
|
case xmpp.IQTypeSet:
|
||||||
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
|
if slices.Contains(roomsConnected, muc) {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Got an IQ request for set something. Answer not implemented")
|
||||||
|
|
||||||
|
answer := "<error type=\"cancel\">"
|
||||||
|
answer += "<feature-not-implemented xmlns=\"urn:ietf:params:xml:ns:xmpp-stanzas\" />"
|
||||||
|
answer += "<unsupported xmlns=\"http://jabber.org/protocol/commands\" feature=\"set\" />"
|
||||||
|
answer += "</error>"
|
||||||
|
|
||||||
|
if id, err := talk.RawInformation(
|
||||||
|
v.To,
|
||||||
|
v.From,
|
||||||
|
v.ID,
|
||||||
|
xmpp.IQTypeError,
|
||||||
|
answer,
|
||||||
|
); err != nil {
|
||||||
|
log.Errorf("Unable to send set feature unimplemented to jabber server: id=%s, err=%s",
|
||||||
|
id,
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug(spew.Sdump(e))
|
||||||
|
|
||||||
|
// Нам прилетело сообщение об ошибке
|
||||||
|
case xmpp.IQTypeError:
|
||||||
|
// Если сервер не хочет пинговаться и отвечает ошибкой на пинг, то наверно он не умеет в пинги,
|
||||||
|
// хотя если мы его пингуем, значит он анонсировал такой capability. Вот, засранец!
|
||||||
|
var iqPingStruct jabberIqPing
|
||||||
|
var parseError = true
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqPingStruct); err == nil {
|
||||||
|
if iqPingStruct.Xmlns == "urn:xmpp:ping" {
|
||||||
|
if v.From == config.Jabber.Server {
|
||||||
|
msg := "Server announced that it can answer c2s ping, but gives us an error to such query, "
|
||||||
|
msg += "fallback to keepalive whitespace pings"
|
||||||
|
log.Error(msg)
|
||||||
|
|
||||||
|
serverCapsList.Set("urn:xmpp:ping", false)
|
||||||
|
} else {
|
||||||
|
log.Errorf("Got 'ping unsupported' message from: %s to: %s", v.From, v.To)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error("Iq parsed as ping, but does not belong to xmlns urn:xmpp:ping")
|
||||||
|
log.Error(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Это у нас пинг xep-0410 и мы не в комнате, предполагается, что надо бы заджойниться
|
||||||
|
if parseError {
|
||||||
|
var iqErrorCancelNotAcceptable jabberIqErrorCancelNotAccepatble
|
||||||
|
|
||||||
|
if err := xml.Unmarshal(v.Query, &iqErrorCancelNotAcceptable); err == nil {
|
||||||
|
if v.To == talk.JID() {
|
||||||
|
nick := strings.SplitN(v.From, "/", 2)[1]
|
||||||
|
|
||||||
|
if slices.Contains(roomsConnected, iqErrorCancelNotAcceptable.By) && nick == config.Jabber.Nick {
|
||||||
|
log.Errorf(
|
||||||
|
"Got Iq error message from: %s to: %s. Looks like i'm not in MUC anymore",
|
||||||
|
v.From, v.To,
|
||||||
|
)
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(config.Jabber.MucRejoinDelay) * time.Second)
|
||||||
|
|
||||||
|
if _, err := talk.JoinMUCNoHistory(iqErrorCancelNotAcceptable.By, nick); err != nil {
|
||||||
|
log.Errorf("Looks like connection to server also lost")
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error(
|
||||||
|
"looks like message parsed as jabberIqErrorCancelNotAcceptable but we're not in given room",
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Error(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Error(
|
||||||
|
"Looks like message parsed as jabberIqErrorCancelNotAcceptable but addressed not to us",
|
||||||
|
)
|
||||||
|
|
||||||
|
log.Error(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
parseError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if parseError {
|
||||||
|
log.Error("Unhandled IQ Error message")
|
||||||
|
log.Error(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
// Нам прилетело что-то неизвестное из семейства IQ stanza
|
||||||
|
default:
|
||||||
|
log.Info("Got an unknown IQ request. Dunno how deal with it, discarding")
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Смена статуса участника
|
||||||
|
case xmpp.Presence:
|
||||||
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
|
if slices.Contains(roomsConnected, muc) {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.Type == xmpp.IQTypeError {
|
||||||
|
// Это событие происходит, когда из чятика выходит другой инстанс клиента.
|
||||||
|
// Такое бывает, когда 2 инстанса с одинаковым ресурсом, например, начинают "драться" за возможность
|
||||||
|
// остаться на сервере. Ситуация, которую допускать нельзя, на самом деле, потому что рано или поздно такого
|
||||||
|
// клиента забанят из-за спама "вошёл-вышел".
|
||||||
|
// Однако, это не значит, что такую ситуацию мы не должны корректным образом обрабатывать.
|
||||||
|
if v.Type == "unavailable" {
|
||||||
|
log.Error("Presence notification - looks like another instance of client leaves room")
|
||||||
|
|
||||||
|
if slices.Contains(roomsConnected, v.From) {
|
||||||
|
go joinMuc(v.From)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Errorf("Presence notification, Type: %s, From: %s, Show: %s, Status: %s",
|
||||||
|
v.Type, v.From, v.Show, v.Status)
|
||||||
|
log.Errorf(spew.Sdump(v))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("Presence notification, Type: %s, From: %s, To: %s Show: %s, Status: %s, Affilation: %s, Role: %s, JID: %s",
|
||||||
|
v.Type, v.From, v.To, v.Show, v.Status, v.Affilation, v.Role, v.JID)
|
||||||
|
|
||||||
|
// Это наш собственный Presence
|
||||||
|
if v.Show == "" && v.Status == "" {
|
||||||
|
if from := strings.Split(v.From, "/"); from[1] == config.Jabber.Nick {
|
||||||
|
roomsConnected = append(roomsConnected, from[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := buny(v); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ответ на запрос поддерживаемых фич, который "http://jabber.org/protocol/disco#info"
|
||||||
|
case xmpp.DiscoResult:
|
||||||
|
if muc, _ := strings.CutSuffix(v.To, "/"); muc != "" {
|
||||||
|
if slices.Contains(roomsConnected, muc) {
|
||||||
|
lastMucActivity.Set(muc, lastServerActivity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Я видел 2 типа disco result и они отличались только []identities. Попробуем вытащить известный identity
|
||||||
|
// TODO: проверять адресата v.To
|
||||||
|
for _, ident := range v.Identities {
|
||||||
|
switch ident.Category {
|
||||||
|
case "server":
|
||||||
|
// Конкретно сейчас нас интересует только поддержка c2s ping
|
||||||
|
for _, feature := range v.Features {
|
||||||
|
log.Debugf("Server %s announced that it supports feature: %s", v.From, feature)
|
||||||
|
serverCapsList.Set(feature, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverCapsQueried = true
|
||||||
|
|
||||||
|
case "conference":
|
||||||
|
mucCaps := make(map[string]bool)
|
||||||
|
|
||||||
|
for _, feature := range v.Features {
|
||||||
|
log.Debugf("MUC %s announced that it supports feature: %s", v.From, feature)
|
||||||
|
mucCaps[feature] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
mucCapsList.Set(v.From, mucCaps)
|
||||||
|
|
||||||
|
case "pubsub":
|
||||||
|
log.Debugf("PubSub component %s reply to disco#info, skipping", v.From)
|
||||||
|
|
||||||
|
default:
|
||||||
|
log.Debug("Got unknown reply to disco#info")
|
||||||
|
log.Debug(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Это что-то неизвестное, подампим событие в лог
|
||||||
|
default:
|
||||||
|
log.Info(spew.Sdump(e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
65
globals.go
Normal file
65
globals.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config - это у нас глобальная штука :)
|
||||||
|
var config myConfig
|
||||||
|
|
||||||
|
// Списки тех, кого мы не баним никогда
|
||||||
|
var whiteList myWhiteList
|
||||||
|
|
||||||
|
// Списки злодеев, которых мы банм
|
||||||
|
var blackList myBlackList
|
||||||
|
|
||||||
|
// Ставится в true, если мы получили сигнал на выключение
|
||||||
|
var shutdown = false
|
||||||
|
|
||||||
|
// Чтобы не организовывать драку за установку коннекта
|
||||||
|
var connecting = false
|
||||||
|
|
||||||
|
// Глобальное состояние соединения
|
||||||
|
var isConnected = false
|
||||||
|
|
||||||
|
// Канал, в который приходят уведомления для хэндлера сигналов от траппера сигналов
|
||||||
|
var sigChan = make(chan os.Signal, 1)
|
||||||
|
|
||||||
|
// Основной инстанс xmpp-клиента
|
||||||
|
var talk *xmpp.Client
|
||||||
|
|
||||||
|
// Опции подключения к xmpp-серверу
|
||||||
|
var options *xmpp.Options
|
||||||
|
|
||||||
|
// Список комнат, в которых находится бот
|
||||||
|
var roomsConnected []string
|
||||||
|
|
||||||
|
// Время последней активности, нужно для jabber:iq:last
|
||||||
|
var lastActivity int64
|
||||||
|
|
||||||
|
// Время последней активности, нужно для c2s пингов - посылаем пинги, только если давненько ничего не приходило с
|
||||||
|
// сервера
|
||||||
|
var lastServerActivity int64
|
||||||
|
|
||||||
|
// Время последней активности MUC-ов, нужно для пингов - посылаем пинги, только если давненько ничего не приходило из
|
||||||
|
// muc-ов
|
||||||
|
var lastMucActivity = NewCollection()
|
||||||
|
|
||||||
|
// Получен ли ответ на запрос disco#info к серверу
|
||||||
|
var serverCapsQueried bool
|
||||||
|
|
||||||
|
// sync.Map-ка с капабилити сервера
|
||||||
|
var serverCapsList = NewCollection()
|
||||||
|
|
||||||
|
// sync.Map-ка с комнатами и их capability
|
||||||
|
var mucCapsList = NewCollection()
|
||||||
|
|
||||||
|
// Время, когда был отправлен c2s ping
|
||||||
|
var serverPingTimestampTx int64
|
||||||
|
|
||||||
|
// Время, когда был принят s2c pong
|
||||||
|
var serverPingTimestampRx int64
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
module buny-jabber-bot
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1
|
||||||
|
github.com/eleksir/go-xmpp v1.3.0
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
)
|
||||||
24
go.sum
Normal file
24
go.sum
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/eleksir/go-xmpp v1.3.0 h1:A4vonXmHJ+3W4LW+BDM24GkAT0zRMhCAMZhAqTRarLE=
|
||||||
|
github.com/eleksir/go-xmpp v1.3.0/go.mod h1:KnvbXvUpttEVFl8l5eDO2Q7J97ZxtTMvOsDWULD2Rn8=
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible h1:Rqr+Ya+0aCJMjaE4s8E9YKvuJLuLVpEvz4ONum52vnI=
|
||||||
|
github.com/hjson/hjson-go v3.3.0+incompatible/go.mod h1:qsetwF8NlsTsOTwZTApNlTCerV+b2GjYRRcIk4JMFio=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw=
|
||||||
|
golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
696
lib.go
Normal file
696
lib.go
Normal file
@@ -0,0 +1,696 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Хэндлер сигналов закрывает все бд, все сетевые соединения и сваливает из приложения
|
||||||
|
func sigHandler() {
|
||||||
|
log.Debug("Installing signal handler")
|
||||||
|
|
||||||
|
for s := range sigChan {
|
||||||
|
switch s {
|
||||||
|
case syscall.SIGINT:
|
||||||
|
log.Infoln("Got SIGINT, quitting")
|
||||||
|
case syscall.SIGTERM:
|
||||||
|
log.Infoln("Got SIGTERM, quitting")
|
||||||
|
case syscall.SIGQUIT:
|
||||||
|
log.Infoln("Got SIGQUIT, quitting")
|
||||||
|
|
||||||
|
// Заходим на новую итерацию, если у нас "неинтересный" сигнал
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Чтобы не срать в логи ошибками, проставим shutdown state приложения в true
|
||||||
|
shutdown = true
|
||||||
|
|
||||||
|
if isConnected && !shutdown {
|
||||||
|
log.Debug("Try to set our presence to Unavailable and status to Offline")
|
||||||
|
|
||||||
|
// Вот тут понадобится коллекция известных пользователей, чтобы им разослать presence, что бот свалил в offline
|
||||||
|
// Пока за неимением лучшего сообщим об этом самим себе
|
||||||
|
for _, room := range roomsConnected {
|
||||||
|
if _, err := talk.SendPresence(
|
||||||
|
xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
To: room,
|
||||||
|
Status: "Offline",
|
||||||
|
Type: "unavailable",
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Infof("Unable to send presence to jabber server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// И закрываем соединение
|
||||||
|
log.Debugf("Closing connection to jabber server")
|
||||||
|
|
||||||
|
if err := talk.Close(); err != nil {
|
||||||
|
log.Infof("Unable to close connection to jabber server: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Читает и валидирует конфиг, а также выставляет некоторые default-ы, если значений для параметров в конфиге нет
|
||||||
|
func readConfig() {
|
||||||
|
configLoaded := false
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get current executable path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configJSONPath := fmt.Sprintf("%s/data/config.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.buny-jabber-bot.json",
|
||||||
|
"~/buny-jabber-bot.json",
|
||||||
|
"/etc/buny-jabber-bot.json",
|
||||||
|
configJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Конфиг-файл длинноват для конфига, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 65535 {
|
||||||
|
log.Warnf("Config file %s is too long for config, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var sampleConfig myConfig
|
||||||
|
var tmp map[string]interface{}
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpjson, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpjson, &sampleConfig); err != nil {
|
||||||
|
log.Warnf("Skip parsing config file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Значения для Jabber-клиента
|
||||||
|
if sampleConfig.Jabber.Server == "" {
|
||||||
|
sampleConfig.Jabber.Server = "localhost"
|
||||||
|
log.Errorf("Jabber server is not defined in config, using localhost")
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Port == 0 {
|
||||||
|
sampleConfig.Jabber.Port = 5552
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Ssl {
|
||||||
|
if !sampleConfig.Jabber.StartTLS {
|
||||||
|
sampleConfig.Jabber.Port = 5553
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Jabber port is not defined in config, using 6667")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sampleConfig.Jabber.Ssl {
|
||||||
|
sampleConfig.Jabber.StartTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sampleConfig.Jabber.Ssl || !sampleConfig.Jabber.StartTLS {
|
||||||
|
sampleConfig.Jabber.SslVerify = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// sampleConfig.Jabber.InsecureAllowUnencryptedAuth = false, если не задан
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ConnectionTimeout == 0 {
|
||||||
|
sampleConfig.Jabber.ConnectionTimeout = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ReconnectDelay == 0 {
|
||||||
|
sampleConfig.Jabber.ReconnectDelay = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.ServerPingDelay == 0 {
|
||||||
|
sampleConfig.Jabber.ServerPingDelay = 60
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.MucPingDelay == 0 {
|
||||||
|
sampleConfig.Jabber.MucPingDelay = 900
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.MucRejoinDelay == 0 {
|
||||||
|
sampleConfig.Jabber.MucRejoinDelay = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.PingSplayDelay == 0 {
|
||||||
|
sampleConfig.Jabber.PingSplayDelay = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Nick == "" {
|
||||||
|
log.Errorf("Jabber nick is not defined in config, quitting")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.Resource == "" {
|
||||||
|
sampleConfig.Jabber.Resource = "buny bot"
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Jabber.User == "" {
|
||||||
|
sampleConfig.Jabber.User = fmt.Sprintf("%s@%s", sampleConfig.Jabber.Nick, sampleConfig.Jabber.Server)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если sampleConfig.Jabber.Password не задан, то авторизации не будет
|
||||||
|
// Если sampleConfig.Jabber.Sasl не задан, то авторизация происходит через NickServ
|
||||||
|
|
||||||
|
// Нам бот нужен в каких-то чат-румах, а не "просто так"
|
||||||
|
if len(sampleConfig.Jabber.Channels) < 1 {
|
||||||
|
log.Errorf("No jabber channels/rooms defined in config, quitting")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sampleConfig.Loglevel == "" {
|
||||||
|
sampleConfig.Loglevel = "info"
|
||||||
|
}
|
||||||
|
|
||||||
|
// sampleConfig.Log = "" if not set
|
||||||
|
|
||||||
|
config = sampleConfig
|
||||||
|
configLoaded = true
|
||||||
|
log.Infof("Using %s as config file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !configLoaded {
|
||||||
|
log.Error("Config was not loaded! Refusing to start.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Читает и валидирует белые списки пользователей
|
||||||
|
func readWhitelist() {
|
||||||
|
whitelistLoaded := false
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get current executable path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistJSONPath := fmt.Sprintf("%s/data/whitelist.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.buny-jabber-bot-whitelist.json",
|
||||||
|
"~/buny-jabber-bot-whitelist.json",
|
||||||
|
"/etc/buny-jabber-bot-whitelist.json",
|
||||||
|
whitelistJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Файл белого списка длинноват для белого списка, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 2097152 {
|
||||||
|
log.Warnf("Whitelist file %s is too long for whitelist, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var sampleWhitelist myWhiteList
|
||||||
|
var tmp map[string]interface{}
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpjson, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpjson, &sampleWhitelist); err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
whiteList = sampleWhitelist
|
||||||
|
whitelistLoaded = true
|
||||||
|
log.Infof("Using %s as whiteList file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !whitelistLoaded {
|
||||||
|
log.Error("Whitelist was not loaded! Refusing to start.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Читает и валидирует чёрные списки пользователей
|
||||||
|
func readBlacklist() {
|
||||||
|
blacklistLoaded := false
|
||||||
|
executablePath, err := os.Executable()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get current executable path: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
whitelistJSONPath := fmt.Sprintf("%s/data/blacklist.json", filepath.Dir(executablePath))
|
||||||
|
|
||||||
|
locations := []string{
|
||||||
|
"~/.buny-jabber-bot-blacklist.json",
|
||||||
|
"~/buny-jabber-bot-blacklist.json",
|
||||||
|
"/etc/buny-jabber-bot-blacklist.json",
|
||||||
|
whitelistJSONPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, location := range locations {
|
||||||
|
fileInfo, err := os.Stat(location)
|
||||||
|
|
||||||
|
// Предполагаем, что файла либо нет, либо мы не можем его прочитать, второе надо бы логгировать, но пока забьём
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Файл чёрного списка длинноват для чёрного списка, попробуем следующего кандидата
|
||||||
|
if fileInfo.Size() > 16777216 {
|
||||||
|
log.Warnf("Blacklist file %s is too long for blacklist, skipping", location)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err := os.ReadFile(location)
|
||||||
|
|
||||||
|
// Не удалось прочитать, попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip reading blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Исходя из документации, hjson какбы умеет парсить "кривой" json, но парсит его в map-ку.
|
||||||
|
// Интереснее на выходе получить структурку: то есть мы вначале конфиг преобразуем в map-ку, затем эту map-ку
|
||||||
|
// сериализуем в json, а потом json превращаем в структурку. Не очень эффективно, но он и нечасто требуется.
|
||||||
|
var sampleBlacklist myBlackList
|
||||||
|
var tmp map[string]interface{}
|
||||||
|
err = hjson.Unmarshal(buf, &tmp)
|
||||||
|
|
||||||
|
// Не удалось распарсить - попробуем следующего кандидата
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpjson, err := json.Marshal(tmp)
|
||||||
|
|
||||||
|
// Не удалось преобразовать map-ку в json
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("Skip parsing blacklist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tmpjson, &sampleBlacklist); err != nil {
|
||||||
|
log.Warnf("Skip parsing whitelist file %s: %s", location, err)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blackList = sampleBlacklist
|
||||||
|
blacklistLoaded = true
|
||||||
|
log.Infof("Using %s as blacklist file", location)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !blacklistLoaded {
|
||||||
|
log.Error("Blacklist was not loaded! Refusing to start.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func establishConnection() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if connecting && !isConnected {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// проставляем глобальные переменные
|
||||||
|
connecting = true
|
||||||
|
isConnected = false
|
||||||
|
roomsConnected = make([]string, 0)
|
||||||
|
|
||||||
|
for {
|
||||||
|
talk, err = options.NewClient()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// По идее keepalive должен же проходить только, если мы уже на сервере, так?
|
||||||
|
for {
|
||||||
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
|
log.Errorf("Try to send initial KeepAlive, got error: %s", err)
|
||||||
|
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
||||||
|
|
||||||
|
// Понятно, что мы уже не в процессе установления соединения, а произошла ошибка
|
||||||
|
connecting = false
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
log.Info("Connected")
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Джойнимся к чятикам, но делаем это в фоне, чтобы не блочиться на ошибках, например, если бота забанили
|
||||||
|
for _, room := range config.Jabber.Channels {
|
||||||
|
go joinMuc(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := talk.SendPresence(
|
||||||
|
xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
Status: "Ready to chat",
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Infof("Unable to send presence to jabber server: %s", err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
lastActivity = time.Now().Unix()
|
||||||
|
connecting = false
|
||||||
|
isConnected = true
|
||||||
|
|
||||||
|
log.Debugf("Sending disco#info to %s", config.Jabber.Server)
|
||||||
|
|
||||||
|
_, err = talk.DiscoverInfo(talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Infof("Unable to send disco#info to jabber server: %s", err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func reEstablishConnection() {
|
||||||
|
// Серверные capabilities нам *уже* неизвестны
|
||||||
|
serverCapsQueried = false
|
||||||
|
serverCapsList = NewCollection()
|
||||||
|
mucCapsList = NewCollection()
|
||||||
|
lastMucActivity = NewCollection()
|
||||||
|
|
||||||
|
// Пока соединение не установлено, мы про пинги ничего не знаем
|
||||||
|
serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен
|
||||||
|
serverPingTimestampTx = 0
|
||||||
|
time.Sleep(time.Duration(config.Jabber.ReconnectDelay) * time.Second)
|
||||||
|
connecting = false
|
||||||
|
establishConnection()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Джойнится к конференциям/каналам/комнатам в джаббере
|
||||||
|
func joinMuc(room string) {
|
||||||
|
log.Debugf("Sending disco#info from %s to %s", talk.JID(), room)
|
||||||
|
|
||||||
|
if _, err := talk.DiscoverInfo(talk.JID(), room); err != nil {
|
||||||
|
log.Infof("Unable to send disco#info to MUC %s: %s", room, err)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ждём, пока muc нам вернёт список фичей
|
||||||
|
for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ {
|
||||||
|
var myRoom interface{}
|
||||||
|
var supported bool
|
||||||
|
var exist bool
|
||||||
|
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
if myRoom, exist = mucCapsList.Get(room); !exist {
|
||||||
|
// Пока не задискаверилась
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if supported, exist = myRoom.(map[string]bool)["muc_unsecured"]; exist {
|
||||||
|
if supported {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
log.Infof("Unable to join to password-protected room. Don't know how to enter passwords :)")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := talk.JoinMUCNoHistory(room, config.Jabber.Nick); err != nil {
|
||||||
|
log.Errorf("Unable to join to MUC: %s", room)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
log.Infof("Joining to MUC: %s", room)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ждём, когда прилетит presence из комнаты, тогда мы точно знаем, что мы вошли
|
||||||
|
for i := 0; i < (20 * int(config.Jabber.ConnectionTimeout)); i++ {
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
if slices.Contains(roomsConnected, room) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вот теперь точно можно слать статус
|
||||||
|
log.Infof("Joined to MUC: %s", room)
|
||||||
|
|
||||||
|
if _, err := talk.SendPresence(
|
||||||
|
xmpp.Presence{ //nolint:exhaustruct
|
||||||
|
To: room,
|
||||||
|
Status: "Ready to chat",
|
||||||
|
},
|
||||||
|
); err != nil {
|
||||||
|
log.Infof("Unable to send presence to MUC %s: %s", room, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяет живость соединения с сервером
|
||||||
|
func probeServerLiveness() {
|
||||||
|
for {
|
||||||
|
if shutdown {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTime := time.Duration(config.Jabber.ServerPingDelay) * 1000 * time.Millisecond
|
||||||
|
sleepTime += time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if !isConnected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пингуем, только если не было никакой активности в течение > config.Jabber.ServerPingDelay,
|
||||||
|
// в худшем случе это будет ~ (config.Jabber.PingSplayDelay * 2) + config.Jabber.PingSplayDelay
|
||||||
|
// if (time.Now().Unix() - lastServerActivity) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
if serverCapsQueried { // Сервер ответил на disco#info
|
||||||
|
var value interface{}
|
||||||
|
var exist bool
|
||||||
|
|
||||||
|
value, exist = serverCapsList.Get("urn:xmpp:ping")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Сервер анонсировал, что умеет в c2s пинги
|
||||||
|
case exist && value.(bool):
|
||||||
|
// Таймаут c2s пинга. Возьмём сумму задержки между пингами, добавим таймаут коннекта и добавим
|
||||||
|
// максимальную корректировку разброса.
|
||||||
|
txTimeout := config.Jabber.ServerPingDelay + config.Jabber.ConnectionTimeout
|
||||||
|
txTimeout += config.Jabber.PingSplayDelay
|
||||||
|
rxTimeout := txTimeout
|
||||||
|
|
||||||
|
rxTimeAgo := time.Now().Unix() - serverPingTimestampRx
|
||||||
|
|
||||||
|
if serverPingTimestampTx > 0 { // Первая пуля от нас ушла...
|
||||||
|
switch {
|
||||||
|
// Давненько мы не получали понгов от сервера, вероятно, соединение с сервером утеряно?
|
||||||
|
case rxTimeAgo > (rxTimeout * 2):
|
||||||
|
log.Debugf(
|
||||||
|
"Stall connection detected. No c2s pong for %d seconds",
|
||||||
|
rxTimeAgo,
|
||||||
|
)
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
// По-умолчанию, мы отправляем c2s пинг
|
||||||
|
default:
|
||||||
|
log.Debugf("Sending c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
|
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
serverPingTimestampTx = time.Now().Unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Первая пуля пока не вылетела, отправляем
|
||||||
|
log.Debugf("Sending first c2s ping from %s to %s", talk.JID(), config.Jabber.Server)
|
||||||
|
|
||||||
|
if err := talk.PingC2S(talk.JID(), config.Jabber.Server); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
} else {
|
||||||
|
serverPingTimestampTx = time.Now().Unix()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сервер не анонсировал, что умеет в c2s пинги
|
||||||
|
default:
|
||||||
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else { // Сервер не ответил на disco#info
|
||||||
|
log.Debug("Sending keepalive whitespace ping")
|
||||||
|
|
||||||
|
if _, err := talk.SendKeepAlive(); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пингует MUC-и, нужно для проверки, что клиент ещё находится в MUC-е
|
||||||
|
func probeMUCLiveness() {
|
||||||
|
for {
|
||||||
|
for _, room := range roomsConnected {
|
||||||
|
var exist bool
|
||||||
|
var lastActivityTs interface{}
|
||||||
|
|
||||||
|
// Если записи про комнату нету, то пинговать её бессмысленно.
|
||||||
|
if lastActivityTs, exist = lastMucActivity.Get(room); !exist {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если время последней активности в чятике не превысило
|
||||||
|
// config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay, ничего не пингуем
|
||||||
|
if (time.Now().Unix() - lastActivityTs.(int64)) < (config.Jabber.ServerPingDelay + config.Jabber.PingSplayDelay) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Пинг MUC-а по сценарию без серверной оптимизации мы реализовывать не будем. Это как-то не надёжно.
|
||||||
|
go func(room string) {
|
||||||
|
// Небольшая рандомная задержка перед пингом комнаты
|
||||||
|
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
if err := talk.PingS2S(talk.JID(), room+"/"+config.Jabber.Nick); err != nil {
|
||||||
|
reEstablishConnection()
|
||||||
|
}
|
||||||
|
}(room)
|
||||||
|
*/
|
||||||
|
|
||||||
|
var roomMap interface{}
|
||||||
|
|
||||||
|
roomMap, exist = mucCapsList.Get(room)
|
||||||
|
|
||||||
|
// Пинги комнаты проводим, только если она записана, как прошедшая disco#info и поддерживающая
|
||||||
|
// Server Optimization
|
||||||
|
if exist && roomMap.(map[string]bool)["http://jabber.org/protocol/muc#self-ping-optimization"] {
|
||||||
|
go func(room string) {
|
||||||
|
// Небольшая рандомная задержка перед пингом комнаты
|
||||||
|
sleepTime := time.Duration(rand.Int63n(1000*config.Jabber.PingSplayDelay)) * time.Millisecond //nolint:gosec
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
|
||||||
|
log.Debugf("Sending MUC ping from %s to %s", talk.JID(), room)
|
||||||
|
|
||||||
|
if err := talk.PingS2S(talk.JID(), room); err != nil {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}(room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Duration(config.Jabber.MucPingDelay) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
157
main.go
Normal file
157
main.go
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/eleksir/go-xmpp"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFormatter(&log.TextFormatter{ //nolint:exhaustruct
|
||||||
|
DisableQuote: true,
|
||||||
|
DisableLevelTruncation: false,
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
TimestampFormat: "2006-01-02 15:04:05",
|
||||||
|
})
|
||||||
|
|
||||||
|
readConfig()
|
||||||
|
|
||||||
|
// no panic
|
||||||
|
switch config.Loglevel {
|
||||||
|
case "fatal":
|
||||||
|
log.SetLevel(log.FatalLevel)
|
||||||
|
case "error":
|
||||||
|
log.SetLevel(log.ErrorLevel)
|
||||||
|
case "warn":
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
case "info":
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
case "debug":
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
case "trace":
|
||||||
|
log.SetLevel(log.TraceLevel)
|
||||||
|
default:
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Пока про capabilities мы ничего не знаем
|
||||||
|
serverCapsQueried = false
|
||||||
|
|
||||||
|
// Мы пока что не пинговали сервер.
|
||||||
|
serverPingTimestampRx = 0
|
||||||
|
serverPingTimestampTx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Откроем лог и скормим его логгеру
|
||||||
|
if config.Log != "" {
|
||||||
|
logfile, err := os.OpenFile(config.Log, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to open log file %s: %s", config.Log, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetOutput(logfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
myLogLevel := log.GetLevel()
|
||||||
|
log.Warnf("Loglevel set to %v", myLogLevel)
|
||||||
|
|
||||||
|
verboseClient := false
|
||||||
|
|
||||||
|
if myLogLevel == log.TraceLevel {
|
||||||
|
verboseClient = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Самое время вгрузить белые и чёрные списки, если что - получим ошибку в лог.
|
||||||
|
readWhitelist()
|
||||||
|
readBlacklist()
|
||||||
|
|
||||||
|
// github.com/mattn/go-xmpp пишет в stdio, нам этого не надо, ловим выхлоп его в logrus с уровнем trace
|
||||||
|
xmpp.DebugWriter = log.WithFields(log.Fields{"logger": "stdlib"}).WriterLevel(log.TraceLevel)
|
||||||
|
|
||||||
|
options = &xmpp.Options{ //nolint:exhaustruct
|
||||||
|
Host: fmt.Sprintf("%s:%d", config.Jabber.Server, config.Jabber.Port),
|
||||||
|
User: config.Jabber.User,
|
||||||
|
Password: config.Jabber.Password,
|
||||||
|
Resource: config.Jabber.Resource,
|
||||||
|
NoTLS: !config.Jabber.Ssl,
|
||||||
|
StartTLS: config.Jabber.StartTLS,
|
||||||
|
TLSConfig: &tls.Config{ //nolint:exhaustruct
|
||||||
|
ServerName: config.Jabber.Server,
|
||||||
|
InsecureSkipVerify: !config.Jabber.SslVerify, //nolint:gosec
|
||||||
|
},
|
||||||
|
InsecureAllowUnencryptedAuth: config.Jabber.InsecureAllowUnencryptedAuth,
|
||||||
|
Debug: verboseClient,
|
||||||
|
Session: false,
|
||||||
|
Status: "xa",
|
||||||
|
StatusMessage: "echo bot is starting up",
|
||||||
|
DialTimeout: 10 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
go sigHandler()
|
||||||
|
signal.Notify(sigChan, os.Interrupt)
|
||||||
|
// Установим коннект
|
||||||
|
establishConnection()
|
||||||
|
|
||||||
|
serverPingTimestampRx = time.Now().Unix() // Считаем, что если коннект запустился, то первый пинг успешен
|
||||||
|
|
||||||
|
// Тыкаем сервер палочкой, проверяем, что коннект жив и переустанавливаем его, если он не жив
|
||||||
|
go probeServerLiveness()
|
||||||
|
|
||||||
|
// Тыкаем muc-и палочкой, проверяем, что они живы и пере-заходим в них, если пинги пропали
|
||||||
|
go probeMUCLiveness()
|
||||||
|
|
||||||
|
// Гребём сообщения...
|
||||||
|
for {
|
||||||
|
// Стриггерилось завершение работы приложения, или соединение не установлено (порвалось, например)
|
||||||
|
// грести не надо
|
||||||
|
if shutdown {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isConnected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
chat, err := talk.Recv()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Unable to get events from server: %s", err)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
// Стрим не читается, он закрылся с той стороны во время чтения
|
||||||
|
case errors.Is(err, io.EOF):
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
// Пытаемся читать закрытый сокет
|
||||||
|
case errors.Is(err, net.ErrClosed):
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
// Не смогли записать в сокет
|
||||||
|
case errors.Is(err, net.ErrWriteToConnected):
|
||||||
|
os.Exit(1)
|
||||||
|
|
||||||
|
// Не сетевая проблема
|
||||||
|
default:
|
||||||
|
// Это уже что-то странное.
|
||||||
|
// Вероятно, ошибка парсинга xml. Собственно, баг сервера, тут мы ничего поделать не можем
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parseEvent(chat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
93
types.go
Normal file
93
types.go
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Конфиг
|
||||||
|
type myConfig struct {
|
||||||
|
Jabber struct {
|
||||||
|
Server string `json:"server,omitempty"`
|
||||||
|
Port int `json:"port,omitempty"`
|
||||||
|
Ssl bool `json:"ssl,omitempty"`
|
||||||
|
StartTLS bool `json:"starttls,omitempty"`
|
||||||
|
SslVerify bool `json:"ssl_verify,omitempty"`
|
||||||
|
InsecureAllowUnencryptedAuth bool `json:"insecureallowunencryptedauth,omitempty"`
|
||||||
|
ConnectionTimeout int64 `json:"connection_timeout,omitempty"`
|
||||||
|
ReconnectDelay int64 `json:"reconnect_delay,omitempty"`
|
||||||
|
ServerPingDelay int64 `json:"server_ping_delay,omitempty"`
|
||||||
|
MucPingDelay int64 `json:"muc_ping_delay,omitempty"`
|
||||||
|
MucRejoinDelay int64 `json:"muc_rejoin_delay,omitempty"`
|
||||||
|
PingSplayDelay int64 `json:"ping_splay_delay,omitempty"`
|
||||||
|
Nick string `json:"nick,omitempty"`
|
||||||
|
Resource string `json:"resource,omitempty"`
|
||||||
|
User string `json:"user,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Channels []string `json:"channels"`
|
||||||
|
OutcastDomains []string `json:"outcast_domains,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
Loglevel string `json:"loglevel,omitempty"`
|
||||||
|
Log string `json:"log,omitempty"`
|
||||||
|
NoEcho bool `json:"noecho,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Белый список
|
||||||
|
type myWhiteList struct {
|
||||||
|
Whitelist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
Jid []string `json:"jid,omitempty"`
|
||||||
|
WipeBans bool `json:"wipe_bans,omitempty"`
|
||||||
|
} `json:"whitelist,omitampty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type myBlackList struct {
|
||||||
|
Blacklist []struct {
|
||||||
|
RoomName string `json:"room_name,omitempty"`
|
||||||
|
JidRe []string `json:"jid_re,omitempty"`
|
||||||
|
} `json:"blacklist,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jabberSimpleIqGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Node string `xml:"node,attr,omitempty"` // для xmlns="http://jabber.org/protocol/disco#items"
|
||||||
|
}
|
||||||
|
|
||||||
|
type jabberPubsubIQGetQuery struct {
|
||||||
|
XMLName xml.Name `xml:"pubsub"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
Items struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
MaxItems string `xml:"max_items,attr"`
|
||||||
|
} `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jabberTimeIqGetQuery struct {
|
||||||
|
// <time xmlns="urn:xmpp:time"/>
|
||||||
|
XMLName xml.Name `xml:"time"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jabberIqPing struct {
|
||||||
|
XMLName xml.Name `xml:"ping"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type jabberIqErrorCancelNotAccepatble struct {
|
||||||
|
XMLName xml.Name `xml:"error"`
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
By string `xml:"by,attr"`
|
||||||
|
NotAcceptable struct {
|
||||||
|
Text string `xml:",chardata"`
|
||||||
|
Xmlns string `xml:"xmlns,attr"`
|
||||||
|
} `xml:"not-acceptable"`
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||||
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
15
vendor/github.com/davecgh/go-spew/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
ISC License
|
||||||
|
|
||||||
|
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
145
vendor/github.com/davecgh/go-spew/spew/bypass.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||||
|
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||||
|
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||||
|
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = false
|
||||||
|
|
||||||
|
// ptrSize is the size of a pointer on the current arch.
|
||||||
|
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
type flag uintptr
|
||||||
|
|
||||||
|
var (
|
||||||
|
// flagRO indicates whether the value field of a reflect.Value
|
||||||
|
// is read-only.
|
||||||
|
flagRO flag
|
||||||
|
|
||||||
|
// flagAddr indicates whether the address of the reflect.Value's
|
||||||
|
// value may be taken.
|
||||||
|
flagAddr flag
|
||||||
|
)
|
||||||
|
|
||||||
|
// flagKindMask holds the bits that make up the kind
|
||||||
|
// part of the flags field. In all the supported versions,
|
||||||
|
// it is in the lower 5 bits.
|
||||||
|
const flagKindMask = flag(0x1f)
|
||||||
|
|
||||||
|
// Different versions of Go have used different
|
||||||
|
// bit layouts for the flags type. This table
|
||||||
|
// records the known combinations.
|
||||||
|
var okFlags = []struct {
|
||||||
|
ro, addr flag
|
||||||
|
}{{
|
||||||
|
// From Go 1.4 to 1.5
|
||||||
|
ro: 1 << 5,
|
||||||
|
addr: 1 << 7,
|
||||||
|
}, {
|
||||||
|
// Up to Go tip.
|
||||||
|
ro: 1<<5 | 1<<6,
|
||||||
|
addr: 1 << 8,
|
||||||
|
}}
|
||||||
|
|
||||||
|
var flagValOffset = func() uintptr {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
return field.Offset
|
||||||
|
}()
|
||||||
|
|
||||||
|
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||||
|
func flagField(v *reflect.Value) *flag {
|
||||||
|
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||||
|
// the typical safety restrictions preventing access to unaddressable and
|
||||||
|
// unexported data. It works by digging the raw pointer to the underlying
|
||||||
|
// value out of the protected value and generating a new unprotected (unsafe)
|
||||||
|
// reflect.Value to it.
|
||||||
|
//
|
||||||
|
// This allows us to check for implementations of the Stringer and error
|
||||||
|
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||||
|
// inaccessible values such as unexported struct fields.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
flagFieldPtr := flagField(&v)
|
||||||
|
*flagFieldPtr &^= flagRO
|
||||||
|
*flagFieldPtr |= flagAddr
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity checks against future reflect package changes
|
||||||
|
// to the type or semantics of the Value.flag field.
|
||||||
|
func init() {
|
||||||
|
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||||
|
if !ok {
|
||||||
|
panic("reflect.Value has no flag field")
|
||||||
|
}
|
||||||
|
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||||
|
panic("reflect.Value flag field has changed kind")
|
||||||
|
}
|
||||||
|
type t0 int
|
||||||
|
var t struct {
|
||||||
|
A t0
|
||||||
|
// t0 will have flagEmbedRO set.
|
||||||
|
t0
|
||||||
|
// a will have flagStickyRO set
|
||||||
|
a t0
|
||||||
|
}
|
||||||
|
vA := reflect.ValueOf(t).FieldByName("A")
|
||||||
|
va := reflect.ValueOf(t).FieldByName("a")
|
||||||
|
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||||
|
|
||||||
|
// Infer flagRO from the difference between the flags
|
||||||
|
// for the (otherwise identical) fields in t.
|
||||||
|
flagPublic := *flagField(&vA)
|
||||||
|
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||||
|
flagRO = flagPublic ^ flagWithRO
|
||||||
|
|
||||||
|
// Infer flagAddr from the difference between a value
|
||||||
|
// taken from a pointer and not.
|
||||||
|
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||||
|
flagNoPtr := *flagField(&vA)
|
||||||
|
flagPtr := *flagField(&vPtrA)
|
||||||
|
flagAddr = flagNoPtr ^ flagPtr
|
||||||
|
|
||||||
|
// Check that the inferred flags tally with one of the known versions.
|
||||||
|
for _, f := range okFlags {
|
||||||
|
if flagRO == f.ro && flagAddr == f.addr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic("reflect.Value read-only flag has changed semantics")
|
||||||
|
}
|
||||||
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
38
vendor/github.com/davecgh/go-spew/spew/bypasssafe.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||||
|
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||||
|
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||||
|
// tag is deprecated and thus should not be used.
|
||||||
|
// +build js appengine safe disableunsafe !go1.4
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import "reflect"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||||
|
// not access to the unsafe package is available.
|
||||||
|
UnsafeDisabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||||
|
// that bypasses the typical safety restrictions preventing access to
|
||||||
|
// unaddressable and unexported data. However, doing this relies on access to
|
||||||
|
// the unsafe package. This is a stub version which simply returns the passed
|
||||||
|
// reflect.Value when the unsafe package is not available.
|
||||||
|
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||||
|
return v
|
||||||
|
}
|
||||||
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
341
vendor/github.com/davecgh/go-spew/spew/common.go
generated
vendored
Normal file
@@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||||
|
// the technique used in the fmt package.
|
||||||
|
var (
|
||||||
|
panicBytes = []byte("(PANIC=")
|
||||||
|
plusBytes = []byte("+")
|
||||||
|
iBytes = []byte("i")
|
||||||
|
trueBytes = []byte("true")
|
||||||
|
falseBytes = []byte("false")
|
||||||
|
interfaceBytes = []byte("(interface {})")
|
||||||
|
commaNewlineBytes = []byte(",\n")
|
||||||
|
newlineBytes = []byte("\n")
|
||||||
|
openBraceBytes = []byte("{")
|
||||||
|
openBraceNewlineBytes = []byte("{\n")
|
||||||
|
closeBraceBytes = []byte("}")
|
||||||
|
asteriskBytes = []byte("*")
|
||||||
|
colonBytes = []byte(":")
|
||||||
|
colonSpaceBytes = []byte(": ")
|
||||||
|
openParenBytes = []byte("(")
|
||||||
|
closeParenBytes = []byte(")")
|
||||||
|
spaceBytes = []byte(" ")
|
||||||
|
pointerChainBytes = []byte("->")
|
||||||
|
nilAngleBytes = []byte("<nil>")
|
||||||
|
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||||
|
maxShortBytes = []byte("<max>")
|
||||||
|
circularBytes = []byte("<already shown>")
|
||||||
|
circularShortBytes = []byte("<shown>")
|
||||||
|
invalidAngleBytes = []byte("<invalid>")
|
||||||
|
openBracketBytes = []byte("[")
|
||||||
|
closeBracketBytes = []byte("]")
|
||||||
|
percentBytes = []byte("%")
|
||||||
|
precisionBytes = []byte(".")
|
||||||
|
openAngleBytes = []byte("<")
|
||||||
|
closeAngleBytes = []byte(">")
|
||||||
|
openMapBytes = []byte("map[")
|
||||||
|
closeMapBytes = []byte("]")
|
||||||
|
lenEqualsBytes = []byte("len=")
|
||||||
|
capEqualsBytes = []byte("cap=")
|
||||||
|
)
|
||||||
|
|
||||||
|
// hexDigits is used to map a decimal value to a hex digit.
|
||||||
|
var hexDigits = "0123456789abcdef"
|
||||||
|
|
||||||
|
// catchPanic handles any panics that might occur during the handleMethods
|
||||||
|
// calls.
|
||||||
|
func catchPanic(w io.Writer, v reflect.Value) {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
w.Write(panicBytes)
|
||||||
|
fmt.Fprintf(w, "%v", err)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleMethods attempts to call the Error and String methods on the underlying
|
||||||
|
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||||
|
//
|
||||||
|
// It handles panics in any called methods by catching and displaying the error
|
||||||
|
// as the formatted value.
|
||||||
|
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||||
|
// We need an interface to check if the type implements the error or
|
||||||
|
// Stringer interface. However, the reflect package won't give us an
|
||||||
|
// interface on certain things like unexported struct fields in order
|
||||||
|
// to enforce visibility rules. We use unsafe, when it's available,
|
||||||
|
// to bypass these restrictions since this package does not mutate the
|
||||||
|
// values.
|
||||||
|
if !v.CanInterface() {
|
||||||
|
if UnsafeDisabled {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Choose whether or not to do error and Stringer interface lookups against
|
||||||
|
// the base type or a pointer to the base type depending on settings.
|
||||||
|
// Technically calling one of these methods with a pointer receiver can
|
||||||
|
// mutate the value, however, types which choose to satisify an error or
|
||||||
|
// Stringer interface with a pointer receiver should not be mutating their
|
||||||
|
// state inside these interface methods.
|
||||||
|
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||||
|
v = unsafeReflectValue(v)
|
||||||
|
}
|
||||||
|
if v.CanAddr() {
|
||||||
|
v = v.Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is it an error or Stringer?
|
||||||
|
switch iface := v.Interface().(type) {
|
||||||
|
case error:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(iface.Error()))
|
||||||
|
return true
|
||||||
|
|
||||||
|
case fmt.Stringer:
|
||||||
|
defer catchPanic(w, v)
|
||||||
|
if cs.ContinueOnMethod {
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
w.Write([]byte(iface.String()))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// printBool outputs a boolean value as true or false to Writer w.
|
||||||
|
func printBool(w io.Writer, val bool) {
|
||||||
|
if val {
|
||||||
|
w.Write(trueBytes)
|
||||||
|
} else {
|
||||||
|
w.Write(falseBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// printInt outputs a signed integer value to Writer w.
|
||||||
|
func printInt(w io.Writer, val int64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printUint outputs an unsigned integer value to Writer w.
|
||||||
|
func printUint(w io.Writer, val uint64, base int) {
|
||||||
|
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printFloat outputs a floating point value using the specified precision,
|
||||||
|
// which is expected to be 32 or 64bit, to Writer w.
|
||||||
|
func printFloat(w io.Writer, val float64, precision int) {
|
||||||
|
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// printComplex outputs a complex value using the specified float precision
|
||||||
|
// for the real and imaginary parts to Writer w.
|
||||||
|
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||||
|
r := real(c)
|
||||||
|
w.Write(openParenBytes)
|
||||||
|
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||||
|
i := imag(c)
|
||||||
|
if i >= 0 {
|
||||||
|
w.Write(plusBytes)
|
||||||
|
}
|
||||||
|
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||||
|
w.Write(iBytes)
|
||||||
|
w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||||
|
// prefix to Writer w.
|
||||||
|
func printHexPtr(w io.Writer, p uintptr) {
|
||||||
|
// Null pointer.
|
||||||
|
num := uint64(p)
|
||||||
|
if num == 0 {
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||||
|
buf := make([]byte, 18)
|
||||||
|
|
||||||
|
// It's simpler to construct the hex string right to left.
|
||||||
|
base := uint64(16)
|
||||||
|
i := len(buf) - 1
|
||||||
|
for num >= base {
|
||||||
|
buf[i] = hexDigits[num%base]
|
||||||
|
num /= base
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
buf[i] = hexDigits[num]
|
||||||
|
|
||||||
|
// Add '0x' prefix.
|
||||||
|
i--
|
||||||
|
buf[i] = 'x'
|
||||||
|
i--
|
||||||
|
buf[i] = '0'
|
||||||
|
|
||||||
|
// Strip unused leading bytes.
|
||||||
|
buf = buf[i:]
|
||||||
|
w.Write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||||
|
// elements to be sorted.
|
||||||
|
type valuesSorter struct {
|
||||||
|
values []reflect.Value
|
||||||
|
strings []string // either nil or same len and values
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||||
|
// surrogate keys on which the data should be sorted. It uses flags in
|
||||||
|
// ConfigState to decide if and how to populate those surrogate keys.
|
||||||
|
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||||
|
vs := &valuesSorter{values: values, cs: cs}
|
||||||
|
if canSortSimply(vs.values[0].Kind()) {
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
if !cs.DisableMethods {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if !handleMethods(cs, &b, vs.values[i]) {
|
||||||
|
vs.strings = nil
|
||||||
|
break
|
||||||
|
}
|
||||||
|
vs.strings[i] = b.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if vs.strings == nil && cs.SpewKeys {
|
||||||
|
vs.strings = make([]string, len(values))
|
||||||
|
for i := range vs.values {
|
||||||
|
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||||
|
// directly, or whether it should be considered for sorting by surrogate keys
|
||||||
|
// (if the ConfigState allows it).
|
||||||
|
func canSortSimply(kind reflect.Kind) bool {
|
||||||
|
// This switch parallels valueSortLess, except for the default case.
|
||||||
|
switch kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
return true
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return true
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return true
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return true
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return true
|
||||||
|
case reflect.Array:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Len returns the number of values in the slice. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Len() int {
|
||||||
|
return len(s.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Swap swaps the values at the passed indices. It is part of the
|
||||||
|
// sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Swap(i, j int) {
|
||||||
|
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||||
|
if s.strings != nil {
|
||||||
|
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// valueSortLess returns whether the first value should sort before the second
|
||||||
|
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||||
|
// implementation.
|
||||||
|
func valueSortLess(a, b reflect.Value) bool {
|
||||||
|
switch a.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !a.Bool() && b.Bool()
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
return a.Int() < b.Int()
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return a.Float() < b.Float()
|
||||||
|
case reflect.String:
|
||||||
|
return a.String() < b.String()
|
||||||
|
case reflect.Uintptr:
|
||||||
|
return a.Uint() < b.Uint()
|
||||||
|
case reflect.Array:
|
||||||
|
// Compare the contents of both arrays.
|
||||||
|
l := a.Len()
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
av := a.Index(i)
|
||||||
|
bv := b.Index(i)
|
||||||
|
if av.Interface() == bv.Interface() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return valueSortLess(av, bv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return a.String() < b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Less returns whether the value at index i should sort before the
|
||||||
|
// value at index j. It is part of the sort.Interface implementation.
|
||||||
|
func (s *valuesSorter) Less(i, j int) bool {
|
||||||
|
if s.strings == nil {
|
||||||
|
return valueSortLess(s.values[i], s.values[j])
|
||||||
|
}
|
||||||
|
return s.strings[i] < s.strings[j]
|
||||||
|
}
|
||||||
|
|
||||||
|
// sortValues is a sort function that handles both native types and any type that
|
||||||
|
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||||
|
// their Value.String() value to ensure display stability.
|
||||||
|
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sort.Sort(newValuesSorter(values, cs))
|
||||||
|
}
|
||||||
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
306
vendor/github.com/davecgh/go-spew/spew/config.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigState houses the configuration options used by spew to format and
|
||||||
|
// display values. There is a global instance, Config, that is used to control
|
||||||
|
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||||
|
// provides methods equivalent to the top-level functions.
|
||||||
|
//
|
||||||
|
// The zero value for ConfigState provides no indentation. You would typically
|
||||||
|
// want to set it to a space or a tab.
|
||||||
|
//
|
||||||
|
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||||
|
// with default settings. See the documentation of NewDefaultConfig for default
|
||||||
|
// values.
|
||||||
|
type ConfigState struct {
|
||||||
|
// Indent specifies the string to use for each indentation level. The
|
||||||
|
// global config instance that all top-level functions use set this to a
|
||||||
|
// single space by default. If you would like more indentation, you might
|
||||||
|
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||||
|
Indent string
|
||||||
|
|
||||||
|
// MaxDepth controls the maximum number of levels to descend into nested
|
||||||
|
// data structures. The default, 0, means there is no limit.
|
||||||
|
//
|
||||||
|
// NOTE: Circular data structures are properly detected, so it is not
|
||||||
|
// necessary to set this value unless you specifically want to limit deeply
|
||||||
|
// nested data structures.
|
||||||
|
MaxDepth int
|
||||||
|
|
||||||
|
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||||
|
// invoked for types that implement them.
|
||||||
|
DisableMethods bool
|
||||||
|
|
||||||
|
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||||
|
// error and Stringer interfaces on types which only accept a pointer
|
||||||
|
// receiver when the current type is not a pointer.
|
||||||
|
//
|
||||||
|
// NOTE: This might be an unsafe action since calling one of these methods
|
||||||
|
// with a pointer receiver could technically mutate the value, however,
|
||||||
|
// in practice, types which choose to satisify an error or Stringer
|
||||||
|
// interface with a pointer receiver should not be mutating their state
|
||||||
|
// inside these interface methods. As a result, this option relies on
|
||||||
|
// access to the unsafe package, so it will not have any effect when
|
||||||
|
// running in environments without access to the unsafe package such as
|
||||||
|
// Google App Engine or with the "safe" build tag specified.
|
||||||
|
DisablePointerMethods bool
|
||||||
|
|
||||||
|
// DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
// pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
DisablePointerAddresses bool
|
||||||
|
|
||||||
|
// DisableCapacities specifies whether to disable the printing of capacities
|
||||||
|
// for arrays, slices, maps and channels. This is useful when diffing
|
||||||
|
// data structures in tests.
|
||||||
|
DisableCapacities bool
|
||||||
|
|
||||||
|
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||||
|
// a custom error or Stringer interface is invoked. The default, false,
|
||||||
|
// means it will print the results of invoking the custom error or Stringer
|
||||||
|
// interface and return immediately instead of continuing to recurse into
|
||||||
|
// the internals of the data type.
|
||||||
|
//
|
||||||
|
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||||
|
// via the DisableMethods or DisablePointerMethods options.
|
||||||
|
ContinueOnMethod bool
|
||||||
|
|
||||||
|
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||||
|
// this to have a more deterministic, diffable output. Note that only
|
||||||
|
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||||
|
// that support the error or Stringer interfaces (if methods are
|
||||||
|
// enabled) are supported, with other types sorted according to the
|
||||||
|
// reflect.Value.String() output which guarantees display stability.
|
||||||
|
SortKeys bool
|
||||||
|
|
||||||
|
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||||
|
// be spewed to strings and sorted by those strings. This is only
|
||||||
|
// considered if SortKeys is true.
|
||||||
|
SpewKeys bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the active configuration of the top-level functions.
|
||||||
|
// The configuration can be changed by modifying the contents of spew.Config.
|
||||||
|
var Config = ConfigState{Indent: " "}
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the formatted string as a value that satisfies error. See NewFormatter
|
||||||
|
// for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||||
|
// the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||||
|
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(c.convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
c.Printf, c.Println, or c.Printf.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(c, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(c, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by modifying the public members
|
||||||
|
of c. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func (c *ConfigState) Dump(a ...interface{}) {
|
||||||
|
fdump(c, os.Stdout, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(c, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a spew Formatter interface using
|
||||||
|
// the ConfigState associated with s.
|
||||||
|
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = newFormatter(c, arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||||
|
//
|
||||||
|
// Indent: " "
|
||||||
|
// MaxDepth: 0
|
||||||
|
// DisableMethods: false
|
||||||
|
// DisablePointerMethods: false
|
||||||
|
// ContinueOnMethod: false
|
||||||
|
// SortKeys: false
|
||||||
|
func NewDefaultConfig() *ConfigState {
|
||||||
|
return &ConfigState{Indent: " "}
|
||||||
|
}
|
||||||
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
211
vendor/github.com/davecgh/go-spew/spew/doc.go
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||||
|
debugging.
|
||||||
|
|
||||||
|
A quick overview of the additional features spew provides over the built-in
|
||||||
|
printing facilities for Go data types are as follows:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output (only when using
|
||||||
|
Dump style)
|
||||||
|
|
||||||
|
There are two different approaches spew allows for dumping Go data structures:
|
||||||
|
|
||||||
|
* Dump style which prints with newlines, customizable indentation,
|
||||||
|
and additional debug information such as types and all pointer addresses
|
||||||
|
used to indirect to the final value
|
||||||
|
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||||
|
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||||
|
similar to the default %v while providing the additional functionality
|
||||||
|
outlined above and passing unsupported format verbs such as %x and %q
|
||||||
|
along to fmt
|
||||||
|
|
||||||
|
Quick Start
|
||||||
|
|
||||||
|
This section demonstrates how to quickly get started with spew. See the
|
||||||
|
sections below for further details on formatting and configuration options.
|
||||||
|
|
||||||
|
To dump a variable with full newlines, indentation, type, and pointer
|
||||||
|
information use Dump, Fdump, or Sdump:
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||||
|
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||||
|
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||||
|
%#+v (adds types and pointer addresses):
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
Configuration Options
|
||||||
|
|
||||||
|
Configuration of spew is handled by fields in the ConfigState type. For
|
||||||
|
convenience, all of the top-level functions use a global state available
|
||||||
|
via the spew.Config global.
|
||||||
|
|
||||||
|
It is also possible to create a ConfigState instance that provides methods
|
||||||
|
equivalent to the top-level functions. This allows concurrent configuration
|
||||||
|
options. See the ConfigState documentation for more details.
|
||||||
|
|
||||||
|
The following configuration options are available:
|
||||||
|
* Indent
|
||||||
|
String to use for each indentation level for Dump functions.
|
||||||
|
It is a single space by default. A popular alternative is "\t".
|
||||||
|
|
||||||
|
* MaxDepth
|
||||||
|
Maximum number of levels to descend into nested data structures.
|
||||||
|
There is no limit by default.
|
||||||
|
|
||||||
|
* DisableMethods
|
||||||
|
Disables invocation of error and Stringer interface methods.
|
||||||
|
Method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerMethods
|
||||||
|
Disables invocation of error and Stringer interface methods on types
|
||||||
|
which only accept pointer receivers from non-pointer variables.
|
||||||
|
Pointer method invocation is enabled by default.
|
||||||
|
|
||||||
|
* DisablePointerAddresses
|
||||||
|
DisablePointerAddresses specifies whether to disable the printing of
|
||||||
|
pointer addresses. This is useful when diffing data structures in tests.
|
||||||
|
|
||||||
|
* DisableCapacities
|
||||||
|
DisableCapacities specifies whether to disable the printing of
|
||||||
|
capacities for arrays, slices, maps and channels. This is useful when
|
||||||
|
diffing data structures in tests.
|
||||||
|
|
||||||
|
* ContinueOnMethod
|
||||||
|
Enables recursion into types after invoking error and Stringer interface
|
||||||
|
methods. Recursion after method invocation is disabled by default.
|
||||||
|
|
||||||
|
* SortKeys
|
||||||
|
Specifies map keys should be sorted before being printed. Use
|
||||||
|
this to have a more deterministic, diffable output. Note that
|
||||||
|
only native types (bool, int, uint, floats, uintptr and string)
|
||||||
|
and types which implement error or Stringer interfaces are
|
||||||
|
supported with other types sorted according to the
|
||||||
|
reflect.Value.String() output which guarantees display
|
||||||
|
stability. Natural map order is used by default.
|
||||||
|
|
||||||
|
* SpewKeys
|
||||||
|
Specifies that, as a last resort attempt, map keys should be
|
||||||
|
spewed to strings and sorted by those strings. This is only
|
||||||
|
considered if SortKeys is true.
|
||||||
|
|
||||||
|
Dump Usage
|
||||||
|
|
||||||
|
Simply call spew.Dump with a list of variables you want to dump:
|
||||||
|
|
||||||
|
spew.Dump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||||
|
io.Writer. For example, to dump to standard error:
|
||||||
|
|
||||||
|
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||||
|
|
||||||
|
str := spew.Sdump(myVar1, myVar2, ...)
|
||||||
|
|
||||||
|
Sample Dump Output
|
||||||
|
|
||||||
|
See the Dump example for details on the setup of the types and variables being
|
||||||
|
shown here.
|
||||||
|
|
||||||
|
(main.Foo) {
|
||||||
|
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||||
|
flag: (main.Flag) flagTwo,
|
||||||
|
data: (uintptr) <nil>
|
||||||
|
}),
|
||||||
|
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||||
|
(string) (len=3) "one": (bool) true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||||
|
command as shown.
|
||||||
|
([]uint8) (len=32 cap=32) {
|
||||||
|
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||||
|
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||||
|
00000020 31 32 |12|
|
||||||
|
}
|
||||||
|
|
||||||
|
Custom Formatter
|
||||||
|
|
||||||
|
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||||
|
so that it integrates cleanly with standard fmt package printing functions. The
|
||||||
|
formatter is useful for inline printing of smaller data types similar to the
|
||||||
|
standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Custom Formatter Usage
|
||||||
|
|
||||||
|
The simplest way to make use of the spew custom formatter is to call one of the
|
||||||
|
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||||
|
functions have syntax you are most likely already familiar with:
|
||||||
|
|
||||||
|
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
spew.Println(myVar, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||||
|
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||||
|
|
||||||
|
See the Index for the full list convenience functions.
|
||||||
|
|
||||||
|
Sample Formatter Output
|
||||||
|
|
||||||
|
Double pointer to a uint8:
|
||||||
|
%v: <**>5
|
||||||
|
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||||
|
%#v: (**uint8)5
|
||||||
|
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||||
|
|
||||||
|
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||||
|
%v: <*>{1 <*><shown>}
|
||||||
|
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||||
|
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||||
|
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||||
|
|
||||||
|
See the Printf example for details on the setup of variables being shown
|
||||||
|
here.
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||||
|
detects them and handles them internally by printing the panic information
|
||||||
|
inline with the output. Since spew is intended to provide deep pretty printing
|
||||||
|
capabilities on structures, it intentionally does not return any errors.
|
||||||
|
*/
|
||||||
|
package spew
|
||||||
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
509
vendor/github.com/davecgh/go-spew/spew/dump.go
generated
vendored
Normal file
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||||
|
// convert cgo types to uint8 slices for hexdumping.
|
||||||
|
uint8Type = reflect.TypeOf(uint8(0))
|
||||||
|
|
||||||
|
// cCharRE is a regular expression that matches a cgo char.
|
||||||
|
// It is used to detect character arrays to hexdump them.
|
||||||
|
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||||
|
|
||||||
|
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||||
|
// char. It is used to detect unsigned character arrays to hexdump
|
||||||
|
// them.
|
||||||
|
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||||
|
|
||||||
|
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||||
|
// It is used to detect uint8_t arrays to hexdump them.
|
||||||
|
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||||
|
)
|
||||||
|
|
||||||
|
// dumpState contains information about the state of a dump operation.
|
||||||
|
type dumpState struct {
|
||||||
|
w io.Writer
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
ignoreNextIndent bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// indent performs indentation according to the depth level and cs.Indent
|
||||||
|
// option.
|
||||||
|
func (d *dumpState) indent() {
|
||||||
|
if d.ignoreNextIndent {
|
||||||
|
d.ignoreNextIndent = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range d.pointers {
|
||||||
|
if depth >= d.depth {
|
||||||
|
delete(d.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by dereferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
d.pointers[addr] = d.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type information.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
d.w.Write([]byte(ve.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
|
||||||
|
// Display pointer information.
|
||||||
|
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
d.w.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(d.w, addr)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
d.w.Write(circularBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
d.ignoreNextType = true
|
||||||
|
d.dump(ve)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||||
|
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||||
|
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||||
|
// Determine whether this type should be hex dumped or not. Also,
|
||||||
|
// for types which should be hexdumped, try to use the underlying data
|
||||||
|
// first, then fall back to trying to convert them to a uint8 slice.
|
||||||
|
var buf []uint8
|
||||||
|
doConvert := false
|
||||||
|
doHexDump := false
|
||||||
|
numEntries := v.Len()
|
||||||
|
if numEntries > 0 {
|
||||||
|
vt := v.Index(0).Type()
|
||||||
|
vts := vt.String()
|
||||||
|
switch {
|
||||||
|
// C types that need to be converted.
|
||||||
|
case cCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUnsignedCharRE.MatchString(vts):
|
||||||
|
fallthrough
|
||||||
|
case cUint8tCharRE.MatchString(vts):
|
||||||
|
doConvert = true
|
||||||
|
|
||||||
|
// Try to use existing uint8 slices and fall back to converting
|
||||||
|
// and copying if that fails.
|
||||||
|
case vt.Kind() == reflect.Uint8:
|
||||||
|
// We need an addressable interface to convert the type
|
||||||
|
// to a byte slice. However, the reflect package won't
|
||||||
|
// give us an interface on certain things like
|
||||||
|
// unexported struct fields in order to enforce
|
||||||
|
// visibility rules. We use unsafe, when available, to
|
||||||
|
// bypass these restrictions since this package does not
|
||||||
|
// mutate the values.
|
||||||
|
vs := v
|
||||||
|
if !vs.CanInterface() || !vs.CanAddr() {
|
||||||
|
vs = unsafeReflectValue(vs)
|
||||||
|
}
|
||||||
|
if !UnsafeDisabled {
|
||||||
|
vs = vs.Slice(0, numEntries)
|
||||||
|
|
||||||
|
// Use the existing uint8 slice if it can be
|
||||||
|
// type asserted.
|
||||||
|
iface := vs.Interface()
|
||||||
|
if slice, ok := iface.([]uint8); ok {
|
||||||
|
buf = slice
|
||||||
|
doHexDump = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The underlying data needs to be converted if it can't
|
||||||
|
// be type asserted to a uint8 slice.
|
||||||
|
doConvert = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy and convert the underlying type if needed.
|
||||||
|
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||||
|
// Convert and copy each element into a uint8 byte
|
||||||
|
// slice.
|
||||||
|
buf = make([]uint8, numEntries)
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
vv := v.Index(i)
|
||||||
|
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||||
|
}
|
||||||
|
doHexDump = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hexdump the entire slice as needed.
|
||||||
|
if doHexDump {
|
||||||
|
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||||
|
str := indent + hex.Dump(buf)
|
||||||
|
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||||
|
str = strings.TrimRight(str, d.cs.Indent)
|
||||||
|
d.w.Write([]byte(str))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively call dump for each item.
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
d.dump(d.unpackValue(v.Index(i)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||||
|
// value to figure out what kind of object we are dealing with and formats it
|
||||||
|
// appropriately. It is a recursive function, however circular data structures
|
||||||
|
// are detected and handled properly.
|
||||||
|
func (d *dumpState) dump(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
d.w.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
d.indent()
|
||||||
|
d.dumpPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !d.ignoreNextType {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
d.w.Write([]byte(v.Type().String()))
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.ignoreNextType = false
|
||||||
|
|
||||||
|
// Display length and capacity if the built-in len and cap functions
|
||||||
|
// work with the value's kind and the len/cap itself is non-zero.
|
||||||
|
valueLen, valueCap := 0, 0
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||||
|
valueLen, valueCap = v.Len(), v.Cap()
|
||||||
|
case reflect.Map, reflect.String:
|
||||||
|
valueLen = v.Len()
|
||||||
|
}
|
||||||
|
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
d.w.Write(openParenBytes)
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(lenEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueLen), 10)
|
||||||
|
}
|
||||||
|
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||||
|
if valueLen != 0 {
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
d.w.Write(capEqualsBytes)
|
||||||
|
printInt(d.w, int64(valueCap), 10)
|
||||||
|
}
|
||||||
|
d.w.Write(closeParenBytes)
|
||||||
|
d.w.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||||
|
// is enabled
|
||||||
|
if !d.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(d.w, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(d.w, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(d.w, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(d.w, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(d.w, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(d.w, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(d.w, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.dumpSlice(v)
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
d.w.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if d.cs.SortKeys {
|
||||||
|
sortValues(keys, d.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
d.dump(d.unpackValue(key))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||||
|
if i < (numEntries - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
d.w.Write(openBraceNewlineBytes)
|
||||||
|
d.depth++
|
||||||
|
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(maxNewlineBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
numFields := v.NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
d.indent()
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
d.w.Write([]byte(vtf.Name))
|
||||||
|
d.w.Write(colonSpaceBytes)
|
||||||
|
d.ignoreNextIndent = true
|
||||||
|
d.dump(d.unpackValue(v.Field(i)))
|
||||||
|
if i < (numFields - 1) {
|
||||||
|
d.w.Write(commaNewlineBytes)
|
||||||
|
} else {
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d.depth--
|
||||||
|
d.indent()
|
||||||
|
d.w.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(d.w, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(d.w, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it in case any new
|
||||||
|
// types are added.
|
||||||
|
default:
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(d.w, "%v", v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fdump is a helper function to consolidate the logic from the various public
|
||||||
|
// methods which take varying writers and config states.
|
||||||
|
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||||
|
for _, arg := range a {
|
||||||
|
if arg == nil {
|
||||||
|
w.Write(interfaceBytes)
|
||||||
|
w.Write(spaceBytes)
|
||||||
|
w.Write(nilAngleBytes)
|
||||||
|
w.Write(newlineBytes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
d := dumpState{w: w, cs: cs}
|
||||||
|
d.pointers = make(map[uintptr]int)
|
||||||
|
d.dump(reflect.ValueOf(arg))
|
||||||
|
d.w.Write(newlineBytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||||
|
// exactly the same as Dump.
|
||||||
|
func Fdump(w io.Writer, a ...interface{}) {
|
||||||
|
fdump(&Config, w, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||||
|
// as Dump.
|
||||||
|
func Sdump(a ...interface{}) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
fdump(&Config, &buf, a...)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Dump displays the passed parameters to standard out with newlines, customizable
|
||||||
|
indentation, and additional debug information such as complete types and all
|
||||||
|
pointer addresses used to indirect to the final value. It provides the
|
||||||
|
following features over the built-in printing facilities provided by the fmt
|
||||||
|
package:
|
||||||
|
|
||||||
|
* Pointers are dereferenced and followed
|
||||||
|
* Circular data structures are detected and handled properly
|
||||||
|
* Custom Stringer/error interfaces are optionally invoked, including
|
||||||
|
on unexported types
|
||||||
|
* Custom types which only implement the Stringer/error interfaces via
|
||||||
|
a pointer receiver are optionally invoked when passing non-pointer
|
||||||
|
variables
|
||||||
|
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||||
|
includes offsets, byte values in hex, and ASCII output
|
||||||
|
|
||||||
|
The configuration options are controlled by an exported package global,
|
||||||
|
spew.Config. See ConfigState for options documentation.
|
||||||
|
|
||||||
|
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||||
|
get the formatted result as a string.
|
||||||
|
*/
|
||||||
|
func Dump(a ...interface{}) {
|
||||||
|
fdump(&Config, os.Stdout, a...)
|
||||||
|
}
|
||||||
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
419
vendor/github.com/davecgh/go-spew/spew/format.go
generated
vendored
Normal file
@@ -0,0 +1,419 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||||
|
const supportedFlags = "0-+# "
|
||||||
|
|
||||||
|
// formatState implements the fmt.Formatter interface and contains information
|
||||||
|
// about the state of a formatting operation. The NewFormatter function can
|
||||||
|
// be used to get a new Formatter which can be used directly as arguments
|
||||||
|
// in standard fmt package printing calls.
|
||||||
|
type formatState struct {
|
||||||
|
value interface{}
|
||||||
|
fs fmt.State
|
||||||
|
depth int
|
||||||
|
pointers map[uintptr]int
|
||||||
|
ignoreNextType bool
|
||||||
|
cs *ConfigState
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildDefaultFormat recreates the original format string without precision
|
||||||
|
// and width information to pass in to fmt.Sprintf in the case of an
|
||||||
|
// unrecognized type. Unless new types are added to the language, this
|
||||||
|
// function won't ever be called.
|
||||||
|
func (f *formatState) buildDefaultFormat() (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune('v')
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructOrigFormat recreates the original format string including precision
|
||||||
|
// and width information to pass along to the standard fmt package. This allows
|
||||||
|
// automatic deferral of all format strings this package doesn't support.
|
||||||
|
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||||
|
buf := bytes.NewBuffer(percentBytes)
|
||||||
|
|
||||||
|
for _, flag := range supportedFlags {
|
||||||
|
if f.fs.Flag(int(flag)) {
|
||||||
|
buf.WriteRune(flag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if width, ok := f.fs.Width(); ok {
|
||||||
|
buf.WriteString(strconv.Itoa(width))
|
||||||
|
}
|
||||||
|
|
||||||
|
if precision, ok := f.fs.Precision(); ok {
|
||||||
|
buf.Write(precisionBytes)
|
||||||
|
buf.WriteString(strconv.Itoa(precision))
|
||||||
|
}
|
||||||
|
|
||||||
|
buf.WriteRune(verb)
|
||||||
|
|
||||||
|
format = buf.String()
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||||
|
// ensures that types for values which have been unpacked from an interface
|
||||||
|
// are displayed when the show types flag is also set.
|
||||||
|
// This is useful for data types like structs, arrays, slices, and maps which
|
||||||
|
// can contain varying types packed inside an interface.
|
||||||
|
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||||
|
if v.Kind() == reflect.Interface {
|
||||||
|
f.ignoreNextType = false
|
||||||
|
if !v.IsNil() {
|
||||||
|
v = v.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||||
|
func (f *formatState) formatPtr(v reflect.Value) {
|
||||||
|
// Display nil if top level pointer is nil.
|
||||||
|
showTypes := f.fs.Flag('#')
|
||||||
|
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove pointers at or below the current depth from map used to detect
|
||||||
|
// circular refs.
|
||||||
|
for k, depth := range f.pointers {
|
||||||
|
if depth >= f.depth {
|
||||||
|
delete(f.pointers, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep list of all dereferenced pointers to possibly show later.
|
||||||
|
pointerChain := make([]uintptr, 0)
|
||||||
|
|
||||||
|
// Figure out how many levels of indirection there are by derferencing
|
||||||
|
// pointers and unpacking interfaces down the chain while detecting circular
|
||||||
|
// references.
|
||||||
|
nilFound := false
|
||||||
|
cycleFound := false
|
||||||
|
indirects := 0
|
||||||
|
ve := v
|
||||||
|
for ve.Kind() == reflect.Ptr {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indirects++
|
||||||
|
addr := ve.Pointer()
|
||||||
|
pointerChain = append(pointerChain, addr)
|
||||||
|
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||||
|
cycleFound = true
|
||||||
|
indirects--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
f.pointers[addr] = f.depth
|
||||||
|
|
||||||
|
ve = ve.Elem()
|
||||||
|
if ve.Kind() == reflect.Interface {
|
||||||
|
if ve.IsNil() {
|
||||||
|
nilFound = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ve = ve.Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display type or indirection level depending on flags.
|
||||||
|
if showTypes && !f.ignoreNextType {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||||
|
f.fs.Write([]byte(ve.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
} else {
|
||||||
|
if nilFound || cycleFound {
|
||||||
|
indirects += strings.Count(ve.Type().String(), "*")
|
||||||
|
}
|
||||||
|
f.fs.Write(openAngleBytes)
|
||||||
|
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||||
|
f.fs.Write(closeAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display pointer information depending on flags.
|
||||||
|
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
for i, addr := range pointerChain {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(pointerChainBytes)
|
||||||
|
}
|
||||||
|
printHexPtr(f.fs, addr)
|
||||||
|
}
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display dereferenced value.
|
||||||
|
switch {
|
||||||
|
case nilFound:
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
|
||||||
|
case cycleFound:
|
||||||
|
f.fs.Write(circularShortBytes)
|
||||||
|
|
||||||
|
default:
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(ve)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format is the main workhorse for providing the Formatter interface. It
|
||||||
|
// uses the passed reflect value to figure out what kind of object we are
|
||||||
|
// dealing with and formats it appropriately. It is a recursive function,
|
||||||
|
// however circular data structures are detected and handled properly.
|
||||||
|
func (f *formatState) format(v reflect.Value) {
|
||||||
|
// Handle invalid reflect values immediately.
|
||||||
|
kind := v.Kind()
|
||||||
|
if kind == reflect.Invalid {
|
||||||
|
f.fs.Write(invalidAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle pointers specially.
|
||||||
|
if kind == reflect.Ptr {
|
||||||
|
f.formatPtr(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print type information unless already handled elsewhere.
|
||||||
|
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||||
|
f.fs.Write(openParenBytes)
|
||||||
|
f.fs.Write([]byte(v.Type().String()))
|
||||||
|
f.fs.Write(closeParenBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = false
|
||||||
|
|
||||||
|
// Call Stringer/error interfaces if they exist and the handle methods
|
||||||
|
// flag is enabled.
|
||||||
|
if !f.cs.DisableMethods {
|
||||||
|
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||||
|
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.Invalid:
|
||||||
|
// Do nothing. We should never get here since invalid has already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
printBool(f.fs, v.Bool())
|
||||||
|
|
||||||
|
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||||
|
printInt(f.fs, v.Int(), 10)
|
||||||
|
|
||||||
|
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||||
|
printUint(f.fs, v.Uint(), 10)
|
||||||
|
|
||||||
|
case reflect.Float32:
|
||||||
|
printFloat(f.fs, v.Float(), 32)
|
||||||
|
|
||||||
|
case reflect.Float64:
|
||||||
|
printFloat(f.fs, v.Float(), 64)
|
||||||
|
|
||||||
|
case reflect.Complex64:
|
||||||
|
printComplex(f.fs, v.Complex(), 32)
|
||||||
|
|
||||||
|
case reflect.Complex128:
|
||||||
|
printComplex(f.fs, v.Complex(), 64)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case reflect.Array:
|
||||||
|
f.fs.Write(openBracketBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
numEntries := v.Len()
|
||||||
|
for i := 0; i < numEntries; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.Index(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBracketBytes)
|
||||||
|
|
||||||
|
case reflect.String:
|
||||||
|
f.fs.Write([]byte(v.String()))
|
||||||
|
|
||||||
|
case reflect.Interface:
|
||||||
|
// The only time we should get here is for nil interfaces due to
|
||||||
|
// unpackValue calls.
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Do nothing. We should never get here since pointers have already
|
||||||
|
// been handled above.
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
// nil maps should be indicated as different than empty maps
|
||||||
|
if v.IsNil() {
|
||||||
|
f.fs.Write(nilAngleBytes)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fs.Write(openMapBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
keys := v.MapKeys()
|
||||||
|
if f.cs.SortKeys {
|
||||||
|
sortValues(keys, f.cs)
|
||||||
|
}
|
||||||
|
for i, key := range keys {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(key))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
f.ignoreNextType = true
|
||||||
|
f.format(f.unpackValue(v.MapIndex(key)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeMapBytes)
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
numFields := v.NumField()
|
||||||
|
f.fs.Write(openBraceBytes)
|
||||||
|
f.depth++
|
||||||
|
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||||
|
f.fs.Write(maxShortBytes)
|
||||||
|
} else {
|
||||||
|
vt := v.Type()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
if i > 0 {
|
||||||
|
f.fs.Write(spaceBytes)
|
||||||
|
}
|
||||||
|
vtf := vt.Field(i)
|
||||||
|
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||||
|
f.fs.Write([]byte(vtf.Name))
|
||||||
|
f.fs.Write(colonBytes)
|
||||||
|
}
|
||||||
|
f.format(f.unpackValue(v.Field(i)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.depth--
|
||||||
|
f.fs.Write(closeBraceBytes)
|
||||||
|
|
||||||
|
case reflect.Uintptr:
|
||||||
|
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||||
|
|
||||||
|
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||||
|
printHexPtr(f.fs, v.Pointer())
|
||||||
|
|
||||||
|
// There were not any other types at the time this code was written, but
|
||||||
|
// fall back to letting the default fmt package handle it if any get added.
|
||||||
|
default:
|
||||||
|
format := f.buildDefaultFormat()
|
||||||
|
if v.CanInterface() {
|
||||||
|
fmt.Fprintf(f.fs, format, v.Interface())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(f.fs, format, v.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||||
|
// details.
|
||||||
|
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||||
|
f.fs = fs
|
||||||
|
|
||||||
|
// Use standard formatting for verbs that are not v.
|
||||||
|
if verb != 'v' {
|
||||||
|
format := f.constructOrigFormat(verb)
|
||||||
|
fmt.Fprintf(fs, format, f.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.value == nil {
|
||||||
|
if fs.Flag('#') {
|
||||||
|
fs.Write(interfaceBytes)
|
||||||
|
}
|
||||||
|
fs.Write(nilAngleBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f.format(reflect.ValueOf(f.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// newFormatter is a helper function to consolidate the logic from the various
|
||||||
|
// public methods which take varying config states.
|
||||||
|
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||||
|
fs := &formatState{value: v, cs: cs}
|
||||||
|
fs.pointers = make(map[uintptr]int)
|
||||||
|
return fs
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||||
|
interface. As a result, it integrates cleanly with standard fmt package
|
||||||
|
printing functions. The formatter is useful for inline printing of smaller data
|
||||||
|
types similar to the standard %v format specifier.
|
||||||
|
|
||||||
|
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||||
|
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||||
|
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||||
|
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||||
|
the width and precision arguments (however they will still work on the format
|
||||||
|
specifiers not handled by the custom formatter).
|
||||||
|
|
||||||
|
Typically this function shouldn't be called directly. It is much easier to make
|
||||||
|
use of the custom formatter by calling one of the convenience functions such as
|
||||||
|
Printf, Println, or Fprintf.
|
||||||
|
*/
|
||||||
|
func NewFormatter(v interface{}) fmt.Formatter {
|
||||||
|
return newFormatter(&Config, v)
|
||||||
|
}
|
||||||
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
148
vendor/github.com/davecgh/go-spew/spew/spew.go
generated
vendored
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted, provided that the above
|
||||||
|
* copyright notice and this permission notice appear in all copies.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package spew
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the formatted string as a value that satisfies error. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Errorf(format string, a ...interface{}) (err error) {
|
||||||
|
return fmt.Errorf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprint(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Fprintln(w, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Print(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Print(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Printf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the number of bytes written and any write error encountered. See
|
||||||
|
// NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Println(a ...interface{}) (n int, err error) {
|
||||||
|
return fmt.Println(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprint(a ...interface{}) string {
|
||||||
|
return fmt.Sprint(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||||
|
// passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintf(format string, a ...interface{}) string {
|
||||||
|
return fmt.Sprintf(format, convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||||
|
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||||
|
// returns the resulting string. See NewFormatter for formatting details.
|
||||||
|
//
|
||||||
|
// This function is shorthand for the following syntax:
|
||||||
|
//
|
||||||
|
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||||
|
func Sprintln(a ...interface{}) string {
|
||||||
|
return fmt.Sprintln(convertArgs(a)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||||
|
// length with each argument converted to a default spew Formatter interface.
|
||||||
|
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||||
|
formatters = make([]interface{}, len(args))
|
||||||
|
for index, arg := range args {
|
||||||
|
formatters[index] = NewFormatter(arg)
|
||||||
|
}
|
||||||
|
return formatters
|
||||||
|
}
|
||||||
30
vendor/github.com/eleksir/go-xmpp/LICENSE
generated
vendored
Normal file
30
vendor/github.com/eleksir/go-xmpp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2012 Russ Cox <rsc@swtch.com>
|
||||||
|
Copyright (c) 2023 Yasuhiro Matsumoto <mattn.jp@gmail.com>
|
||||||
|
Copyright (c) 2023 Sergei Fedosoff <eleksir@gmail.com>
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
14
vendor/github.com/eleksir/go-xmpp/README.md
generated
vendored
Normal file
14
vendor/github.com/eleksir/go-xmpp/README.md
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
go-xmpp
|
||||||
|
=======
|
||||||
|
|
||||||
|
go xmpp library.
|
||||||
|
|
||||||
|
[Original](https://github.com/rsc/rsc/blob/master/xmpp/xmpp.go) was done by [Russ Cox](https://github.com/rsc), then
|
||||||
|
forked by [Yasuhiro Matsumoto](https://github.com/mattn).
|
||||||
|
|
||||||
|
Since both of them have amost zero interest in continuing develop of this lib, i decide to fork it.
|
||||||
|
|
||||||
|
Documentation can be found at [godoc project](https://godoc.org/github.com/eleksir/go-xmpp)
|
||||||
|
|
||||||
|
This fork mainains my needs for xmpp protocol. Feel free to contribute via PR-s if you find it interesing too and feel
|
||||||
|
that it needs more love than it already has.
|
||||||
1455
vendor/github.com/eleksir/go-xmpp/xmpp.go
generated
vendored
Normal file
1455
vendor/github.com/eleksir/go-xmpp/xmpp.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
125
vendor/github.com/eleksir/go-xmpp/xmpp_avatar.go
generated
vendored
Normal file
125
vendor/github.com/eleksir/go-xmpp/xmpp_avatar.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data"
|
||||||
|
XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientAvatarData struct {
|
||||||
|
XMLName xml.Name `xml:"data"`
|
||||||
|
Data []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientAvatarInfo struct {
|
||||||
|
XMLName xml.Name `xml:"info"`
|
||||||
|
Bytes string `xml:"bytes,attr"`
|
||||||
|
Width string `xml:"width,attr"`
|
||||||
|
Height string `xml:"height,attr"`
|
||||||
|
ID string `xml:"id,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
URL string `xml:"url,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientAvatarMetadata struct {
|
||||||
|
XMLName xml.Name `xml:"metadata"`
|
||||||
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
|
Info clientAvatarInfo `xml:"info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvatarData struct {
|
||||||
|
Data []byte
|
||||||
|
From string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AvatarMetadata struct {
|
||||||
|
From string
|
||||||
|
Bytes int
|
||||||
|
Width int
|
||||||
|
Height int
|
||||||
|
ID string
|
||||||
|
Type string
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) {
|
||||||
|
var data clientAvatarData
|
||||||
|
err := xml.Unmarshal(itemsBody, &data)
|
||||||
|
if err != nil {
|
||||||
|
return AvatarData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base64-decode the avatar data to check its SHA1 hash
|
||||||
|
dataRaw, err := base64.StdEncoding.DecodeString(
|
||||||
|
string(data.Data))
|
||||||
|
if err != nil {
|
||||||
|
return AvatarData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := sha1.Sum(dataRaw)
|
||||||
|
hashStr := hex.EncodeToString(hash[:])
|
||||||
|
if hashStr != id {
|
||||||
|
return AvatarData{}, errors.New("SHA1 hashes do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvatarData{
|
||||||
|
Data: dataRaw,
|
||||||
|
From: from,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) {
|
||||||
|
var meta clientAvatarMetadata
|
||||||
|
err := xml.Unmarshal(body, &meta)
|
||||||
|
if err != nil {
|
||||||
|
return AvatarMetadata{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return AvatarMetadata{
|
||||||
|
From: from,
|
||||||
|
Bytes: atoiw(meta.Info.Bytes),
|
||||||
|
Width: atoiw(meta.Info.Width),
|
||||||
|
Height: atoiw(meta.Info.Height),
|
||||||
|
ID: meta.Info.ID,
|
||||||
|
Type: meta.Info.Type,
|
||||||
|
URL: meta.Info.URL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// A wrapper for atoi which just returns -1 if an error occurs
|
||||||
|
func atoiw(str string) int {
|
||||||
|
i, err := strconv.Atoi(str)
|
||||||
|
if err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarSubscribeMetadata(jid string) {
|
||||||
|
c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarUnsubscribeMetadata(jid string) {
|
||||||
|
c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestData(jid string) {
|
||||||
|
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestDataByID(jid, id string) {
|
||||||
|
c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) AvatarRequestMetadata(jid string) {
|
||||||
|
c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid)
|
||||||
|
}
|
||||||
102
vendor/github.com/eleksir/go-xmpp/xmpp_disco.go
generated
vendored
Normal file
102
vendor/github.com/eleksir/go-xmpp/xmpp_disco.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
|
||||||
|
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientDiscoFeature struct {
|
||||||
|
XMLName xml.Name `xml:"feature"`
|
||||||
|
Var string `xml:"var,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientDiscoIdentity struct {
|
||||||
|
XMLName xml.Name `xml:"identity"`
|
||||||
|
Category string `xml:"category,attr"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientDiscoQuery struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Features []clientDiscoFeature `xml:"feature"`
|
||||||
|
Identities []clientDiscoIdentity `xml:"identity"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientDiscoItem struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
Jid string `xml:"jid,attr"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientDiscoItemsQuery struct {
|
||||||
|
XMLName xml.Name `xml:"query"`
|
||||||
|
Items []clientDiscoItem `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoIdentity struct {
|
||||||
|
Category string
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoItem struct {
|
||||||
|
Jid string
|
||||||
|
Name string
|
||||||
|
Node string
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoResult struct {
|
||||||
|
ID string
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Features []string
|
||||||
|
Identities []DiscoIdentity
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiscoItems struct {
|
||||||
|
Jid string
|
||||||
|
Items []DiscoItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientFeaturesToReturn(features []clientDiscoFeature) []string {
|
||||||
|
var ret []string
|
||||||
|
|
||||||
|
for _, feature := range features {
|
||||||
|
ret = append(ret, feature.Var)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity {
|
||||||
|
var ret []DiscoIdentity
|
||||||
|
|
||||||
|
for _, id := range identities {
|
||||||
|
ret = append(ret, DiscoIdentity{
|
||||||
|
Category: id.Category,
|
||||||
|
Type: id.Type,
|
||||||
|
Name: id.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
|
||||||
|
var ret []DiscoItem
|
||||||
|
for _, item := range items {
|
||||||
|
ret = append(ret, DiscoItem{
|
||||||
|
Jid: item.Jid,
|
||||||
|
Name: item.Name,
|
||||||
|
Node: item.Node,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
54
vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go
generated
vendored
Normal file
54
vendor/github.com/eleksir/go-xmpp/xmpp_information_query.go
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const IQTypeGet = "get"
|
||||||
|
const IQTypeSet = "set"
|
||||||
|
const IQTypeResult = "result"
|
||||||
|
const IQTypeError = "error"
|
||||||
|
|
||||||
|
func (c *Client) Discovery() (string, error) {
|
||||||
|
// use getCookie for a pseudo random id.
|
||||||
|
reqID := strconv.FormatUint(uint64(getCookie()), 10)
|
||||||
|
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover information about a node. Empty node queries info about server itself.
|
||||||
|
func (c *Client) DiscoverNodeInfo(node string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
|
||||||
|
return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover information about given item from given jid.
|
||||||
|
func (c *Client) DiscoverInfo(from string, to string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_INFO)
|
||||||
|
return c.RawInformation(from, to, "info3", IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover items that the server exposes
|
||||||
|
func (c *Client) DiscoverServerItems() (string, error) {
|
||||||
|
return c.DiscoverEntityItems(c.domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Discover items that an entity exposes
|
||||||
|
func (c *Client) DiscoverEntityItems(jid string) (string, error) {
|
||||||
|
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
|
||||||
|
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RawInformationQuery sends an information query request to the server.
|
||||||
|
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
|
||||||
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
|
||||||
|
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// rawInformation send a IQ request with the payload body to the server
|
||||||
|
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
|
||||||
|
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>"
|
||||||
|
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
135
vendor/github.com/eleksir/go-xmpp/xmpp_muc.go
generated
vendored
Normal file
135
vendor/github.com/eleksir/go-xmpp/xmpp_muc.go
generated
vendored
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
// Copyright 2013 Flo Lauber <dev@qatfy.at>. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// TODO(flo):
|
||||||
|
// - support password protected MUC rooms
|
||||||
|
// - cleanup signatures of join/leave functions
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nsMUC = "http://jabber.org/protocol/muc"
|
||||||
|
nsMUCUser = "http://jabber.org/protocol/muc#user"
|
||||||
|
NoHistory = 0
|
||||||
|
CharHistory = 1
|
||||||
|
StanzaHistory = 2
|
||||||
|
SecondsHistory = 3
|
||||||
|
SinceHistory = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Send sends room topic wrapped inside an XMPP message stanza body.
|
||||||
|
func (c *Client) SendTopic(chat Chat) (n int, err error) {
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
|
||||||
|
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>"+
|
||||||
|
"<history maxchars='0'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.2
|
||||||
|
func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
switch history_type {
|
||||||
|
case NoHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s' />\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC)
|
||||||
|
case CharHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<history maxchars='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
|
case StanzaHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<history maxstanzas='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
|
case SecondsHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<history seconds='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
|
||||||
|
case SinceHistory:
|
||||||
|
if history_date != nil {
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<history since='%s'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, history_date.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("Unknown history option")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.2.6
|
||||||
|
func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_type, history int, history_date *time.Time) (n int, err error) {
|
||||||
|
if nick == "" {
|
||||||
|
nick = c.jid
|
||||||
|
}
|
||||||
|
switch history_type {
|
||||||
|
case NoHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<password>%s</password>"+
|
||||||
|
"</x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
|
||||||
|
case CharHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<password>%s</password>\n"+
|
||||||
|
"<history maxchars='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
|
case StanzaHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<password>%s</password>\n"+
|
||||||
|
"<history maxstanzas='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
|
case SecondsHistory:
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<password>%s</password>\n"+
|
||||||
|
"<history seconds='%d'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
|
||||||
|
case SinceHistory:
|
||||||
|
if history_date != nil {
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
|
||||||
|
"<x xmlns='%s'>\n"+
|
||||||
|
"<password>%s</password>\n"+
|
||||||
|
"<history since='%s'/></x>\n"+
|
||||||
|
"</presence>",
|
||||||
|
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history_date.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, errors.New("Unknown history option")
|
||||||
|
}
|
||||||
|
|
||||||
|
// xep-0045 7.14
|
||||||
|
func (c *Client) LeaveMUC(jid string) (n int, err error) {
|
||||||
|
return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s' type='unavailable' />",
|
||||||
|
c.jid, xmlEscape(jid))
|
||||||
|
}
|
||||||
33
vendor/github.com/eleksir/go-xmpp/xmpp_ping.go
generated
vendored
Normal file
33
vendor/github.com/eleksir/go-xmpp/xmpp_ping.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) PingC2S(jid, server string) error {
|
||||||
|
if jid == "" {
|
||||||
|
jid = c.jid
|
||||||
|
}
|
||||||
|
if server == "" {
|
||||||
|
server = c.domain
|
||||||
|
}
|
||||||
|
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
|
||||||
|
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
||||||
|
"</iq>",
|
||||||
|
xmlEscape(jid), xmlEscape(server))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PingS2S(fromServer, toServer string) error {
|
||||||
|
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
|
||||||
|
"<ping xmlns='urn:xmpp:ping'/>\n"+
|
||||||
|
"</iq>",
|
||||||
|
xmlEscape(fromServer), xmlEscape(toServer))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SendResultPing(id, toServer string) error {
|
||||||
|
_, err := fmt.Fprintf(StanzaWriter, "<iq type='result' to='%s' id='%s'/>",
|
||||||
|
xmlEscape(toServer), xmlEscape(id))
|
||||||
|
return err
|
||||||
|
}
|
||||||
133
vendor/github.com/eleksir/go-xmpp/xmpp_pubsub.go
generated
vendored
Normal file
133
vendor/github.com/eleksir/go-xmpp/xmpp_pubsub.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
XMPPNS_PUBSUB = "http://jabber.org/protocol/pubsub"
|
||||||
|
XMPPNS_PUBSUB_EVENT = "http://jabber.org/protocol/pubsub#event"
|
||||||
|
)
|
||||||
|
|
||||||
|
type clientPubsubItem struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
ID string `xml:"id,attr"`
|
||||||
|
Body []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubItems struct {
|
||||||
|
XMLName xml.Name `xml:"items"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Items []clientPubsubItem `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsub struct {
|
||||||
|
XMLName xml.Name `xml:"pubsub"`
|
||||||
|
Items clientPubsubItems `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubEvent struct {
|
||||||
|
XMLName xml.Name `xml:"event"`
|
||||||
|
XMLNS string `xml:"xmlns,attr"`
|
||||||
|
Items clientPubsubItems `xml:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubError struct {
|
||||||
|
XMLName xml.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientPubsubSubscription struct {
|
||||||
|
XMLName xml.Name `xml:"subscription"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
JID string `xml:"jid,attr"`
|
||||||
|
SubID string `xml:"subid,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubEvent struct {
|
||||||
|
Node string
|
||||||
|
Items []PubsubItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubSubscription struct {
|
||||||
|
SubID string
|
||||||
|
JID string
|
||||||
|
Node string
|
||||||
|
Errors []string
|
||||||
|
}
|
||||||
|
type PubsubUnsubscription PubsubSubscription
|
||||||
|
|
||||||
|
type PubsubItem struct {
|
||||||
|
ID string
|
||||||
|
InnerXML []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubsubItems struct {
|
||||||
|
Node string
|
||||||
|
Items []PubsubItem
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts []clientPubsubItem to []PubsubItem
|
||||||
|
func pubsubItemsToReturn(items []clientPubsubItem) []PubsubItem {
|
||||||
|
var tmp []PubsubItem
|
||||||
|
for _, i := range items {
|
||||||
|
tmp = append(tmp, PubsubItem{
|
||||||
|
ID: i.ID,
|
||||||
|
InnerXML: i.Body,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubClientToReturn(event clientPubsubEvent) PubsubEvent {
|
||||||
|
return PubsubEvent{
|
||||||
|
Node: event.Items.Node,
|
||||||
|
Items: pubsubItemsToReturn(event.Items.Items),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubStanza(body string) string {
|
||||||
|
return fmt.Sprintf("<pubsub xmlns='%s'>%s</pubsub>",
|
||||||
|
XMPPNS_PUBSUB, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubSubscriptionStanza(node, jid string) string {
|
||||||
|
body := fmt.Sprintf("<subscribe node='%s' jid='%s'/>",
|
||||||
|
xmlEscape(node),
|
||||||
|
xmlEscape(jid))
|
||||||
|
return pubsubStanza(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func pubsubUnsubscriptionStanza(node, jid string) string {
|
||||||
|
body := fmt.Sprintf("<unsubscribe node='%s' jid='%s'/>",
|
||||||
|
xmlEscape(node),
|
||||||
|
xmlEscape(jid))
|
||||||
|
return pubsubStanza(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubSubscribeNode(node, jid string) {
|
||||||
|
c.RawInformation(c.jid,
|
||||||
|
jid,
|
||||||
|
"sub1",
|
||||||
|
"set",
|
||||||
|
pubsubSubscriptionStanza(node, c.jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubUnsubscribeNode(node, jid string) {
|
||||||
|
c.RawInformation(c.jid,
|
||||||
|
jid,
|
||||||
|
"unsub1",
|
||||||
|
"set",
|
||||||
|
pubsubUnsubscriptionStanza(node, c.jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubRequestLastItems(node, jid string) {
|
||||||
|
body := fmt.Sprintf("<items node='%s'/>", node)
|
||||||
|
c.RawInformation(c.jid, jid, "items1", "get", pubsubStanza(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PubsubRequestItem(node, jid, id string) {
|
||||||
|
body := fmt.Sprintf("<items node='%s'><item id='%s'/></items>", node, id)
|
||||||
|
c.RawInformation(c.jid, jid, "items3", "get", pubsubStanza(body))
|
||||||
|
}
|
||||||
25
vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go
generated
vendored
Normal file
25
vendor/github.com/eleksir/go-xmpp/xmpp_subscription.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package xmpp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) ApproveSubscription(jid string) {
|
||||||
|
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribed'/>",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RevokeSubscription(jid string) {
|
||||||
|
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='unsubscribed'/>",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RetrieveSubscription(jid string) {
|
||||||
|
fmt.Fprintf(c.conn, "<presence to='%s' type='unsubscribe'/>",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) RequestSubscription(jid string) {
|
||||||
|
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribe'/>",
|
||||||
|
xmlEscape(jid))
|
||||||
|
}
|
||||||
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
0
vendor/github.com/hjson/hjson-go/.gitignore
generated
vendored
Normal file
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/hjson/hjson-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016, 2017 Christian Zangl
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
181
vendor/github.com/hjson/hjson-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
# hjson-go
|
||||||
|
|
||||||
|
[](https://github.com/hjson/hjson-go/actions)
|
||||||
|
[](https://github.com/hjson/hjson-go/releases)
|
||||||
|
[](https://goreportcard.com/report/github.com/hjson/hjson-go)
|
||||||
|
[](http://gocover.io/github.com/hjson/hjson-go/)
|
||||||
|
[](https://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
# specify rate in requests/second (because comments are helpful!)
|
||||||
|
rate: 1000
|
||||||
|
|
||||||
|
// prefer c-style comments?
|
||||||
|
/* feeling old fashioned? */
|
||||||
|
|
||||||
|
# did you notice that rate doesn't need quotes?
|
||||||
|
hey: look ma, no quotes for strings either!
|
||||||
|
|
||||||
|
# best of all
|
||||||
|
notice: []
|
||||||
|
anything: ?
|
||||||
|
|
||||||
|
# yes, commas are optional!
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The Go implementation of Hjson is based on [hjson-js](https://github.com/hjson/hjson-js). For other platforms see [hjson.github.io](https://hjson.github.io).
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
Make sure you have a working Go environment. See the [install instructions](http://golang.org/doc/install.html).
|
||||||
|
|
||||||
|
1. Get the sources
|
||||||
|
```bash
|
||||||
|
$ go get -u github.com/hjson/hjson-go
|
||||||
|
```
|
||||||
|
2. Build the **hjson-cli** commandline tool (optional)
|
||||||
|
```bash
|
||||||
|
$ cd $(go env GOPATH)/src/github.com/hjson/hjson-go/hjson-cli && go install
|
||||||
|
$ hjson-cli --version
|
||||||
|
```
|
||||||
|
# Usage as command line tool
|
||||||
|
```
|
||||||
|
usage: hjson-cli [OPTIONS] [INPUT]
|
||||||
|
hjson can be used to convert JSON from/to Hjson.
|
||||||
|
|
||||||
|
hjson will read the given JSON/Hjson input file or read from stdin.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-allowMinusZero
|
||||||
|
Allow -0.
|
||||||
|
-bracesSameLine
|
||||||
|
Print braces on the same line.
|
||||||
|
-c Output as JSON.
|
||||||
|
-h Show this screen.
|
||||||
|
-indentBy string
|
||||||
|
The indent string. (default " ")
|
||||||
|
-j Output as formatted JSON.
|
||||||
|
-omitRootBraces
|
||||||
|
Omit braces at the root.
|
||||||
|
-quoteAlways
|
||||||
|
Always quote string values.
|
||||||
|
```
|
||||||
|
|
||||||
|
Sample:
|
||||||
|
- run `hjson-cli test.json > test.hjson` to convert to Hjson
|
||||||
|
- run `hjson-cli -j test.hjson > test.json` to convert to JSON
|
||||||
|
|
||||||
|
# Usage as a GO library
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
// Now let's look at decoding Hjson data into Go
|
||||||
|
// values.
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// We need to provide a variable where Hjson
|
||||||
|
// can put the decoded data.
|
||||||
|
var dat map[string]interface{}
|
||||||
|
|
||||||
|
// Decode and a check for errors.
|
||||||
|
if err := hjson.Unmarshal(sampleText, &dat); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(dat)
|
||||||
|
|
||||||
|
// In order to use the values in the decoded map,
|
||||||
|
// we'll need to cast them to their appropriate type.
|
||||||
|
|
||||||
|
rate := dat["rate"].(float64)
|
||||||
|
fmt.Println(rate)
|
||||||
|
|
||||||
|
array := dat["array"].([]interface{})
|
||||||
|
str1 := array[0].(string)
|
||||||
|
fmt.Println(str1)
|
||||||
|
|
||||||
|
|
||||||
|
// To encode to Hjson with default options:
|
||||||
|
sampleMap := map[string]int{"apple": 5, "lettuce": 7}
|
||||||
|
hjson, _ := hjson.Marshal(sampleMap)
|
||||||
|
// this is short for:
|
||||||
|
// options := hjson.DefaultOptions()
|
||||||
|
// hjson, _ := hjson.MarshalWithOptions(sampleMap, options)
|
||||||
|
fmt.Println(string(hjson))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you prefer, you can also unmarshal to Go objects by converting to JSON:
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hjson/hjson-go"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Sample struct {
|
||||||
|
Rate int
|
||||||
|
Array []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
sampleText := []byte(`
|
||||||
|
{
|
||||||
|
# specify rate in requests/second
|
||||||
|
rate: 1000
|
||||||
|
array:
|
||||||
|
[
|
||||||
|
foo
|
||||||
|
bar
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
// read Hjson
|
||||||
|
var dat map[string]interface{}
|
||||||
|
hjson.Unmarshal(sampleText, &dat)
|
||||||
|
|
||||||
|
// convert to JSON
|
||||||
|
b, _ := json.Marshal(dat)
|
||||||
|
|
||||||
|
// unmarshal
|
||||||
|
var sample Sample
|
||||||
|
json.Unmarshal(b, &sample)
|
||||||
|
|
||||||
|
fmt.Println(sample.Rate)
|
||||||
|
fmt.Println(sample.Array)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# API
|
||||||
|
|
||||||
|
[](http://godoc.org/github.com/hjson/hjson-go)
|
||||||
|
|
||||||
|
# History
|
||||||
|
|
||||||
|
[see releases](https://github.com/hjson/hjson-go/releases)
|
||||||
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
58
vendor/github.com/hjson/hjson-go/build_release.sh
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd `dirname $0`
|
||||||
|
ROOT=$PWD/_dist
|
||||||
|
|
||||||
|
if [ -d "$ROOT" ]; then rm -rf $ROOT; fi
|
||||||
|
|
||||||
|
mkdir $ROOT
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
export GOOS=$1
|
||||||
|
export GOARCH=$2
|
||||||
|
|
||||||
|
echo build $GOOS $GOARCH
|
||||||
|
OUT=$ROOT/${GOOS}_${GOARCH}
|
||||||
|
mkdir $OUT
|
||||||
|
cd $OUT
|
||||||
|
go build github.com/hjson/hjson-go/hjson-cli
|
||||||
|
if [[ $3 == "zip" ]]; then
|
||||||
|
mv $OUT/hjson-cli.exe $OUT/hjson.exe
|
||||||
|
zip -j ${OUT}.zip $OUT/*
|
||||||
|
else
|
||||||
|
mv $OUT/hjson-cli $OUT/hjson
|
||||||
|
tar -czf ${OUT}.tar.gz -C $OUT .
|
||||||
|
fi
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# not all targets can be built on travis
|
||||||
|
|
||||||
|
# build android arm
|
||||||
|
build darwin 386
|
||||||
|
build darwin amd64
|
||||||
|
# build darwin arm
|
||||||
|
# build darwin arm64
|
||||||
|
build dragonfly amd64
|
||||||
|
build freebsd 386
|
||||||
|
build freebsd amd64
|
||||||
|
build freebsd arm
|
||||||
|
build linux 386
|
||||||
|
build linux amd64
|
||||||
|
build linux arm
|
||||||
|
build linux arm64
|
||||||
|
build linux mips64
|
||||||
|
build linux mips64le
|
||||||
|
build linux ppc64
|
||||||
|
build linux ppc64le
|
||||||
|
build netbsd 386
|
||||||
|
build netbsd amd64
|
||||||
|
build netbsd arm
|
||||||
|
build openbsd 386
|
||||||
|
build openbsd amd64
|
||||||
|
build openbsd arm
|
||||||
|
build plan9 386
|
||||||
|
build plan9 amd64
|
||||||
|
build solaris amd64
|
||||||
|
build windows 386 zip
|
||||||
|
build windows amd64 zip
|
||||||
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
480
vendor/github.com/hjson/hjson-go/decode.go
generated
vendored
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type hjsonParser struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) resetAt() {
|
||||||
|
p.at = 0
|
||||||
|
p.ch = ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
func isPunctuatorChar(c byte) bool {
|
||||||
|
return c == '{' || c == '}' || c == '[' || c == ']' || c == ',' || c == ':'
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) errAt(message string) error {
|
||||||
|
var i int
|
||||||
|
col := 0
|
||||||
|
line := 1
|
||||||
|
for i = p.at - 1; i > 0 && p.data[i] != '\n'; i-- {
|
||||||
|
col++
|
||||||
|
}
|
||||||
|
for ; i > 0; i-- {
|
||||||
|
if p.data[i] == '\n' {
|
||||||
|
line++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
samEnd := p.at - col + 20
|
||||||
|
if samEnd > len(p.data) {
|
||||||
|
samEnd = len(p.data)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%s at line %d,%d >>> %s", message, line, col, string(p.data[p.at-col:samEnd]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
if p.at < len(p.data) {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
p.ch = 0
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[p.at+offs]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var escapee = map[byte]byte{
|
||||||
|
'"': '"',
|
||||||
|
'\'': '\'',
|
||||||
|
'\\': '\\',
|
||||||
|
'/': '/',
|
||||||
|
'b': '\b',
|
||||||
|
'f': '\f',
|
||||||
|
'n': '\n',
|
||||||
|
'r': '\r',
|
||||||
|
't': '\t',
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readString(allowML bool) (string, error) {
|
||||||
|
|
||||||
|
// Parse a string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
|
||||||
|
// callers make sure that (ch === '"' || ch === "'")
|
||||||
|
// When parsing for string values, we must look for " and \ characters.
|
||||||
|
exitCh := p.ch
|
||||||
|
for p.next() {
|
||||||
|
if p.ch == exitCh {
|
||||||
|
p.next()
|
||||||
|
if allowML && exitCh == '\'' && p.ch == '\'' && res.Len() == 0 {
|
||||||
|
// ''' indicates a multiline string
|
||||||
|
p.next()
|
||||||
|
return p.readMLString()
|
||||||
|
} else {
|
||||||
|
return res.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\\' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == 'u' {
|
||||||
|
uffff := 0
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
p.next()
|
||||||
|
var hex int
|
||||||
|
if p.ch >= '0' && p.ch <= '9' {
|
||||||
|
hex = int(p.ch - '0')
|
||||||
|
} else if p.ch >= 'a' && p.ch <= 'f' {
|
||||||
|
hex = int(p.ch - 'a' + 0xa)
|
||||||
|
} else if p.ch >= 'A' && p.ch <= 'F' {
|
||||||
|
hex = int(p.ch - 'A' + 0xa)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad \\u char " + string(p.ch))
|
||||||
|
}
|
||||||
|
uffff = uffff*16 + hex
|
||||||
|
}
|
||||||
|
res.WriteRune(rune(uffff))
|
||||||
|
} else if ech, ok := escapee[p.ch]; ok {
|
||||||
|
res.WriteByte(ech)
|
||||||
|
} else {
|
||||||
|
return "", p.errAt("Bad escape \\" + string(p.ch))
|
||||||
|
}
|
||||||
|
} else if p.ch == '\n' || p.ch == '\r' {
|
||||||
|
return "", p.errAt("Bad string containing newline")
|
||||||
|
} else {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", p.errAt("Bad string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readMLString() (value string, err error) {
|
||||||
|
|
||||||
|
// Parse a multiline string value.
|
||||||
|
res := new(bytes.Buffer)
|
||||||
|
triple := 0
|
||||||
|
|
||||||
|
// we are at ''' +1 - get indent
|
||||||
|
indent := 0
|
||||||
|
for {
|
||||||
|
c := p.peek(-indent - 5)
|
||||||
|
if c == 0 || c == '\n' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
indent++
|
||||||
|
}
|
||||||
|
|
||||||
|
skipIndent := func() {
|
||||||
|
skip := indent
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' && skip > 0 {
|
||||||
|
skip--
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
// When parsing multiline string values, we must look for ' characters.
|
||||||
|
lastLf := false
|
||||||
|
for {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Bad multiline string")
|
||||||
|
} else if p.ch == '\'' {
|
||||||
|
triple++
|
||||||
|
p.next()
|
||||||
|
if triple == 3 {
|
||||||
|
sres := res.Bytes()
|
||||||
|
if lastLf {
|
||||||
|
return string(sres[0 : len(sres)-1]), nil // remove last EOL
|
||||||
|
}
|
||||||
|
return string(sres), nil
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
for triple > 0 {
|
||||||
|
res.WriteByte('\'')
|
||||||
|
triple--
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == '\n' {
|
||||||
|
res.WriteByte('\n')
|
||||||
|
lastLf = true
|
||||||
|
p.next()
|
||||||
|
skipIndent()
|
||||||
|
} else {
|
||||||
|
if p.ch != '\r' {
|
||||||
|
res.WriteByte(p.ch)
|
||||||
|
lastLf = false
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readKeyname() (string, error) {
|
||||||
|
|
||||||
|
// quotes for keys are optional in Hjson
|
||||||
|
// unless they include {}[],: or whitespace.
|
||||||
|
|
||||||
|
if p.ch == '"' || p.ch == '\'' {
|
||||||
|
return p.readString(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
name := new(bytes.Buffer)
|
||||||
|
start := p.at
|
||||||
|
space := -1
|
||||||
|
for {
|
||||||
|
if p.ch == ':' {
|
||||||
|
if name.Len() == 0 {
|
||||||
|
return "", p.errAt("Found ':' but no key name (for an empty key name use quotes)")
|
||||||
|
} else if space >= 0 && space != name.Len() {
|
||||||
|
p.at = start + space
|
||||||
|
return "", p.errAt("Found whitespace in your key name (use quotes to include)")
|
||||||
|
}
|
||||||
|
return name.String(), nil
|
||||||
|
} else if p.ch <= ' ' {
|
||||||
|
if p.ch == 0 {
|
||||||
|
return "", p.errAt("Found EOF while looking for a key name (check your syntax)")
|
||||||
|
}
|
||||||
|
if space < 0 {
|
||||||
|
space = name.Len()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return "", p.errAt("Found '" + string(p.ch) + "' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)")
|
||||||
|
}
|
||||||
|
name.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) white() {
|
||||||
|
for p.ch > 0 {
|
||||||
|
// Skip whitespace.
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
// Hjson allows comments
|
||||||
|
if p.ch == '#' || p.ch == '/' && p.peek(0) == '/' {
|
||||||
|
for p.ch > 0 && p.ch != '\n' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else if p.ch == '/' && p.peek(0) == '*' {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
for p.ch > 0 && !(p.ch == '*' && p.peek(0) == '/') {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if p.ch > 0 {
|
||||||
|
p.next()
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readTfnns() (interface{}, error) {
|
||||||
|
|
||||||
|
// Hjson strings can be quoteless
|
||||||
|
// returns string, true, false, or null.
|
||||||
|
|
||||||
|
if isPunctuatorChar(p.ch) {
|
||||||
|
return nil, p.errAt("Found a punctuator character '" + string(p.ch) + "' when expecting a quoteless string (check your syntax)")
|
||||||
|
}
|
||||||
|
chf := p.ch
|
||||||
|
value := new(bytes.Buffer)
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
|
||||||
|
for {
|
||||||
|
p.next()
|
||||||
|
isEol := p.ch == '\r' || p.ch == '\n' || p.ch == 0
|
||||||
|
if isEol ||
|
||||||
|
p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' ||
|
||||||
|
p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
switch chf {
|
||||||
|
case 'f':
|
||||||
|
if strings.TrimSpace(value.String()) == "false" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
case 'n':
|
||||||
|
if strings.TrimSpace(value.String()) == "null" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
case 't':
|
||||||
|
if strings.TrimSpace(value.String()) == "true" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if chf == '-' || chf >= '0' && chf <= '9' {
|
||||||
|
if n, err := tryParseNumber(value.Bytes(), false); err == nil {
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isEol {
|
||||||
|
// remove any whitespace at the end (ignored in quoteless strings)
|
||||||
|
return strings.TrimSpace(value.String()), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.WriteByte(p.ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readArray() (value interface{}, err error) {
|
||||||
|
|
||||||
|
// Parse an array value.
|
||||||
|
// assuming ch == '['
|
||||||
|
|
||||||
|
array := make([]interface{}, 0, 1)
|
||||||
|
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil // empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
for p.ch > 0 {
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
array = append(array, val)
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == ']' {
|
||||||
|
p.next()
|
||||||
|
return array, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, p.errAt("End of input while parsing an array (did you forget a closing ']'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readObject(withoutBraces bool) (value interface{}, err error) {
|
||||||
|
// Parse an object value.
|
||||||
|
|
||||||
|
object := make(map[string]interface{})
|
||||||
|
|
||||||
|
if !withoutBraces {
|
||||||
|
// assuming ch == '{'
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil // empty object
|
||||||
|
}
|
||||||
|
for p.ch > 0 {
|
||||||
|
var key string
|
||||||
|
if key, err = p.readKeyname(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch != ':' {
|
||||||
|
return nil, p.errAt("Expected ':' instead of '" + string(p.ch) + "'")
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
// duplicate keys overwrite the previous value
|
||||||
|
var val interface{}
|
||||||
|
if val, err = p.readValue(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
object[key] = val
|
||||||
|
p.white()
|
||||||
|
// in Hjson the comma is optional and trailing commas are allowed
|
||||||
|
if p.ch == ',' {
|
||||||
|
p.next()
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
if p.ch == '}' && !withoutBraces {
|
||||||
|
p.next()
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
}
|
||||||
|
|
||||||
|
if withoutBraces {
|
||||||
|
return object, nil
|
||||||
|
}
|
||||||
|
return nil, p.errAt("End of input while parsing an object (did you forget a closing '}'?)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) readValue() (interface{}, error) {
|
||||||
|
|
||||||
|
// Parse a Hjson value. It could be an object, an array, a string, a number or a word.
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.readObject(false)
|
||||||
|
case '[':
|
||||||
|
return p.readArray()
|
||||||
|
case '"', '\'':
|
||||||
|
return p.readString(true)
|
||||||
|
default:
|
||||||
|
return p.readTfnns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) rootValue() (interface{}, error) {
|
||||||
|
// Braces for the root object are optional
|
||||||
|
|
||||||
|
p.white()
|
||||||
|
switch p.ch {
|
||||||
|
case '{':
|
||||||
|
return p.checkTrailing(p.readObject(false))
|
||||||
|
case '[':
|
||||||
|
return p.checkTrailing(p.readArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
// assume we have a root object without braces
|
||||||
|
res, err := p.checkTrailing(p.readObject(true))
|
||||||
|
if err == nil {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// test if we are dealing with a single JSON value instead (true/false/null/num/"")
|
||||||
|
p.resetAt()
|
||||||
|
if res2, err2 := p.checkTrailing(p.readValue()); err2 == nil {
|
||||||
|
return res2, nil
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *hjsonParser) checkTrailing(v interface{}, err error) (interface{}, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.white()
|
||||||
|
if p.ch > 0 {
|
||||||
|
return nil, p.errAt("Syntax error, found trailing characters")
|
||||||
|
}
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the Hjson-encoded data and stores the result
|
||||||
|
// in the value pointed to by v.
|
||||||
|
//
|
||||||
|
// Unmarshal uses the inverse of the encodings that
|
||||||
|
// Marshal uses, allocating maps, slices, and pointers as necessary.
|
||||||
|
//
|
||||||
|
func Unmarshal(data []byte, v interface{}) (err error) {
|
||||||
|
var value interface{}
|
||||||
|
parser := &hjsonParser{data, 0, ' '}
|
||||||
|
parser.resetAt()
|
||||||
|
value, err = parser.rootValue()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() != reflect.Ptr || rv.IsNil() {
|
||||||
|
return fmt.Errorf("non-pointer %v", reflect.TypeOf(v))
|
||||||
|
}
|
||||||
|
for rv.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if e := recover(); e != nil {
|
||||||
|
err = fmt.Errorf("%v", e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
rv.Set(reflect.ValueOf(value))
|
||||||
|
return err
|
||||||
|
}
|
||||||
492
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
492
vendor/github.com/hjson/hjson-go/encode.go
generated
vendored
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EncoderOptions defines options for encoding to Hjson.
|
||||||
|
type EncoderOptions struct {
|
||||||
|
// End of line, should be either \n or \r\n
|
||||||
|
Eol string
|
||||||
|
// Place braces on the same line
|
||||||
|
BracesSameLine bool
|
||||||
|
// Emit braces for the root object
|
||||||
|
EmitRootBraces bool
|
||||||
|
// Always place string in quotes
|
||||||
|
QuoteAlways bool
|
||||||
|
// Place string in quotes if it could otherwise be a number, boolean or null
|
||||||
|
QuoteAmbiguousStrings bool
|
||||||
|
// Indent string
|
||||||
|
IndentBy string
|
||||||
|
// Base indentation string
|
||||||
|
BaseIndentation string
|
||||||
|
// Allow the -0 value (unlike ES6)
|
||||||
|
AllowMinusZero bool
|
||||||
|
// Encode unknown values as 'null'
|
||||||
|
UnknownAsNull bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultOptions returns the default encoding options.
|
||||||
|
func DefaultOptions() EncoderOptions {
|
||||||
|
opt := EncoderOptions{}
|
||||||
|
opt.Eol = "\n"
|
||||||
|
opt.BracesSameLine = false
|
||||||
|
opt.EmitRootBraces = true
|
||||||
|
opt.QuoteAlways = false
|
||||||
|
opt.QuoteAmbiguousStrings = true
|
||||||
|
opt.IndentBy = " "
|
||||||
|
opt.BaseIndentation = ""
|
||||||
|
opt.AllowMinusZero = false
|
||||||
|
opt.UnknownAsNull = false
|
||||||
|
return opt
|
||||||
|
}
|
||||||
|
|
||||||
|
type hjsonEncoder struct {
|
||||||
|
bytes.Buffer // output
|
||||||
|
EncoderOptions
|
||||||
|
indent int
|
||||||
|
}
|
||||||
|
|
||||||
|
var needsEscape, needsQuotes, needsEscapeML, startsWithKeyword, needsEscapeName *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var commonRange = `\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}`
|
||||||
|
// needsEscape tests if the string can be written without escapes
|
||||||
|
needsEscape = regexp.MustCompile(`[\\\"\x00-\x1f` + commonRange + `]`)
|
||||||
|
// needsQuotes tests if the string can be written as a quoteless string (includes needsEscape but without \\ and \")
|
||||||
|
needsQuotes = regexp.MustCompile(`^\s|^"|^'|^#|^/\*|^//|^\{|^\}|^\[|^\]|^:|^,|\s$|[\x00-\x1f\x7f-\x9f\x{00ad}\x{0600}-\x{0604}\x{070f}\x{17b4}\x{17b5}\x{200c}-\x{200f}\x{2028}-\x{202f}\x{2060}-\x{206f}\x{feff}\x{fff0}-\x{ffff}]`)
|
||||||
|
// needsEscapeML tests if the string can be written as a multiline string (like needsEscape but without \n, \r, \\, \", \t)
|
||||||
|
var x08Or9 = `\x08` // `\x09` for the old behavior
|
||||||
|
needsEscapeML = regexp.MustCompile(`'''|^[\s]+$|[\x00-` + x08Or9 + `\x0b\x0c\x0e-\x1f` + commonRange + `]`)
|
||||||
|
// starts with a keyword and optionally is followed by a comment
|
||||||
|
startsWithKeyword = regexp.MustCompile(`^(true|false|null)\s*((,|\]|\}|#|//|/\*).*)?$`)
|
||||||
|
needsEscapeName = regexp.MustCompile(`[,\{\[\}\]\s:#"']|//|/\*`)
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta = map[byte][]byte{
|
||||||
|
// table of character substitutions
|
||||||
|
'\b': []byte("\\b"),
|
||||||
|
'\t': []byte("\\t"),
|
||||||
|
'\n': []byte("\\n"),
|
||||||
|
'\f': []byte("\\f"),
|
||||||
|
'\r': []byte("\\r"),
|
||||||
|
'"': []byte("\\\""),
|
||||||
|
'\\': []byte("\\\\"),
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteReplace(text string) string {
|
||||||
|
return string(needsEscape.ReplaceAllFunc([]byte(text), func(a []byte) []byte {
|
||||||
|
c := meta[a[0]]
|
||||||
|
if c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
r, _ := utf8.DecodeRune(a)
|
||||||
|
return []byte(fmt.Sprintf("\\u%04x", r))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quote(value string, separator string, isRootObject bool) {
|
||||||
|
|
||||||
|
// Check if we can insert this string without quotes
|
||||||
|
// see hjson syntax (must not parse as true, false, null or number)
|
||||||
|
|
||||||
|
if len(value) == 0 {
|
||||||
|
e.WriteString(separator + `""`)
|
||||||
|
} else if e.QuoteAlways ||
|
||||||
|
needsQuotes.MatchString(value) || (e.QuoteAmbiguousStrings && (startsWithNumber([]byte(value)) ||
|
||||||
|
startsWithKeyword.MatchString(value))) {
|
||||||
|
|
||||||
|
// If the string contains no control characters, no quote characters, and no
|
||||||
|
// backslash characters, then we can safely slap some quotes around it.
|
||||||
|
// Otherwise we first check if the string can be expressed in multiline
|
||||||
|
// format or we must replace the offending characters with safe escape
|
||||||
|
// sequences.
|
||||||
|
|
||||||
|
if !needsEscape.MatchString(value) {
|
||||||
|
e.WriteString(separator + `"` + value + `"`)
|
||||||
|
} else if !needsEscapeML.MatchString(value) && !isRootObject {
|
||||||
|
e.mlString(value, separator)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator + `"` + e.quoteReplace(value) + `"`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// return without quotes
|
||||||
|
e.WriteString(separator + value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) mlString(value string, separator string) {
|
||||||
|
// wrap the string into the ''' (multiline) format
|
||||||
|
|
||||||
|
a := strings.Split(strings.Replace(value, "\r", "", -1), "\n")
|
||||||
|
|
||||||
|
if len(a) == 1 {
|
||||||
|
// The string contains only a single line. We still use the multiline
|
||||||
|
// format as it avoids escaping the \ character (e.g. when used in a
|
||||||
|
// regex).
|
||||||
|
e.WriteString(separator + "'''")
|
||||||
|
e.WriteString(a[0])
|
||||||
|
} else {
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
e.WriteString("'''")
|
||||||
|
for _, v := range a {
|
||||||
|
indent := e.indent + 1
|
||||||
|
if len(v) == 0 {
|
||||||
|
indent = 0
|
||||||
|
}
|
||||||
|
e.writeIndent(indent)
|
||||||
|
e.WriteString(v)
|
||||||
|
}
|
||||||
|
e.writeIndent(e.indent + 1)
|
||||||
|
}
|
||||||
|
e.WriteString("'''")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) quoteName(name string) string {
|
||||||
|
if len(name) == 0 {
|
||||||
|
return `""`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we can insert this name without quotes
|
||||||
|
|
||||||
|
if needsEscapeName.MatchString(name) {
|
||||||
|
if needsEscape.MatchString(name) {
|
||||||
|
name = e.quoteReplace(name)
|
||||||
|
}
|
||||||
|
return `"` + name + `"`
|
||||||
|
}
|
||||||
|
// without quotes
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortAlpha []reflect.Value
|
||||||
|
|
||||||
|
func (s sortAlpha) Len() int {
|
||||||
|
return len(s)
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Swap(i, j int) {
|
||||||
|
s[i], s[j] = s[j], s[i]
|
||||||
|
}
|
||||||
|
func (s sortAlpha) Less(i, j int) bool {
|
||||||
|
return s[i].String() < s[j].String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) writeIndent(indent int) {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
e.WriteString(e.BaseIndentation)
|
||||||
|
for i := 0; i < indent; i++ {
|
||||||
|
e.WriteString(e.IndentBy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) useMarshaler(value reflect.Value, separator string) error {
|
||||||
|
b, err := value.Interface().(json.Marshaler).MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshaler = reflect.TypeOf((*json.Marshaler)(nil)).Elem()
|
||||||
|
|
||||||
|
func (e *hjsonEncoder) str(value reflect.Value, noIndent bool, separator string, isRootObject bool) error {
|
||||||
|
|
||||||
|
// Produce a string from value.
|
||||||
|
|
||||||
|
kind := value.Kind()
|
||||||
|
|
||||||
|
if kind == reflect.Interface || kind == reflect.Ptr {
|
||||||
|
if value.IsNil() {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("null")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return e.str(value.Elem(),noIndent,separator,isRootObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
if value.Type().Implements(marshaler) {
|
||||||
|
return e.useMarshaler(value, separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
e.quote(value.String(), separator, isRootObject)
|
||||||
|
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatInt(value.Int(), 10))
|
||||||
|
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
|
||||||
|
reflect.Uintptr:
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString(strconv.FormatUint(value.Uint(), 10))
|
||||||
|
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
// JSON numbers must be finite. Encode non-finite numbers as null.
|
||||||
|
e.WriteString(separator)
|
||||||
|
number := value.Float()
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
e.WriteString("null")
|
||||||
|
} else if !e.AllowMinusZero && number == -0 {
|
||||||
|
e.WriteString("0")
|
||||||
|
} else {
|
||||||
|
// find shortest representation ('G' does not work)
|
||||||
|
val := strconv.FormatFloat(number, 'f', -1, 64)
|
||||||
|
exp := strconv.FormatFloat(number, 'E', -1, 64)
|
||||||
|
if len(exp) < len(val) {
|
||||||
|
val = strings.ToLower(exp)
|
||||||
|
}
|
||||||
|
e.WriteString(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
e.WriteString(separator)
|
||||||
|
if value.Bool() {
|
||||||
|
e.WriteString("true")
|
||||||
|
} else {
|
||||||
|
e.WriteString("false")
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice, reflect.Array:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("[]")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
e.indent++
|
||||||
|
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
e.WriteString("[")
|
||||||
|
|
||||||
|
// Join all of the element texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
if err := e.str(value.Index(i), true, "", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("]")
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
|
||||||
|
len := value.Len()
|
||||||
|
if len == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent++
|
||||||
|
e.WriteString("{")
|
||||||
|
}
|
||||||
|
|
||||||
|
keys := value.MapKeys()
|
||||||
|
sort.Sort(sortAlpha(keys))
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < len; i++ {
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(e.quoteName(keys[i].String()))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(value.MapIndex(keys[i]), false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
}
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
case reflect.Struct:
|
||||||
|
|
||||||
|
l := value.NumField()
|
||||||
|
if l == 0 {
|
||||||
|
e.WriteString(separator)
|
||||||
|
e.WriteString("{}")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
indent1 := e.indent
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
if !noIndent && !e.BracesSameLine {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
} else {
|
||||||
|
e.WriteString(separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent++
|
||||||
|
e.WriteString("{")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join all of the member texts together, separated with newlines
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
curStructField := value.Type().Field(i)
|
||||||
|
curField := value.Field(i)
|
||||||
|
|
||||||
|
name := curStructField.Name
|
||||||
|
jsonTag := curStructField.Tag.Get("json")
|
||||||
|
jsonComment := curStructField.Tag.Get("comment")
|
||||||
|
omitEmpty := false
|
||||||
|
if jsonTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
splits := strings.Split(jsonTag, ",")
|
||||||
|
if splits[0] != "" {
|
||||||
|
name = splits[0]
|
||||||
|
}
|
||||||
|
if len(splits) > 1 {
|
||||||
|
for _, opt := range splits[1:] {
|
||||||
|
if opt == "omitempty" {
|
||||||
|
omitEmpty = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if omitEmpty && isEmptyValue(curField) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 {
|
||||||
|
for _, line := range strings.Split(jsonComment, e.Eol) {
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(fmt.Sprintf("# %s", line))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i > 0 || !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(e.indent)
|
||||||
|
}
|
||||||
|
e.WriteString(e.quoteName(name))
|
||||||
|
e.WriteString(":")
|
||||||
|
if err := e.str(curField, false, " ", false); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(jsonComment) > 0 && i < l-1 {
|
||||||
|
e.WriteString(e.Eol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isRootObject || e.EmitRootBraces {
|
||||||
|
e.writeIndent(indent1)
|
||||||
|
e.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
|
e.indent = indent1
|
||||||
|
|
||||||
|
default:
|
||||||
|
if e.UnknownAsNull {
|
||||||
|
// Use null as a placeholder for non-JSON values.
|
||||||
|
e.WriteString("null")
|
||||||
|
} else {
|
||||||
|
return errors.New("Unsupported type " + value.Type().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil()
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal returns the Hjson encoding of v using
|
||||||
|
// default options.
|
||||||
|
//
|
||||||
|
// See MarshalWithOptions.
|
||||||
|
//
|
||||||
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return MarshalWithOptions(v, DefaultOptions())
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalWithOptions returns the Hjson encoding of v.
|
||||||
|
//
|
||||||
|
// Marshal traverses the value v recursively.
|
||||||
|
//
|
||||||
|
// Boolean values encode as JSON booleans.
|
||||||
|
//
|
||||||
|
// Floating point, integer, and Number values encode as JSON numbers.
|
||||||
|
//
|
||||||
|
// String values encode as Hjson strings (quoteless, multiline or
|
||||||
|
// JSON).
|
||||||
|
//
|
||||||
|
// Array and slice values encode as JSON arrays.
|
||||||
|
//
|
||||||
|
// Map values encode as JSON objects. The map's key type must be a
|
||||||
|
// string. The map keys are sorted and used as JSON object keys.
|
||||||
|
//
|
||||||
|
// Pointer values encode as the value pointed to.
|
||||||
|
// A nil pointer encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// Interface values encode as the value contained in the interface.
|
||||||
|
// A nil interface value encodes as the null JSON value.
|
||||||
|
//
|
||||||
|
// JSON cannot represent cyclic data structures and Marshal does not
|
||||||
|
// handle them. Passing cyclic structures to Marshal will result in
|
||||||
|
// an infinite recursion.
|
||||||
|
//
|
||||||
|
func MarshalWithOptions(v interface{}, options EncoderOptions) ([]byte, error) {
|
||||||
|
e := &hjsonEncoder{}
|
||||||
|
e.indent = 0
|
||||||
|
e.Eol = options.Eol
|
||||||
|
e.BracesSameLine = options.BracesSameLine
|
||||||
|
e.EmitRootBraces = options.EmitRootBraces
|
||||||
|
e.QuoteAlways = options.QuoteAlways
|
||||||
|
e.QuoteAmbiguousStrings = options.QuoteAmbiguousStrings
|
||||||
|
e.IndentBy = options.IndentBy
|
||||||
|
e.BaseIndentation = options.BaseIndentation
|
||||||
|
|
||||||
|
err := e.str(reflect.ValueOf(v), true, e.BaseIndentation, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return e.Bytes(), nil
|
||||||
|
}
|
||||||
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
108
vendor/github.com/hjson/hjson-go/parseNumber.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package hjson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type parseNumber struct {
|
||||||
|
data []byte
|
||||||
|
at int // The index of the current character
|
||||||
|
ch byte // The current character
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) next() bool {
|
||||||
|
// get the next character.
|
||||||
|
len := len(p.data)
|
||||||
|
if p.at < len {
|
||||||
|
p.ch = p.data[p.at]
|
||||||
|
p.at++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if p.at == len {
|
||||||
|
p.at++
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parseNumber) peek(offs int) byte {
|
||||||
|
pos := p.at + offs
|
||||||
|
if pos >= 0 && pos < len(p.data) {
|
||||||
|
return p.data[pos]
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func startsWithNumber(text []byte) bool {
|
||||||
|
if _, err := tryParseNumber(text, true); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryParseNumber(text []byte, stopAtNext bool) (float64, error) {
|
||||||
|
// Parse a number value.
|
||||||
|
|
||||||
|
p := parseNumber{text, 0, ' '}
|
||||||
|
leadingZeros := 0
|
||||||
|
testLeading := true
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
if testLeading {
|
||||||
|
if p.ch == '0' {
|
||||||
|
leadingZeros++
|
||||||
|
} else {
|
||||||
|
testLeading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
if testLeading {
|
||||||
|
leadingZeros--
|
||||||
|
} // single 0 is allowed
|
||||||
|
if p.ch == '.' {
|
||||||
|
for p.next() && p.ch >= '0' && p.ch <= '9' {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.ch == 'e' || p.ch == 'E' {
|
||||||
|
p.next()
|
||||||
|
if p.ch == '-' || p.ch == '+' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
for p.ch >= '0' && p.ch <= '9' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := p.at
|
||||||
|
|
||||||
|
// skip white/to (newline)
|
||||||
|
for p.ch > 0 && p.ch <= ' ' {
|
||||||
|
p.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
if stopAtNext {
|
||||||
|
// end scan if we find a punctuator character like ,}] or a comment
|
||||||
|
if p.ch == ',' || p.ch == '}' || p.ch == ']' ||
|
||||||
|
p.ch == '#' || p.ch == '/' && (p.peek(0) == '/' || p.peek(0) == '*') {
|
||||||
|
p.ch = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.ch > 0 || leadingZeros != 0 {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
number, err := strconv.ParseFloat(string(p.data[0:end-1]), 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if math.IsInf(number, 0) || math.IsNaN(number) {
|
||||||
|
return 0, errors.New("Invalid number")
|
||||||
|
}
|
||||||
|
return number, nil
|
||||||
|
}
|
||||||
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
Normal file
4
vendor/github.com/sirupsen/logrus/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
logrus
|
||||||
|
vendor
|
||||||
|
|
||||||
|
.idea/
|
||||||
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
40
vendor/github.com/sirupsen/logrus/.golangci.yml
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
run:
|
||||||
|
# do not run on test files yet
|
||||||
|
tests: false
|
||||||
|
|
||||||
|
# all available settings of specific linters
|
||||||
|
linters-settings:
|
||||||
|
errcheck:
|
||||||
|
# report about not checking of errors in type assetions: `a := b.(MyStruct)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-type-assertions: false
|
||||||
|
|
||||||
|
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
|
||||||
|
# default is false: such cases aren't reported by default.
|
||||||
|
check-blank: false
|
||||||
|
|
||||||
|
lll:
|
||||||
|
line-length: 100
|
||||||
|
tab-width: 4
|
||||||
|
|
||||||
|
prealloc:
|
||||||
|
simple: false
|
||||||
|
range-loops: false
|
||||||
|
for-loops: false
|
||||||
|
|
||||||
|
whitespace:
|
||||||
|
multi-if: false # Enforces newlines (or comments) after every multi-line if statement
|
||||||
|
multi-func: false # Enforces newlines (or comments) after every multi-line function signature
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- megacheck
|
||||||
|
- govet
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- prealloc
|
||||||
|
disable-all: false
|
||||||
|
presets:
|
||||||
|
- bugs
|
||||||
|
- unused
|
||||||
|
fast: false
|
||||||
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
Normal file
15
vendor/github.com/sirupsen/logrus/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
language: go
|
||||||
|
go_import_path: github.com/sirupsen/logrus
|
||||||
|
git:
|
||||||
|
depth: 1
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
|
go: 1.15.x
|
||||||
|
os: linux
|
||||||
|
install:
|
||||||
|
- ./travis/install.sh
|
||||||
|
script:
|
||||||
|
- cd ci
|
||||||
|
- go run mage.go -v -w ../ crossBuild
|
||||||
|
- go run mage.go -v -w ../ lint
|
||||||
|
- go run mage.go -v -w ../ test
|
||||||
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
259
vendor/github.com/sirupsen/logrus/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
# 1.8.1
|
||||||
|
Code quality:
|
||||||
|
* move magefile in its own subdir/submodule to remove magefile dependency on logrus consumer
|
||||||
|
* improve timestamp format documentation
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* fix race condition on logger hooks
|
||||||
|
|
||||||
|
|
||||||
|
# 1.8.0
|
||||||
|
|
||||||
|
Correct versioning number replacing v1.7.1.
|
||||||
|
|
||||||
|
# 1.7.1
|
||||||
|
|
||||||
|
Beware this release has introduced a new public API and its semver is therefore incorrect.
|
||||||
|
|
||||||
|
Code quality:
|
||||||
|
* use go 1.15 in travis
|
||||||
|
* use magefile as task runner
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* small fixes about new go 1.13 error formatting system
|
||||||
|
* Fix for long time race condiction with mutating data hooks
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* build support for zos
|
||||||
|
|
||||||
|
# 1.7.0
|
||||||
|
Fixes:
|
||||||
|
* the dependency toward a windows terminal library has been removed
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* a new buffer pool management API has been added
|
||||||
|
* a set of `<LogLevel>Fn()` functions have been added
|
||||||
|
|
||||||
|
# 1.6.0
|
||||||
|
Fixes:
|
||||||
|
* end of line cleanup
|
||||||
|
* revert the entry concurrency bug fix whic leads to deadlock under some circumstances
|
||||||
|
* update dependency on go-windows-terminal-sequences to fix a crash with go 1.14
|
||||||
|
|
||||||
|
Features:
|
||||||
|
* add an option to the `TextFormatter` to completely disable fields quoting
|
||||||
|
|
||||||
|
# 1.5.0
|
||||||
|
Code quality:
|
||||||
|
* add golangci linter run on travis
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* add mutex for hooks concurrent access on `Entry` data
|
||||||
|
* caller function field for go1.14
|
||||||
|
* fix build issue for gopherjs target
|
||||||
|
|
||||||
|
Feature:
|
||||||
|
* add an hooks/writer sub-package whose goal is to split output on different stream depending on the trace level
|
||||||
|
* add a `DisableHTMLEscape` option in the `JSONFormatter`
|
||||||
|
* add `ForceQuote` and `PadLevelText` options in the `TextFormatter`
|
||||||
|
|
||||||
|
# 1.4.2
|
||||||
|
* Fixes build break for plan9, nacl, solaris
|
||||||
|
# 1.4.1
|
||||||
|
This new release introduces:
|
||||||
|
* Enhance TextFormatter to not print caller information when they are empty (#944)
|
||||||
|
* Remove dependency on golang.org/x/crypto (#932, #943)
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fix Entry.WithContext method to return a copy of the initial entry (#941)
|
||||||
|
|
||||||
|
# 1.4.0
|
||||||
|
This new release introduces:
|
||||||
|
* Add `DeferExitHandler`, similar to `RegisterExitHandler` but prepending the handler to the list of handlers (semantically like `defer`) (#848).
|
||||||
|
* Add `CallerPrettyfier` to `JSONFormatter` and `TextFormatter` (#909, #911)
|
||||||
|
* Add `Entry.WithContext()` and `Entry.Context`, to set a context on entries to be used e.g. in hooks (#919).
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Fix wrong method calls `Logger.Print` and `Logger.Warningln` (#893).
|
||||||
|
* Update `Entry.Logf` to not do string formatting unless the log level is enabled (#903)
|
||||||
|
* Fix infinite recursion on unknown `Level.String()` (#907)
|
||||||
|
* Fix race condition in `getCaller` (#916).
|
||||||
|
|
||||||
|
|
||||||
|
# 1.3.0
|
||||||
|
This new release introduces:
|
||||||
|
* Log, Logf, Logln functions for Logger and Entry that take a Level
|
||||||
|
|
||||||
|
Fixes:
|
||||||
|
* Building prometheus node_exporter on AIX (#840)
|
||||||
|
* Race condition in TextFormatter (#468)
|
||||||
|
* Travis CI import path (#868)
|
||||||
|
* Remove coloured output on Windows (#862)
|
||||||
|
* Pointer to func as field in JSONFormatter (#870)
|
||||||
|
* Properly marshal Levels (#873)
|
||||||
|
|
||||||
|
# 1.2.0
|
||||||
|
This new release introduces:
|
||||||
|
* A new method `SetReportCaller` in the `Logger` to enable the file, line and calling function from which the trace has been issued
|
||||||
|
* A new trace level named `Trace` whose level is below `Debug`
|
||||||
|
* A configurable exit function to be called upon a Fatal trace
|
||||||
|
* The `Level` object now implements `encoding.TextUnmarshaler` interface
|
||||||
|
|
||||||
|
# 1.1.1
|
||||||
|
This is a bug fix release.
|
||||||
|
* fix the build break on Solaris
|
||||||
|
* don't drop a whole trace in JSONFormatter when a field param is a function pointer which can not be serialized
|
||||||
|
|
||||||
|
# 1.1.0
|
||||||
|
This new release introduces:
|
||||||
|
* several fixes:
|
||||||
|
* a fix for a race condition on entry formatting
|
||||||
|
* proper cleanup of previously used entries before putting them back in the pool
|
||||||
|
* the extra new line at the end of message in text formatter has been removed
|
||||||
|
* a new global public API to check if a level is activated: IsLevelEnabled
|
||||||
|
* the following methods have been added to the Logger object
|
||||||
|
* IsLevelEnabled
|
||||||
|
* SetFormatter
|
||||||
|
* SetOutput
|
||||||
|
* ReplaceHooks
|
||||||
|
* introduction of go module
|
||||||
|
* an indent configuration for the json formatter
|
||||||
|
* output colour support for windows
|
||||||
|
* the field sort function is now configurable for text formatter
|
||||||
|
* the CLICOLOR and CLICOLOR\_FORCE environment variable support in text formater
|
||||||
|
|
||||||
|
# 1.0.6
|
||||||
|
|
||||||
|
This new release introduces:
|
||||||
|
* a new api WithTime which allows to easily force the time of the log entry
|
||||||
|
which is mostly useful for logger wrapper
|
||||||
|
* a fix reverting the immutability of the entry given as parameter to the hooks
|
||||||
|
a new configuration field of the json formatter in order to put all the fields
|
||||||
|
in a nested dictionnary
|
||||||
|
* a new SetOutput method in the Logger
|
||||||
|
* a new configuration of the textformatter to configure the name of the default keys
|
||||||
|
* a new configuration of the text formatter to disable the level truncation
|
||||||
|
|
||||||
|
# 1.0.5
|
||||||
|
|
||||||
|
* Fix hooks race (#707)
|
||||||
|
* Fix panic deadlock (#695)
|
||||||
|
|
||||||
|
# 1.0.4
|
||||||
|
|
||||||
|
* Fix race when adding hooks (#612)
|
||||||
|
* Fix terminal check in AppEngine (#635)
|
||||||
|
|
||||||
|
# 1.0.3
|
||||||
|
|
||||||
|
* Replace example files with testable examples
|
||||||
|
|
||||||
|
# 1.0.2
|
||||||
|
|
||||||
|
* bug: quote non-string values in text formatter (#583)
|
||||||
|
* Make (*Logger) SetLevel a public method
|
||||||
|
|
||||||
|
# 1.0.1
|
||||||
|
|
||||||
|
* bug: fix escaping in text formatter (#575)
|
||||||
|
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
* Officially changed name to lower-case
|
||||||
|
* bug: colors on Windows 10 (#541)
|
||||||
|
* bug: fix race in accessing level (#512)
|
||||||
|
|
||||||
|
# 0.11.5
|
||||||
|
|
||||||
|
* feature: add writer and writerlevel to entry (#372)
|
||||||
|
|
||||||
|
# 0.11.4
|
||||||
|
|
||||||
|
* bug: fix undefined variable on solaris (#493)
|
||||||
|
|
||||||
|
# 0.11.3
|
||||||
|
|
||||||
|
* formatter: configure quoting of empty values (#484)
|
||||||
|
* formatter: configure quoting character (default is `"`) (#484)
|
||||||
|
* bug: fix not importing io correctly in non-linux environments (#481)
|
||||||
|
|
||||||
|
# 0.11.2
|
||||||
|
|
||||||
|
* bug: fix windows terminal detection (#476)
|
||||||
|
|
||||||
|
# 0.11.1
|
||||||
|
|
||||||
|
* bug: fix tty detection with custom out (#471)
|
||||||
|
|
||||||
|
# 0.11.0
|
||||||
|
|
||||||
|
* performance: Use bufferpool to allocate (#370)
|
||||||
|
* terminal: terminal detection for app-engine (#343)
|
||||||
|
* feature: exit handler (#375)
|
||||||
|
|
||||||
|
# 0.10.0
|
||||||
|
|
||||||
|
* feature: Add a test hook (#180)
|
||||||
|
* feature: `ParseLevel` is now case-insensitive (#326)
|
||||||
|
* feature: `FieldLogger` interface that generalizes `Logger` and `Entry` (#308)
|
||||||
|
* performance: avoid re-allocations on `WithFields` (#335)
|
||||||
|
|
||||||
|
# 0.9.0
|
||||||
|
|
||||||
|
* logrus/text_formatter: don't emit empty msg
|
||||||
|
* logrus/hooks/airbrake: move out of main repository
|
||||||
|
* logrus/hooks/sentry: move out of main repository
|
||||||
|
* logrus/hooks/papertrail: move out of main repository
|
||||||
|
* logrus/hooks/bugsnag: move out of main repository
|
||||||
|
* logrus/core: run tests with `-race`
|
||||||
|
* logrus/core: detect TTY based on `stderr`
|
||||||
|
* logrus/core: support `WithError` on logger
|
||||||
|
* logrus/core: Solaris support
|
||||||
|
|
||||||
|
# 0.8.7
|
||||||
|
|
||||||
|
* logrus/core: fix possible race (#216)
|
||||||
|
* logrus/doc: small typo fixes and doc improvements
|
||||||
|
|
||||||
|
|
||||||
|
# 0.8.6
|
||||||
|
|
||||||
|
* hooks/raven: allow passing an initialized client
|
||||||
|
|
||||||
|
# 0.8.5
|
||||||
|
|
||||||
|
* logrus/core: revert #208
|
||||||
|
|
||||||
|
# 0.8.4
|
||||||
|
|
||||||
|
* formatter/text: fix data race (#218)
|
||||||
|
|
||||||
|
# 0.8.3
|
||||||
|
|
||||||
|
* logrus/core: fix entry log level (#208)
|
||||||
|
* logrus/core: improve performance of text formatter by 40%
|
||||||
|
* logrus/core: expose `LevelHooks` type
|
||||||
|
* logrus/core: add support for DragonflyBSD and NetBSD
|
||||||
|
* formatter/text: print structs more verbosely
|
||||||
|
|
||||||
|
# 0.8.2
|
||||||
|
|
||||||
|
* logrus: fix more Fatal family functions
|
||||||
|
|
||||||
|
# 0.8.1
|
||||||
|
|
||||||
|
* logrus: fix not exiting on `Fatalf` and `Fatalln`
|
||||||
|
|
||||||
|
# 0.8.0
|
||||||
|
|
||||||
|
* logrus: defaults to stderr instead of stdout
|
||||||
|
* hooks/sentry: add special field for `*http.Request`
|
||||||
|
* formatter/text: ignore Windows for colors
|
||||||
|
|
||||||
|
# 0.7.3
|
||||||
|
|
||||||
|
* formatter/\*: allow configuration of timestamp layout
|
||||||
|
|
||||||
|
# 0.7.2
|
||||||
|
|
||||||
|
* formatter/text: Add configuration option for time format (#158)
|
||||||
21
vendor/github.com/sirupsen/logrus/LICENSE
generated
vendored
Normal file
21
vendor/github.com/sirupsen/logrus/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Simon Eskildsen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
515
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
Normal file
515
vendor/github.com/sirupsen/logrus/README.md
generated
vendored
Normal file
@@ -0,0 +1,515 @@
|
|||||||
|
# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [](https://github.com/sirupsen/logrus/actions?query=workflow%3ACI) [](https://travis-ci.org/sirupsen/logrus) [](https://pkg.go.dev/github.com/sirupsen/logrus)
|
||||||
|
|
||||||
|
Logrus is a structured logger for Go (golang), completely API compatible with
|
||||||
|
the standard library logger.
|
||||||
|
|
||||||
|
**Logrus is in maintenance-mode.** We will not be introducing new features. It's
|
||||||
|
simply too hard to do in a way that won't break many people's projects, which is
|
||||||
|
the last thing you want from your Logging library (again...).
|
||||||
|
|
||||||
|
This does not mean Logrus is dead. Logrus will continue to be maintained for
|
||||||
|
security, (backwards compatible) bug fixes, and performance (where we are
|
||||||
|
limited by the interface).
|
||||||
|
|
||||||
|
I believe Logrus' biggest contribution is to have played a part in today's
|
||||||
|
widespread use of structured logging in Golang. There doesn't seem to be a
|
||||||
|
reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
|
||||||
|
community has built those independently. Many fantastic alternatives have sprung
|
||||||
|
up. Logrus would look like those, had it been re-designed with what we know
|
||||||
|
about structured logging in Go today. Check out, for example,
|
||||||
|
[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
|
||||||
|
|
||||||
|
[zerolog]: https://github.com/rs/zerolog
|
||||||
|
[zap]: https://github.com/uber-go/zap
|
||||||
|
[apex]: https://github.com/apex/log
|
||||||
|
|
||||||
|
**Seeing weird case-sensitive problems?** It's in the past been possible to
|
||||||
|
import Logrus as both upper- and lower-case. Due to the Go package environment,
|
||||||
|
this caused issues in the community and we needed a standard. Some environments
|
||||||
|
experienced problems with the upper-case variant, so the lower-case was decided.
|
||||||
|
Everything using `logrus` will need to use the lower-case:
|
||||||
|
`github.com/sirupsen/logrus`. Any package that isn't, should be changed.
|
||||||
|
|
||||||
|
To fix Glide, see [these
|
||||||
|
comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
|
||||||
|
For an in-depth explanation of the casing issue, see [this
|
||||||
|
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
|
||||||
|
|
||||||
|
Nicely color-coded in development (when a TTY is attached, otherwise just
|
||||||
|
plain text):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
With `log.SetFormatter(&log.JSONFormatter{})`, for easy parsing by logstash
|
||||||
|
or Splunk:
|
||||||
|
|
||||||
|
```text
|
||||||
|
{"animal":"walrus","level":"info","msg":"A group of walrus emerges from the
|
||||||
|
ocean","size":10,"time":"2014-03-10 19:57:38.562264131 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"warning","msg":"The group's number increased tremendously!",
|
||||||
|
"number":122,"omg":true,"time":"2014-03-10 19:57:38.562471297 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"A giant walrus appears!",
|
||||||
|
"size":10,"time":"2014-03-10 19:57:38.562500591 -0400 EDT"}
|
||||||
|
|
||||||
|
{"animal":"walrus","level":"info","msg":"Tremendously sized cow enters the ocean.",
|
||||||
|
"size":9,"time":"2014-03-10 19:57:38.562527896 -0400 EDT"}
|
||||||
|
|
||||||
|
{"level":"fatal","msg":"The ice breaks!","number":100,"omg":true,
|
||||||
|
"time":"2014-03-10 19:57:38.562543128 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
With the default `log.SetFormatter(&log.TextFormatter{})` when a TTY is not
|
||||||
|
attached, the output is compatible with the
|
||||||
|
[logfmt](http://godoc.org/github.com/kr/logfmt) format:
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true
|
||||||
|
```
|
||||||
|
To ensure this behaviour even if a TTY is attached, set your formatter as follows:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.SetFormatter(&log.TextFormatter{
|
||||||
|
DisableColors: true,
|
||||||
|
FullTimestamp: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logging Method Name
|
||||||
|
|
||||||
|
If you wish to add the calling method as a field, instruct the logger via:
|
||||||
|
```go
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
```
|
||||||
|
This adds the caller as 'method' like so:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
|
||||||
|
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}
|
||||||
|
```
|
||||||
|
|
||||||
|
```text
|
||||||
|
time="2015-03-26T01:27:38-04:00" level=fatal method=github.com/sirupsen/arcticcreatures.migrate msg="a penguin swims by" animal=penguin
|
||||||
|
```
|
||||||
|
Note that this does add measurable overhead - the cost will depend on the version of Go, but is
|
||||||
|
between 20 and 40% in recent tests with 1.6 and 1.7. You can validate this in your
|
||||||
|
environment via benchmarks:
|
||||||
|
```
|
||||||
|
go test -bench=.*CallerTracing
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### Case-sensitivity
|
||||||
|
|
||||||
|
The organization's name was changed to lower-case--and this will not be changed
|
||||||
|
back. If you are getting import conflicts due to case sensitivity, please use
|
||||||
|
the lower-case import: `github.com/sirupsen/logrus`.
|
||||||
|
|
||||||
|
#### Example
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that it's completely api-compatible with the stdlib logger, so you can
|
||||||
|
replace your `log` imports everywhere with `log "github.com/sirupsen/logrus"`
|
||||||
|
and you'll now have the flexibility of Logrus. You can customize it all you
|
||||||
|
want:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Log as JSON instead of the default ASCII formatter.
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
|
||||||
|
// Output to stdout instead of the default stderr
|
||||||
|
// Can be any io.Writer, see below for File example
|
||||||
|
log.SetOutput(os.Stdout)
|
||||||
|
|
||||||
|
// Only log the warning severity or above.
|
||||||
|
log.SetLevel(log.WarnLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 122,
|
||||||
|
}).Warn("The group's number increased tremendously!")
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"omg": true,
|
||||||
|
"number": 100,
|
||||||
|
}).Fatal("The ice breaks!")
|
||||||
|
|
||||||
|
// A common pattern is to re-use fields between logging statements by re-using
|
||||||
|
// the logrus.Entry returned from WithFields()
|
||||||
|
contextLogger := log.WithFields(log.Fields{
|
||||||
|
"common": "this is a common field",
|
||||||
|
"other": "I also should be logged always",
|
||||||
|
})
|
||||||
|
|
||||||
|
contextLogger.Info("I'll be logged with common and other field")
|
||||||
|
contextLogger.Info("Me too")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For more advanced usage such as logging to multiple locations from the same
|
||||||
|
application, you can also create an instance of the `logrus` Logger:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create a new instance of the logger. You can have any number of instances.
|
||||||
|
var log = logrus.New()
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// The API for setting attributes is a little different than the package level
|
||||||
|
// exported logger. See Godoc.
|
||||||
|
log.Out = os.Stdout
|
||||||
|
|
||||||
|
// You could set this to any `io.Writer` such as a file
|
||||||
|
// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
// if err == nil {
|
||||||
|
// log.Out = file
|
||||||
|
// } else {
|
||||||
|
// log.Info("Failed to log to file, using default stderr")
|
||||||
|
// }
|
||||||
|
|
||||||
|
log.WithFields(logrus.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A group of walrus emerges from the ocean")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fields
|
||||||
|
|
||||||
|
Logrus encourages careful, structured logging through logging fields instead of
|
||||||
|
long, unparseable error messages. For example, instead of: `log.Fatalf("Failed
|
||||||
|
to send event %s to topic %s with key %d")`, you should log the much more
|
||||||
|
discoverable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"event": event,
|
||||||
|
"topic": topic,
|
||||||
|
"key": key,
|
||||||
|
}).Fatal("Failed to send event")
|
||||||
|
```
|
||||||
|
|
||||||
|
We've found this API forces you to think about logging in a way that produces
|
||||||
|
much more useful logging messages. We've been in countless situations where just
|
||||||
|
a single added field to a log statement that was already there would've saved us
|
||||||
|
hours. The `WithFields` call is optional.
|
||||||
|
|
||||||
|
In general, with Logrus using any of the `printf`-family functions should be
|
||||||
|
seen as a hint you should add a field, however, you can still use the
|
||||||
|
`printf`-family functions with Logrus.
|
||||||
|
|
||||||
|
#### Default Fields
|
||||||
|
|
||||||
|
Often it's helpful to have fields _always_ attached to log statements in an
|
||||||
|
application or parts of one. For example, you may want to always log the
|
||||||
|
`request_id` and `user_ip` in the context of a request. Instead of writing
|
||||||
|
`log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})` on
|
||||||
|
every line, you can create a `logrus.Entry` to pass around instead:
|
||||||
|
|
||||||
|
```go
|
||||||
|
requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
|
||||||
|
requestLogger.Info("something happened on that request") # will log request_id and user_ip
|
||||||
|
requestLogger.Warn("something not great happened")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Hooks
|
||||||
|
|
||||||
|
You can add hooks for logging levels. For example to send errors to an exception
|
||||||
|
tracking service on `Error`, `Fatal` and `Panic`, info to StatsD or log to
|
||||||
|
multiple places simultaneously, e.g. syslog.
|
||||||
|
|
||||||
|
Logrus comes with [built-in hooks](hooks/). Add those, or your custom hook, in
|
||||||
|
`init`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
"gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"
|
||||||
|
logrus_syslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||||
|
"log/syslog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
// Use the Airbrake hook to report errors that have Error severity or above to
|
||||||
|
// an exception tracker. You can create custom hooks, see the Hooks section.
|
||||||
|
log.AddHook(airbrake.NewHook(123, "xyz", "production"))
|
||||||
|
|
||||||
|
hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to connect to local syslog daemon")
|
||||||
|
} else {
|
||||||
|
log.AddHook(hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
|
||||||
|
|
||||||
|
A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
|
||||||
|
|
||||||
|
|
||||||
|
#### Level logging
|
||||||
|
|
||||||
|
Logrus has seven logging levels: Trace, Debug, Info, Warning, Error, Fatal and Panic.
|
||||||
|
|
||||||
|
```go
|
||||||
|
log.Trace("Something very low level.")
|
||||||
|
log.Debug("Useful debugging information.")
|
||||||
|
log.Info("Something noteworthy happened!")
|
||||||
|
log.Warn("You should probably take a look at this.")
|
||||||
|
log.Error("Something failed but I'm not quitting.")
|
||||||
|
// Calls os.Exit(1) after logging
|
||||||
|
log.Fatal("Bye.")
|
||||||
|
// Calls panic() after logging
|
||||||
|
log.Panic("I'm bailing.")
|
||||||
|
```
|
||||||
|
|
||||||
|
You can set the logging level on a `Logger`, then it will only log entries with
|
||||||
|
that severity or anything above it:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Will log anything that is info or above (warn, error, fatal, panic). Default.
|
||||||
|
log.SetLevel(log.InfoLevel)
|
||||||
|
```
|
||||||
|
|
||||||
|
It may be useful to set `log.Level = logrus.DebugLevel` in a debug or verbose
|
||||||
|
environment if your application has that.
|
||||||
|
|
||||||
|
Note: If you want different log levels for global (`log.SetLevel(...)`) and syslog logging, please check the [syslog hook README](hooks/syslog/README.md#different-log-levels-for-local-and-remote-logging).
|
||||||
|
|
||||||
|
#### Entries
|
||||||
|
|
||||||
|
Besides the fields added with `WithField` or `WithFields` some fields are
|
||||||
|
automatically added to all logging events:
|
||||||
|
|
||||||
|
1. `time`. The timestamp when the entry was created.
|
||||||
|
2. `msg`. The logging message passed to `{Info,Warn,Error,Fatal,Panic}` after
|
||||||
|
the `AddFields` call. E.g. `Failed to send event.`
|
||||||
|
3. `level`. The logging level. E.g. `info`.
|
||||||
|
|
||||||
|
#### Environments
|
||||||
|
|
||||||
|
Logrus has no notion of environment.
|
||||||
|
|
||||||
|
If you wish for hooks and formatters to only be used in specific environments,
|
||||||
|
you should handle that yourself. For example, if your application has a global
|
||||||
|
variable `Environment`, which is a string representation of the environment you
|
||||||
|
could do:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// do something here to set environment depending on an environment variable
|
||||||
|
// or command-line flag
|
||||||
|
if Environment == "production" {
|
||||||
|
log.SetFormatter(&log.JSONFormatter{})
|
||||||
|
} else {
|
||||||
|
// The TextFormatter is default, you don't actually have to do this.
|
||||||
|
log.SetFormatter(&log.TextFormatter{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This configuration is how `logrus` was intended to be used, but JSON in
|
||||||
|
production is mostly only useful if you do log aggregation with tools like
|
||||||
|
Splunk or Logstash.
|
||||||
|
|
||||||
|
#### Formatters
|
||||||
|
|
||||||
|
The built-in logging formatters are:
|
||||||
|
|
||||||
|
* `logrus.TextFormatter`. Logs the event in colors if stdout is a tty, otherwise
|
||||||
|
without colors.
|
||||||
|
* *Note:* to force colored output when there is no TTY, set the `ForceColors`
|
||||||
|
field to `true`. To force no colored output even if there is a TTY set the
|
||||||
|
`DisableColors` field to `true`. For Windows, see
|
||||||
|
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
|
||||||
|
* When colors are enabled, levels are truncated to 4 characters by default. To disable
|
||||||
|
truncation set the `DisableLevelTruncation` field to `true`.
|
||||||
|
* When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
|
||||||
|
* `logrus.JSONFormatter`. Logs fields as JSON.
|
||||||
|
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
|
||||||
|
|
||||||
|
Third party logging formatters:
|
||||||
|
|
||||||
|
* [`FluentdFormatter`](https://github.com/joonix/log). Formats entries that can be parsed by Kubernetes and Google Container Engine.
|
||||||
|
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
|
||||||
|
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
|
||||||
|
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
|
||||||
|
* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
|
||||||
|
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
|
||||||
|
* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
|
||||||
|
* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
|
||||||
|
|
||||||
|
You can define your formatter by implementing the `Formatter` interface,
|
||||||
|
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
|
||||||
|
`Fields` type (`map[string]interface{}`) with all your fields as well as the
|
||||||
|
default ones (see Entries section above):
|
||||||
|
|
||||||
|
```go
|
||||||
|
type MyJSONFormatter struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
log.SetFormatter(new(MyJSONFormatter))
|
||||||
|
|
||||||
|
func (f *MyJSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
// Note this doesn't include Time, Level and Message which are available on
|
||||||
|
// the Entry. Consult `godoc` on information about those fields or read the
|
||||||
|
// source of the official loggers.
|
||||||
|
serialized, err := json.Marshal(entry.Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to marshal fields to JSON, %w", err)
|
||||||
|
}
|
||||||
|
return append(serialized, '\n'), nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Logger as an `io.Writer`
|
||||||
|
|
||||||
|
Logrus can be transformed into an `io.Writer`. That writer is the end of an `io.Pipe` and it is your responsibility to close it.
|
||||||
|
|
||||||
|
```go
|
||||||
|
w := logger.Writer()
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
// create a stdlib log.Logger that writes to
|
||||||
|
// logrus.Logger.
|
||||||
|
ErrorLog: log.New(w, "", 0),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line written to that writer will be printed the usual way, using formatters
|
||||||
|
and hooks. The level for those entries is `info`.
|
||||||
|
|
||||||
|
This means that we can override the standard library logger easily:
|
||||||
|
|
||||||
|
```go
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Formatter = &logrus.JSONFormatter{}
|
||||||
|
|
||||||
|
// Use logrus for standard log output
|
||||||
|
// Note that `log` here references stdlib's log
|
||||||
|
// Not logrus imported under the name `log`.
|
||||||
|
log.SetOutput(logger.Writer())
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Rotation
|
||||||
|
|
||||||
|
Log rotation is not provided with Logrus. Log rotation should be done by an
|
||||||
|
external program (like `logrotate(8)`) that can compress and delete old log
|
||||||
|
entries. It should not be a feature of the application-level logger.
|
||||||
|
|
||||||
|
#### Tools
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
| ---- | ----------- |
|
||||||
|
|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|
||||||
|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
|
||||||
|
|
||||||
|
#### Testing
|
||||||
|
|
||||||
|
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
|
||||||
|
|
||||||
|
* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
|
||||||
|
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
|
||||||
|
|
||||||
|
```go
|
||||||
|
import(
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSomething(t*testing.T){
|
||||||
|
logger, hook := test.NewNullLogger()
|
||||||
|
logger.Error("Helloerror")
|
||||||
|
|
||||||
|
assert.Equal(t, 1, len(hook.Entries))
|
||||||
|
assert.Equal(t, logrus.ErrorLevel, hook.LastEntry().Level)
|
||||||
|
assert.Equal(t, "Helloerror", hook.LastEntry().Message)
|
||||||
|
|
||||||
|
hook.Reset()
|
||||||
|
assert.Nil(t, hook.LastEntry())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Fatal handlers
|
||||||
|
|
||||||
|
Logrus can register one or more functions that will be called when any `fatal`
|
||||||
|
level message is logged. The registered handlers will be executed before
|
||||||
|
logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
|
||||||
|
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
|
||||||
|
|
||||||
|
```
|
||||||
|
...
|
||||||
|
handler := func() {
|
||||||
|
// gracefully shutdown something...
|
||||||
|
}
|
||||||
|
logrus.RegisterExitHandler(handler)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Thread safety
|
||||||
|
|
||||||
|
By default, Logger is protected by a mutex for concurrent writes. The mutex is held when calling hooks and writing logs.
|
||||||
|
If you are sure such locking is not needed, you can call logger.SetNoLock() to disable the locking.
|
||||||
|
|
||||||
|
Situation when locking is not needed includes:
|
||||||
|
|
||||||
|
* You have no hooks registered, or hooks calling is already thread-safe.
|
||||||
|
|
||||||
|
* Writing to logger.Out is already thread-safe, for example:
|
||||||
|
|
||||||
|
1) logger.Out is protected by locks.
|
||||||
|
|
||||||
|
2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
|
||||||
|
|
||||||
|
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
|
||||||
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
76
vendor/github.com/sirupsen/logrus/alt_exit.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
// The following code was sourced and modified from the
|
||||||
|
// https://github.com/tebeka/atexit package governed by the following license:
|
||||||
|
//
|
||||||
|
// Copyright (c) 2012 Miki Tebeka <miki.tebeka@gmail.com>.
|
||||||
|
//
|
||||||
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
// this software and associated documentation files (the "Software"), to deal in
|
||||||
|
// the Software without restriction, including without limitation the rights to
|
||||||
|
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
// the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
// subject to the following conditions:
|
||||||
|
//
|
||||||
|
// The above copyright notice and this permission notice shall be included in all
|
||||||
|
// copies or substantial portions of the Software.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var handlers = []func(){}
|
||||||
|
|
||||||
|
func runHandler(handler func()) {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error: Logrus exit handler error:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
handler()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runHandlers() {
|
||||||
|
for _, handler := range handlers {
|
||||||
|
runHandler(handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit runs all the Logrus atexit handlers and then terminates the program using os.Exit(code)
|
||||||
|
func Exit(code int) {
|
||||||
|
runHandlers()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExitHandler appends a Logrus Exit handler to the list of handlers,
|
||||||
|
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||||
|
// any Fatal log entry is made.
|
||||||
|
//
|
||||||
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
|
// closing database connections, or sending a alert that the application is
|
||||||
|
// closing.
|
||||||
|
func RegisterExitHandler(handler func()) {
|
||||||
|
handlers = append(handlers, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeferExitHandler prepends a Logrus Exit handler to the list of handlers,
|
||||||
|
// call logrus.Exit to invoke all handlers. The handlers will also be invoked when
|
||||||
|
// any Fatal log entry is made.
|
||||||
|
//
|
||||||
|
// This method is useful when a caller wishes to use logrus to log a fatal
|
||||||
|
// message but also needs to gracefully shutdown. An example usecase could be
|
||||||
|
// closing database connections, or sending a alert that the application is
|
||||||
|
// closing.
|
||||||
|
func DeferExitHandler(handler func()) {
|
||||||
|
handlers = append([]func(){handler}, handlers...)
|
||||||
|
}
|
||||||
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
14
vendor/github.com/sirupsen/logrus/appveyor.yml
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
version: "{build}"
|
||||||
|
platform: x64
|
||||||
|
clone_folder: c:\gopath\src\github.com\sirupsen\logrus
|
||||||
|
environment:
|
||||||
|
GOPATH: c:\gopath
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
install:
|
||||||
|
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
|
||||||
|
- go version
|
||||||
|
build_script:
|
||||||
|
- go get -t
|
||||||
|
- go test
|
||||||
43
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
43
vendor/github.com/sirupsen/logrus/buffer_pool.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bufferPool BufferPool
|
||||||
|
)
|
||||||
|
|
||||||
|
type BufferPool interface {
|
||||||
|
Put(*bytes.Buffer)
|
||||||
|
Get() *bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
type defaultPool struct {
|
||||||
|
pool *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *defaultPool) Put(buf *bytes.Buffer) {
|
||||||
|
p.pool.Put(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *defaultPool) Get() *bytes.Buffer {
|
||||||
|
return p.pool.Get().(*bytes.Buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBufferPool allows to replace the default logrus buffer pool
|
||||||
|
// to better meets the specific needs of an application.
|
||||||
|
func SetBufferPool(bp BufferPool) {
|
||||||
|
bufferPool = bp
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetBufferPool(&defaultPool{
|
||||||
|
pool: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
Normal file
26
vendor/github.com/sirupsen/logrus/doc.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Package logrus is a structured logger for Go, completely API compatible with the standard library logger.
|
||||||
|
|
||||||
|
|
||||||
|
The simplest way to use Logrus is simply the package-level exported logger:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"animal": "walrus",
|
||||||
|
"number": 1,
|
||||||
|
"size": 10,
|
||||||
|
}).Info("A walrus appears")
|
||||||
|
}
|
||||||
|
|
||||||
|
Output:
|
||||||
|
time="2015-09-07T08:48:33Z" level=info msg="A walrus appears" animal=walrus number=1 size=10
|
||||||
|
|
||||||
|
For a full guide visit https://github.com/sirupsen/logrus
|
||||||
|
*/
|
||||||
|
package logrus
|
||||||
442
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
Normal file
442
vendor/github.com/sirupsen/logrus/entry.go
generated
vendored
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
|
||||||
|
// qualified package name, cached at first use
|
||||||
|
logrusPackage string
|
||||||
|
|
||||||
|
// Positions in the call stack when tracing to report the calling method
|
||||||
|
minimumCallerDepth int
|
||||||
|
|
||||||
|
// Used for caller information initialisation
|
||||||
|
callerInitOnce sync.Once
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maximumCallerDepth int = 25
|
||||||
|
knownLogrusFrames int = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// start at the bottom of the stack before the package-name cache is primed
|
||||||
|
minimumCallerDepth = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Defines the key when adding errors using WithError.
|
||||||
|
var ErrorKey = "error"
|
||||||
|
|
||||||
|
// An entry is the final or intermediate Logrus logging entry. It contains all
|
||||||
|
// the fields passed with WithField{,s}. It's finally logged when Trace, Debug,
|
||||||
|
// Info, Warn, Error, Fatal or Panic is called on it. These objects can be
|
||||||
|
// reused and passed around as much as you wish to avoid field duplication.
|
||||||
|
type Entry struct {
|
||||||
|
Logger *Logger
|
||||||
|
|
||||||
|
// Contains all the fields set by the user.
|
||||||
|
Data Fields
|
||||||
|
|
||||||
|
// Time at which the log entry was created
|
||||||
|
Time time.Time
|
||||||
|
|
||||||
|
// Level the log entry was logged at: Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
// This field will be set on entry firing and the value will be equal to the one in Logger struct field.
|
||||||
|
Level Level
|
||||||
|
|
||||||
|
// Calling method, with package name
|
||||||
|
Caller *runtime.Frame
|
||||||
|
|
||||||
|
// Message passed to Trace, Debug, Info, Warn, Error, Fatal or Panic
|
||||||
|
Message string
|
||||||
|
|
||||||
|
// When formatter is called in entry.log(), a Buffer may be set to entry
|
||||||
|
Buffer *bytes.Buffer
|
||||||
|
|
||||||
|
// Contains the context set by the user. Useful for hook processing etc.
|
||||||
|
Context context.Context
|
||||||
|
|
||||||
|
// err may contain a field formatting error
|
||||||
|
err string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEntry(logger *Logger) *Entry {
|
||||||
|
return &Entry{
|
||||||
|
Logger: logger,
|
||||||
|
// Default is three fields, plus one optional. Give a little extra room.
|
||||||
|
Data: make(Fields, 6),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Dup() *Entry {
|
||||||
|
data := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, Context: entry.Context, err: entry.err}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the bytes representation of this entry from the formatter.
|
||||||
|
func (entry *Entry) Bytes() ([]byte, error) {
|
||||||
|
return entry.Logger.Formatter.Format(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the string representation from the reader and ultimately the
|
||||||
|
// formatter.
|
||||||
|
func (entry *Entry) String() (string, error) {
|
||||||
|
serialized, err := entry.Bytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
str := string(serialized)
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field (using the key defined in ErrorKey) to the Entry.
|
||||||
|
func (entry *Entry) WithError(err error) *Entry {
|
||||||
|
return entry.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a context to the Entry.
|
||||||
|
func (entry *Entry) WithContext(ctx context.Context) *Entry {
|
||||||
|
dataCopy := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
dataCopy[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a single field to the Entry.
|
||||||
|
func (entry *Entry) WithField(key string, value interface{}) *Entry {
|
||||||
|
return entry.WithFields(Fields{key: value})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a map of fields to the Entry.
|
||||||
|
func (entry *Entry) WithFields(fields Fields) *Entry {
|
||||||
|
data := make(Fields, len(entry.Data)+len(fields))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
fieldErr := entry.err
|
||||||
|
for k, v := range fields {
|
||||||
|
isErrField := false
|
||||||
|
if t := reflect.TypeOf(v); t != nil {
|
||||||
|
switch {
|
||||||
|
case t.Kind() == reflect.Func, t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Func:
|
||||||
|
isErrField = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isErrField {
|
||||||
|
tmp := fmt.Sprintf("can not add field %q", k)
|
||||||
|
if fieldErr != "" {
|
||||||
|
fieldErr = entry.err + ", " + tmp
|
||||||
|
} else {
|
||||||
|
fieldErr = tmp
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: data, Time: entry.Time, err: fieldErr, Context: entry.Context}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the time of the Entry.
|
||||||
|
func (entry *Entry) WithTime(t time.Time) *Entry {
|
||||||
|
dataCopy := make(Fields, len(entry.Data))
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
dataCopy[k] = v
|
||||||
|
}
|
||||||
|
return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPackageName reduces a fully qualified function name to the package name
|
||||||
|
// There really ought to be to be a better way...
|
||||||
|
func getPackageName(f string) string {
|
||||||
|
for {
|
||||||
|
lastPeriod := strings.LastIndex(f, ".")
|
||||||
|
lastSlash := strings.LastIndex(f, "/")
|
||||||
|
if lastPeriod > lastSlash {
|
||||||
|
f = f[:lastPeriod]
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
// getCaller retrieves the name of the first non-logrus calling function
|
||||||
|
func getCaller() *runtime.Frame {
|
||||||
|
// cache this package's fully-qualified name
|
||||||
|
callerInitOnce.Do(func() {
|
||||||
|
pcs := make([]uintptr, maximumCallerDepth)
|
||||||
|
_ = runtime.Callers(0, pcs)
|
||||||
|
|
||||||
|
// dynamic get the package name and the minimum caller depth
|
||||||
|
for i := 0; i < maximumCallerDepth; i++ {
|
||||||
|
funcName := runtime.FuncForPC(pcs[i]).Name()
|
||||||
|
if strings.Contains(funcName, "getCaller") {
|
||||||
|
logrusPackage = getPackageName(funcName)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minimumCallerDepth = knownLogrusFrames
|
||||||
|
})
|
||||||
|
|
||||||
|
// Restrict the lookback frames to avoid runaway lookups
|
||||||
|
pcs := make([]uintptr, maximumCallerDepth)
|
||||||
|
depth := runtime.Callers(minimumCallerDepth, pcs)
|
||||||
|
frames := runtime.CallersFrames(pcs[:depth])
|
||||||
|
|
||||||
|
for f, again := frames.Next(); again; f, again = frames.Next() {
|
||||||
|
pkg := getPackageName(f.Function)
|
||||||
|
|
||||||
|
// If the caller isn't part of this package, we're done
|
||||||
|
if pkg != logrusPackage {
|
||||||
|
return &f //nolint:scopelint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we got here, we failed to find the caller's context
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry Entry) HasCaller() (has bool) {
|
||||||
|
return entry.Logger != nil &&
|
||||||
|
entry.Logger.ReportCaller &&
|
||||||
|
entry.Caller != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) log(level Level, msg string) {
|
||||||
|
var buffer *bytes.Buffer
|
||||||
|
|
||||||
|
newEntry := entry.Dup()
|
||||||
|
|
||||||
|
if newEntry.Time.IsZero() {
|
||||||
|
newEntry.Time = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry.Level = level
|
||||||
|
newEntry.Message = msg
|
||||||
|
|
||||||
|
newEntry.Logger.mu.Lock()
|
||||||
|
reportCaller := newEntry.Logger.ReportCaller
|
||||||
|
bufPool := newEntry.getBufferPool()
|
||||||
|
newEntry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
if reportCaller {
|
||||||
|
newEntry.Caller = getCaller()
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry.fireHooks()
|
||||||
|
buffer = bufPool.Get()
|
||||||
|
defer func() {
|
||||||
|
newEntry.Buffer = nil
|
||||||
|
buffer.Reset()
|
||||||
|
bufPool.Put(buffer)
|
||||||
|
}()
|
||||||
|
buffer.Reset()
|
||||||
|
newEntry.Buffer = buffer
|
||||||
|
|
||||||
|
newEntry.write()
|
||||||
|
|
||||||
|
newEntry.Buffer = nil
|
||||||
|
|
||||||
|
// To avoid Entry#log() returning a value that only would make sense for
|
||||||
|
// panic() to use in Entry#Panic(), we avoid the allocation by checking
|
||||||
|
// directly here.
|
||||||
|
if level <= PanicLevel {
|
||||||
|
panic(newEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) getBufferPool() (pool BufferPool) {
|
||||||
|
if entry.Logger.BufferPool != nil {
|
||||||
|
return entry.Logger.BufferPool
|
||||||
|
}
|
||||||
|
return bufferPool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) fireHooks() {
|
||||||
|
var tmpHooks LevelHooks
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
tmpHooks = make(LevelHooks, len(entry.Logger.Hooks))
|
||||||
|
for k, v := range entry.Logger.Hooks {
|
||||||
|
tmpHooks[k] = v
|
||||||
|
}
|
||||||
|
entry.Logger.mu.Unlock()
|
||||||
|
|
||||||
|
err := tmpHooks.Fire(entry.Level, entry)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to fire hook: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) write() {
|
||||||
|
entry.Logger.mu.Lock()
|
||||||
|
defer entry.Logger.mu.Unlock()
|
||||||
|
serialized, err := entry.Logger.Formatter.Format(entry)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err := entry.Logger.Out.Write(serialized); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log will log a message at the level given as parameter.
|
||||||
|
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
||||||
|
// For this behaviour Entry.Panic or Entry.Fatal should be used instead.
|
||||||
|
func (entry *Entry) Log(level Level, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.log(level, fmt.Sprint(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Trace(args ...interface{}) {
|
||||||
|
entry.Log(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debug(args ...interface{}) {
|
||||||
|
entry.Log(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Print(args ...interface{}) {
|
||||||
|
entry.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Info(args ...interface{}) {
|
||||||
|
entry.Log(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warn(args ...interface{}) {
|
||||||
|
entry.Log(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warning(args ...interface{}) {
|
||||||
|
entry.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Error(args ...interface{}) {
|
||||||
|
entry.Log(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatal(args ...interface{}) {
|
||||||
|
entry.Log(FatalLevel, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panic(args ...interface{}) {
|
||||||
|
entry.Log(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Printf family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Logf(level Level, format string, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.Log(level, fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Tracef(format string, args ...interface{}) {
|
||||||
|
entry.Logf(TraceLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debugf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(DebugLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infof(format string, args ...interface{}) {
|
||||||
|
entry.Logf(InfoLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Printf(format string, args ...interface{}) {
|
||||||
|
entry.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(WarnLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningf(format string, args ...interface{}) {
|
||||||
|
entry.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(ErrorLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(FatalLevel, format, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicf(format string, args ...interface{}) {
|
||||||
|
entry.Logf(PanicLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry Println family functions
|
||||||
|
|
||||||
|
func (entry *Entry) Logln(level Level, args ...interface{}) {
|
||||||
|
if entry.Logger.IsLevelEnabled(level) {
|
||||||
|
entry.Log(level, entry.sprintlnn(args...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Traceln(args ...interface{}) {
|
||||||
|
entry.Logln(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Debugln(args ...interface{}) {
|
||||||
|
entry.Logln(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Infoln(args ...interface{}) {
|
||||||
|
entry.Logln(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Println(args ...interface{}) {
|
||||||
|
entry.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warnln(args ...interface{}) {
|
||||||
|
entry.Logln(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Warningln(args ...interface{}) {
|
||||||
|
entry.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Errorln(args ...interface{}) {
|
||||||
|
entry.Logln(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Fatalln(args ...interface{}) {
|
||||||
|
entry.Logln(FatalLevel, args...)
|
||||||
|
entry.Logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (entry *Entry) Panicln(args ...interface{}) {
|
||||||
|
entry.Logln(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sprintlnn => Sprint no newline. This is to get the behavior of how
|
||||||
|
// fmt.Sprintln where spaces are always added between operands, regardless of
|
||||||
|
// their type. Instead of vendoring the Sprintln implementation to spare a
|
||||||
|
// string allocation, we do the simplest thing.
|
||||||
|
func (entry *Entry) sprintlnn(args ...interface{}) string {
|
||||||
|
msg := fmt.Sprintln(args...)
|
||||||
|
return msg[:len(msg)-1]
|
||||||
|
}
|
||||||
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
Normal file
270
vendor/github.com/sirupsen/logrus/exported.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// std is the name of the standard logger in stdlib `log`
|
||||||
|
std = New()
|
||||||
|
)
|
||||||
|
|
||||||
|
func StandardLogger() *Logger {
|
||||||
|
return std
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the standard logger output.
|
||||||
|
func SetOutput(out io.Writer) {
|
||||||
|
std.SetOutput(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the standard logger formatter.
|
||||||
|
func SetFormatter(formatter Formatter) {
|
||||||
|
std.SetFormatter(formatter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetReportCaller sets whether the standard logger will include the calling
|
||||||
|
// method as a field.
|
||||||
|
func SetReportCaller(include bool) {
|
||||||
|
std.SetReportCaller(include)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the standard logger level.
|
||||||
|
func SetLevel(level Level) {
|
||||||
|
std.SetLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the standard logger level.
|
||||||
|
func GetLevel() Level {
|
||||||
|
return std.GetLevel()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLevelEnabled checks if the log level of the standard logger is greater than the level param
|
||||||
|
func IsLevelEnabled(level Level) bool {
|
||||||
|
return std.IsLevelEnabled(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the standard logger hooks.
|
||||||
|
func AddHook(hook Hook) {
|
||||||
|
std.AddHook(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithError creates an entry from the standard logger and adds an error to it, using the value defined in ErrorKey as key.
|
||||||
|
func WithError(err error) *Entry {
|
||||||
|
return std.WithField(ErrorKey, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext creates an entry from the standard logger and adds a context to it.
|
||||||
|
func WithContext(ctx context.Context) *Entry {
|
||||||
|
return std.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField creates an entry from the standard logger and adds a field to
|
||||||
|
// it. If you want multiple fields, use `WithFields`.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithField(key string, value interface{}) *Entry {
|
||||||
|
return std.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithFields creates an entry from the standard logger and adds multiple
|
||||||
|
// fields to it. This is simply a helper for `WithField`, invoking it
|
||||||
|
// once for each field.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithFields(fields Fields) *Entry {
|
||||||
|
return std.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTime creates an entry from the standard logger and overrides the time of
|
||||||
|
// logs generated with it.
|
||||||
|
//
|
||||||
|
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
|
||||||
|
// or Panic on the Entry it returns.
|
||||||
|
func WithTime(t time.Time) *Entry {
|
||||||
|
return std.WithTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trace logs a message at level Trace on the standard logger.
|
||||||
|
func Trace(args ...interface{}) {
|
||||||
|
std.Trace(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a message at level Debug on the standard logger.
|
||||||
|
func Debug(args ...interface{}) {
|
||||||
|
std.Debug(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print logs a message at level Info on the standard logger.
|
||||||
|
func Print(args ...interface{}) {
|
||||||
|
std.Print(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs a message at level Info on the standard logger.
|
||||||
|
func Info(args ...interface{}) {
|
||||||
|
std.Info(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a message at level Warn on the standard logger.
|
||||||
|
func Warn(args ...interface{}) {
|
||||||
|
std.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning logs a message at level Warn on the standard logger.
|
||||||
|
func Warning(args ...interface{}) {
|
||||||
|
std.Warning(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs a message at level Error on the standard logger.
|
||||||
|
func Error(args ...interface{}) {
|
||||||
|
std.Error(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic logs a message at level Panic on the standard logger.
|
||||||
|
func Panic(args ...interface{}) {
|
||||||
|
std.Panic(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatal logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatal(args ...interface{}) {
|
||||||
|
std.Fatal(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TraceFn logs a message from a func at level Trace on the standard logger.
|
||||||
|
func TraceFn(fn LogFunction) {
|
||||||
|
std.TraceFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugFn logs a message from a func at level Debug on the standard logger.
|
||||||
|
func DebugFn(fn LogFunction) {
|
||||||
|
std.DebugFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintFn logs a message from a func at level Info on the standard logger.
|
||||||
|
func PrintFn(fn LogFunction) {
|
||||||
|
std.PrintFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InfoFn logs a message from a func at level Info on the standard logger.
|
||||||
|
func InfoFn(fn LogFunction) {
|
||||||
|
std.InfoFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarnFn logs a message from a func at level Warn on the standard logger.
|
||||||
|
func WarnFn(fn LogFunction) {
|
||||||
|
std.WarnFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WarningFn logs a message from a func at level Warn on the standard logger.
|
||||||
|
func WarningFn(fn LogFunction) {
|
||||||
|
std.WarningFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorFn logs a message from a func at level Error on the standard logger.
|
||||||
|
func ErrorFn(fn LogFunction) {
|
||||||
|
std.ErrorFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PanicFn logs a message from a func at level Panic on the standard logger.
|
||||||
|
func PanicFn(fn LogFunction) {
|
||||||
|
std.PanicFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FatalFn logs a message from a func at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func FatalFn(fn LogFunction) {
|
||||||
|
std.FatalFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracef logs a message at level Trace on the standard logger.
|
||||||
|
func Tracef(format string, args ...interface{}) {
|
||||||
|
std.Tracef(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugf logs a message at level Debug on the standard logger.
|
||||||
|
func Debugf(format string, args ...interface{}) {
|
||||||
|
std.Debugf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Printf logs a message at level Info on the standard logger.
|
||||||
|
func Printf(format string, args ...interface{}) {
|
||||||
|
std.Printf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infof logs a message at level Info on the standard logger.
|
||||||
|
func Infof(format string, args ...interface{}) {
|
||||||
|
std.Infof(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnf logs a message at level Warn on the standard logger.
|
||||||
|
func Warnf(format string, args ...interface{}) {
|
||||||
|
std.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningf logs a message at level Warn on the standard logger.
|
||||||
|
func Warningf(format string, args ...interface{}) {
|
||||||
|
std.Warningf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorf logs a message at level Error on the standard logger.
|
||||||
|
func Errorf(format string, args ...interface{}) {
|
||||||
|
std.Errorf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicf logs a message at level Panic on the standard logger.
|
||||||
|
func Panicf(format string, args ...interface{}) {
|
||||||
|
std.Panicf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalf logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatalf(format string, args ...interface{}) {
|
||||||
|
std.Fatalf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traceln logs a message at level Trace on the standard logger.
|
||||||
|
func Traceln(args ...interface{}) {
|
||||||
|
std.Traceln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debugln logs a message at level Debug on the standard logger.
|
||||||
|
func Debugln(args ...interface{}) {
|
||||||
|
std.Debugln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Println logs a message at level Info on the standard logger.
|
||||||
|
func Println(args ...interface{}) {
|
||||||
|
std.Println(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infoln logs a message at level Info on the standard logger.
|
||||||
|
func Infoln(args ...interface{}) {
|
||||||
|
std.Infoln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warnln logs a message at level Warn on the standard logger.
|
||||||
|
func Warnln(args ...interface{}) {
|
||||||
|
std.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warningln logs a message at level Warn on the standard logger.
|
||||||
|
func Warningln(args ...interface{}) {
|
||||||
|
std.Warningln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Errorln logs a message at level Error on the standard logger.
|
||||||
|
func Errorln(args ...interface{}) {
|
||||||
|
std.Errorln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panicln logs a message at level Panic on the standard logger.
|
||||||
|
func Panicln(args ...interface{}) {
|
||||||
|
std.Panicln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fatalln logs a message at level Fatal on the standard logger then the process will exit with status set to 1.
|
||||||
|
func Fatalln(args ...interface{}) {
|
||||||
|
std.Fatalln(args...)
|
||||||
|
}
|
||||||
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
Normal file
78
vendor/github.com/sirupsen/logrus/formatter.go
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Default key names for the default fields
|
||||||
|
const (
|
||||||
|
defaultTimestampFormat = time.RFC3339
|
||||||
|
FieldKeyMsg = "msg"
|
||||||
|
FieldKeyLevel = "level"
|
||||||
|
FieldKeyTime = "time"
|
||||||
|
FieldKeyLogrusError = "logrus_error"
|
||||||
|
FieldKeyFunc = "func"
|
||||||
|
FieldKeyFile = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The Formatter interface is used to implement a custom Formatter. It takes an
|
||||||
|
// `Entry`. It exposes all the fields, including the default ones:
|
||||||
|
//
|
||||||
|
// * `entry.Data["msg"]`. The message passed from Info, Warn, Error ..
|
||||||
|
// * `entry.Data["time"]`. The timestamp.
|
||||||
|
// * `entry.Data["level"]. The level the entry was logged at.
|
||||||
|
//
|
||||||
|
// Any additional fields added with `WithField` or `WithFields` are also in
|
||||||
|
// `entry.Data`. Format is expected to return an array of bytes which are then
|
||||||
|
// logged to `logger.Out`.
|
||||||
|
type Formatter interface {
|
||||||
|
Format(*Entry) ([]byte, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is to not silently overwrite `time`, `msg`, `func` and `level` fields when
|
||||||
|
// dumping it. If this code wasn't there doing:
|
||||||
|
//
|
||||||
|
// logrus.WithField("level", 1).Info("hello")
|
||||||
|
//
|
||||||
|
// Would just silently drop the user provided level. Instead with this code
|
||||||
|
// it'll logged as:
|
||||||
|
//
|
||||||
|
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||||
|
//
|
||||||
|
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||||
|
// avoid code duplication between the two default formatters.
|
||||||
|
func prefixFieldClashes(data Fields, fieldMap FieldMap, reportCaller bool) {
|
||||||
|
timeKey := fieldMap.resolve(FieldKeyTime)
|
||||||
|
if t, ok := data[timeKey]; ok {
|
||||||
|
data["fields."+timeKey] = t
|
||||||
|
delete(data, timeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
msgKey := fieldMap.resolve(FieldKeyMsg)
|
||||||
|
if m, ok := data[msgKey]; ok {
|
||||||
|
data["fields."+msgKey] = m
|
||||||
|
delete(data, msgKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
levelKey := fieldMap.resolve(FieldKeyLevel)
|
||||||
|
if l, ok := data[levelKey]; ok {
|
||||||
|
data["fields."+levelKey] = l
|
||||||
|
delete(data, levelKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrusErrKey := fieldMap.resolve(FieldKeyLogrusError)
|
||||||
|
if l, ok := data[logrusErrKey]; ok {
|
||||||
|
data["fields."+logrusErrKey] = l
|
||||||
|
delete(data, logrusErrKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If reportCaller is not set, 'func' will not conflict.
|
||||||
|
if reportCaller {
|
||||||
|
funcKey := fieldMap.resolve(FieldKeyFunc)
|
||||||
|
if l, ok := data[funcKey]; ok {
|
||||||
|
data["fields."+funcKey] = l
|
||||||
|
}
|
||||||
|
fileKey := fieldMap.resolve(FieldKeyFile)
|
||||||
|
if l, ok := data[fileKey]; ok {
|
||||||
|
data["fields."+fileKey] = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
Normal file
34
vendor/github.com/sirupsen/logrus/hooks.go
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
// A hook to be fired when logging on the logging levels returned from
|
||||||
|
// `Levels()` on your implementation of the interface. Note that this is not
|
||||||
|
// fired in a goroutine or a channel with workers, you should handle such
|
||||||
|
// functionality yourself if your call is non-blocking and you don't wish for
|
||||||
|
// the logging calls for levels returned from `Levels()` to block.
|
||||||
|
type Hook interface {
|
||||||
|
Levels() []Level
|
||||||
|
Fire(*Entry) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal type for storing the hooks on a logger instance.
|
||||||
|
type LevelHooks map[Level][]Hook
|
||||||
|
|
||||||
|
// Add a hook to an instance of logger. This is called with
|
||||||
|
// `log.Hooks.Add(new(MyHook))` where `MyHook` implements the `Hook` interface.
|
||||||
|
func (hooks LevelHooks) Add(hook Hook) {
|
||||||
|
for _, level := range hook.Levels() {
|
||||||
|
hooks[level] = append(hooks[level], hook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fire all the hooks for the passed level. Used by `entry.log` to fire
|
||||||
|
// appropriate hooks for a log entry.
|
||||||
|
func (hooks LevelHooks) Fire(level Level, entry *Entry) error {
|
||||||
|
for _, hook := range hooks[level] {
|
||||||
|
if err := hook.Fire(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
128
vendor/github.com/sirupsen/logrus/json_formatter.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fieldKey string
|
||||||
|
|
||||||
|
// FieldMap allows customization of the key names for default fields.
|
||||||
|
type FieldMap map[fieldKey]string
|
||||||
|
|
||||||
|
func (f FieldMap) resolve(key fieldKey) string {
|
||||||
|
if k, ok := f[key]; ok {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONFormatter formats logs into parsable json
|
||||||
|
type JSONFormatter struct {
|
||||||
|
// TimestampFormat sets the format used for marshaling timestamps.
|
||||||
|
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||||
|
// library.
|
||||||
|
// The standard Library already provides a set of predefined format.
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// DisableTimestamp allows disabling automatic timestamps in output
|
||||||
|
DisableTimestamp bool
|
||||||
|
|
||||||
|
// DisableHTMLEscape allows disabling html escaping in output
|
||||||
|
DisableHTMLEscape bool
|
||||||
|
|
||||||
|
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
|
||||||
|
DataKey string
|
||||||
|
|
||||||
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
|
// As an example:
|
||||||
|
// formatter := &JSONFormatter{
|
||||||
|
// FieldMap: FieldMap{
|
||||||
|
// FieldKeyTime: "@timestamp",
|
||||||
|
// FieldKeyLevel: "@level",
|
||||||
|
// FieldKeyMsg: "@message",
|
||||||
|
// FieldKeyFunc: "@caller",
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
FieldMap FieldMap
|
||||||
|
|
||||||
|
// CallerPrettyfier can be set by the user to modify the content
|
||||||
|
// of the function and file keys in the json data when ReportCaller is
|
||||||
|
// activated. If any of the returned value is the empty string the
|
||||||
|
// corresponding key will be removed from json fields.
|
||||||
|
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||||
|
|
||||||
|
// PrettyPrint will indent all json logs
|
||||||
|
PrettyPrint bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
|
func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
data := make(Fields, len(entry.Data)+4)
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case error:
|
||||||
|
// Otherwise errors are ignored by `encoding/json`
|
||||||
|
// https://github.com/sirupsen/logrus/issues/137
|
||||||
|
data[k] = v.Error()
|
||||||
|
default:
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.DataKey != "" {
|
||||||
|
newData := make(Fields, 4)
|
||||||
|
newData[f.DataKey] = data
|
||||||
|
data = newData
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = defaultTimestampFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
if entry.err != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyLogrusError)] = entry.err
|
||||||
|
}
|
||||||
|
if !f.DisableTimestamp {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyTime)] = entry.Time.Format(timestampFormat)
|
||||||
|
}
|
||||||
|
data[f.FieldMap.resolve(FieldKeyMsg)] = entry.Message
|
||||||
|
data[f.FieldMap.resolve(FieldKeyLevel)] = entry.Level.String()
|
||||||
|
if entry.HasCaller() {
|
||||||
|
funcVal := entry.Caller.Function
|
||||||
|
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||||
|
if f.CallerPrettyfier != nil {
|
||||||
|
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||||
|
}
|
||||||
|
if funcVal != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyFunc)] = funcVal
|
||||||
|
}
|
||||||
|
if fileVal != "" {
|
||||||
|
data[f.FieldMap.resolve(FieldKeyFile)] = fileVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *bytes.Buffer
|
||||||
|
if entry.Buffer != nil {
|
||||||
|
b = entry.Buffer
|
||||||
|
} else {
|
||||||
|
b = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
encoder := json.NewEncoder(b)
|
||||||
|
encoder.SetEscapeHTML(!f.DisableHTMLEscape)
|
||||||
|
if f.PrettyPrint {
|
||||||
|
encoder.SetIndent("", " ")
|
||||||
|
}
|
||||||
|
if err := encoder.Encode(data); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal fields to JSON, %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
417
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
Normal file
417
vendor/github.com/sirupsen/logrus/logger.go
generated
vendored
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LogFunction For big messages, it can be more efficient to pass a function
|
||||||
|
// and only call it if the log level is actually enables rather than
|
||||||
|
// generating the log message and then checking if the level is enabled
|
||||||
|
type LogFunction func() []interface{}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
// The logs are `io.Copy`'d to this in a mutex. It's common to set this to a
|
||||||
|
// file, or leave it default which is `os.Stderr`. You can also set this to
|
||||||
|
// something more adventurous, such as logging to Kafka.
|
||||||
|
Out io.Writer
|
||||||
|
// Hooks for the logger instance. These allow firing events based on logging
|
||||||
|
// levels and log entries. For example, to send errors to an error tracking
|
||||||
|
// service, log to StatsD or dump the core on fatal errors.
|
||||||
|
Hooks LevelHooks
|
||||||
|
// All log entries pass through the formatter before logged to Out. The
|
||||||
|
// included formatters are `TextFormatter` and `JSONFormatter` for which
|
||||||
|
// TextFormatter is the default. In development (when a TTY is attached) it
|
||||||
|
// logs with colors, but to a file it wouldn't. You can easily implement your
|
||||||
|
// own that implements the `Formatter` interface, see the `README` or included
|
||||||
|
// formatters for examples.
|
||||||
|
Formatter Formatter
|
||||||
|
|
||||||
|
// Flag for whether to log caller info (off by default)
|
||||||
|
ReportCaller bool
|
||||||
|
|
||||||
|
// The logging level the logger should log at. This is typically (and defaults
|
||||||
|
// to) `logrus.Info`, which allows Info(), Warn(), Error() and Fatal() to be
|
||||||
|
// logged.
|
||||||
|
Level Level
|
||||||
|
// Used to sync writing to the log. Locking is enabled by Default
|
||||||
|
mu MutexWrap
|
||||||
|
// Reusable empty entry
|
||||||
|
entryPool sync.Pool
|
||||||
|
// Function to exit the application, defaults to `os.Exit()`
|
||||||
|
ExitFunc exitFunc
|
||||||
|
// The buffer pool used to format the log. If it is nil, the default global
|
||||||
|
// buffer pool will be used.
|
||||||
|
BufferPool BufferPool
|
||||||
|
}
|
||||||
|
|
||||||
|
type exitFunc func(int)
|
||||||
|
|
||||||
|
type MutexWrap struct {
|
||||||
|
lock sync.Mutex
|
||||||
|
disabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Lock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Lock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Unlock() {
|
||||||
|
if !mw.disabled {
|
||||||
|
mw.lock.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mw *MutexWrap) Disable() {
|
||||||
|
mw.disabled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new logger. Configuration should be set by changing `Formatter`,
|
||||||
|
// `Out` and `Hooks` directly on the default logger instance. You can also just
|
||||||
|
// instantiate your own:
|
||||||
|
//
|
||||||
|
// var log = &logrus.Logger{
|
||||||
|
// Out: os.Stderr,
|
||||||
|
// Formatter: new(logrus.TextFormatter),
|
||||||
|
// Hooks: make(logrus.LevelHooks),
|
||||||
|
// Level: logrus.DebugLevel,
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// It's recommended to make this a global instance called `log`.
|
||||||
|
func New() *Logger {
|
||||||
|
return &Logger{
|
||||||
|
Out: os.Stderr,
|
||||||
|
Formatter: new(TextFormatter),
|
||||||
|
Hooks: make(LevelHooks),
|
||||||
|
Level: InfoLevel,
|
||||||
|
ExitFunc: os.Exit,
|
||||||
|
ReportCaller: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) newEntry() *Entry {
|
||||||
|
entry, ok := logger.entryPool.Get().(*Entry)
|
||||||
|
if ok {
|
||||||
|
return entry
|
||||||
|
}
|
||||||
|
return NewEntry(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) releaseEntry(entry *Entry) {
|
||||||
|
entry.Data = map[string]interface{}{}
|
||||||
|
logger.entryPool.Put(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithField allocates a new entry and adds a field to it.
|
||||||
|
// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
|
||||||
|
// this new returned entry.
|
||||||
|
// If you want multiple fields, use `WithFields`.
|
||||||
|
func (logger *Logger) WithField(key string, value interface{}) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithField(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a struct of fields to the log entry. All it does is call `WithField` for
|
||||||
|
// each `Field`.
|
||||||
|
func (logger *Logger) WithFields(fields Fields) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithFields(fields)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error as single field to the log entry. All it does is call
|
||||||
|
// `WithError` for the given `error`.
|
||||||
|
func (logger *Logger) WithError(err error) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a context to the log entry.
|
||||||
|
func (logger *Logger) WithContext(ctx context.Context) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides the time of the log entry.
|
||||||
|
func (logger *Logger) WithTime(t time.Time) *Entry {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
defer logger.releaseEntry(entry)
|
||||||
|
return entry.WithTime(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Logf(level Level, format string, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Logf(level, format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Tracef(format string, args ...interface{}) {
|
||||||
|
logger.Logf(TraceLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(DebugLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infof(format string, args ...interface{}) {
|
||||||
|
logger.Logf(InfoLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Printf(format string, args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Printf(format, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(WarnLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningf(format string, args ...interface{}) {
|
||||||
|
logger.Warnf(format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(ErrorLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(FatalLevel, format, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicf(format string, args ...interface{}) {
|
||||||
|
logger.Logf(PanicLevel, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log will log a message at the level given as parameter.
|
||||||
|
// Warning: using Log at Panic or Fatal level will not respectively Panic nor Exit.
|
||||||
|
// For this behaviour Logger.Panic or Logger.Fatal should be used instead.
|
||||||
|
func (logger *Logger) Log(level Level, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Log(level, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) LogFn(level Level, fn LogFunction) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Log(level, fn()...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Trace(args ...interface{}) {
|
||||||
|
logger.Log(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debug(args ...interface{}) {
|
||||||
|
logger.Log(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Info(args ...interface{}) {
|
||||||
|
logger.Log(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Print(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Print(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warn(args ...interface{}) {
|
||||||
|
logger.Log(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warning(args ...interface{}) {
|
||||||
|
logger.Warn(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Error(args ...interface{}) {
|
||||||
|
logger.Log(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatal(args ...interface{}) {
|
||||||
|
logger.Log(FatalLevel, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panic(args ...interface{}) {
|
||||||
|
logger.Log(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) TraceFn(fn LogFunction) {
|
||||||
|
logger.LogFn(TraceLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) DebugFn(fn LogFunction) {
|
||||||
|
logger.LogFn(DebugLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) InfoFn(fn LogFunction) {
|
||||||
|
logger.LogFn(InfoLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) PrintFn(fn LogFunction) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Print(fn()...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) WarnFn(fn LogFunction) {
|
||||||
|
logger.LogFn(WarnLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) WarningFn(fn LogFunction) {
|
||||||
|
logger.WarnFn(fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) ErrorFn(fn LogFunction) {
|
||||||
|
logger.LogFn(ErrorLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) FatalFn(fn LogFunction) {
|
||||||
|
logger.LogFn(FatalLevel, fn)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) PanicFn(fn LogFunction) {
|
||||||
|
logger.LogFn(PanicLevel, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Logln(level Level, args ...interface{}) {
|
||||||
|
if logger.IsLevelEnabled(level) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Logln(level, args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Traceln(args ...interface{}) {
|
||||||
|
logger.Logln(TraceLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Debugln(args ...interface{}) {
|
||||||
|
logger.Logln(DebugLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Infoln(args ...interface{}) {
|
||||||
|
logger.Logln(InfoLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Println(args ...interface{}) {
|
||||||
|
entry := logger.newEntry()
|
||||||
|
entry.Println(args...)
|
||||||
|
logger.releaseEntry(entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warnln(args ...interface{}) {
|
||||||
|
logger.Logln(WarnLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Warningln(args ...interface{}) {
|
||||||
|
logger.Warnln(args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Errorln(args ...interface{}) {
|
||||||
|
logger.Logln(ErrorLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Fatalln(args ...interface{}) {
|
||||||
|
logger.Logln(FatalLevel, args...)
|
||||||
|
logger.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Panicln(args ...interface{}) {
|
||||||
|
logger.Logln(PanicLevel, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) Exit(code int) {
|
||||||
|
runHandlers()
|
||||||
|
if logger.ExitFunc == nil {
|
||||||
|
logger.ExitFunc = os.Exit
|
||||||
|
}
|
||||||
|
logger.ExitFunc(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
//When file is opened with appending mode, it's safe to
|
||||||
|
//write concurrently to a file (within 4k message on Linux).
|
||||||
|
//In these cases user can choose to disable the lock.
|
||||||
|
func (logger *Logger) SetNoLock() {
|
||||||
|
logger.mu.Disable()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) level() Level {
|
||||||
|
return Level(atomic.LoadUint32((*uint32)(&logger.Level)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLevel sets the logger level.
|
||||||
|
func (logger *Logger) SetLevel(level Level) {
|
||||||
|
atomic.StoreUint32((*uint32)(&logger.Level), uint32(level))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLevel returns the logger level.
|
||||||
|
func (logger *Logger) GetLevel() Level {
|
||||||
|
return logger.level()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHook adds a hook to the logger hooks.
|
||||||
|
func (logger *Logger) AddHook(hook Hook) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsLevelEnabled checks if the log level of the logger is greater than the level param
|
||||||
|
func (logger *Logger) IsLevelEnabled(level Level) bool {
|
||||||
|
return logger.level() >= level
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetFormatter sets the logger formatter.
|
||||||
|
func (logger *Logger) SetFormatter(formatter Formatter) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Formatter = formatter
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOutput sets the logger output.
|
||||||
|
func (logger *Logger) SetOutput(output io.Writer) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.Out = output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (logger *Logger) SetReportCaller(reportCaller bool) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.ReportCaller = reportCaller
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReplaceHooks replaces the logger hooks and returns the old ones
|
||||||
|
func (logger *Logger) ReplaceHooks(hooks LevelHooks) LevelHooks {
|
||||||
|
logger.mu.Lock()
|
||||||
|
oldHooks := logger.Hooks
|
||||||
|
logger.Hooks = hooks
|
||||||
|
logger.mu.Unlock()
|
||||||
|
return oldHooks
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBufferPool sets the logger buffer pool.
|
||||||
|
func (logger *Logger) SetBufferPool(pool BufferPool) {
|
||||||
|
logger.mu.Lock()
|
||||||
|
defer logger.mu.Unlock()
|
||||||
|
logger.BufferPool = pool
|
||||||
|
}
|
||||||
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
Normal file
186
vendor/github.com/sirupsen/logrus/logrus.go
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fields type, used to pass to `WithFields`.
|
||||||
|
type Fields map[string]interface{}
|
||||||
|
|
||||||
|
// Level type
|
||||||
|
type Level uint32
|
||||||
|
|
||||||
|
// Convert the Level to a string. E.g. PanicLevel becomes "panic".
|
||||||
|
func (level Level) String() string {
|
||||||
|
if b, err := level.MarshalText(); err == nil {
|
||||||
|
return string(b)
|
||||||
|
} else {
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLevel takes a string level and returns the Logrus log level constant.
|
||||||
|
func ParseLevel(lvl string) (Level, error) {
|
||||||
|
switch strings.ToLower(lvl) {
|
||||||
|
case "panic":
|
||||||
|
return PanicLevel, nil
|
||||||
|
case "fatal":
|
||||||
|
return FatalLevel, nil
|
||||||
|
case "error":
|
||||||
|
return ErrorLevel, nil
|
||||||
|
case "warn", "warning":
|
||||||
|
return WarnLevel, nil
|
||||||
|
case "info":
|
||||||
|
return InfoLevel, nil
|
||||||
|
case "debug":
|
||||||
|
return DebugLevel, nil
|
||||||
|
case "trace":
|
||||||
|
return TraceLevel, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var l Level
|
||||||
|
return l, fmt.Errorf("not a valid logrus Level: %q", lvl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText implements encoding.TextUnmarshaler.
|
||||||
|
func (level *Level) UnmarshalText(text []byte) error {
|
||||||
|
l, err := ParseLevel(string(text))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*level = l
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (level Level) MarshalText() ([]byte, error) {
|
||||||
|
switch level {
|
||||||
|
case TraceLevel:
|
||||||
|
return []byte("trace"), nil
|
||||||
|
case DebugLevel:
|
||||||
|
return []byte("debug"), nil
|
||||||
|
case InfoLevel:
|
||||||
|
return []byte("info"), nil
|
||||||
|
case WarnLevel:
|
||||||
|
return []byte("warning"), nil
|
||||||
|
case ErrorLevel:
|
||||||
|
return []byte("error"), nil
|
||||||
|
case FatalLevel:
|
||||||
|
return []byte("fatal"), nil
|
||||||
|
case PanicLevel:
|
||||||
|
return []byte("panic"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("not a valid logrus level %d", level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A constant exposing all logging levels
|
||||||
|
var AllLevels = []Level{
|
||||||
|
PanicLevel,
|
||||||
|
FatalLevel,
|
||||||
|
ErrorLevel,
|
||||||
|
WarnLevel,
|
||||||
|
InfoLevel,
|
||||||
|
DebugLevel,
|
||||||
|
TraceLevel,
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are the different logging levels. You can set the logging level to log
|
||||||
|
// on your instance of logger, obtained with `logrus.New()`.
|
||||||
|
const (
|
||||||
|
// PanicLevel level, highest level of severity. Logs and then calls panic with the
|
||||||
|
// message passed to Debug, Info, ...
|
||||||
|
PanicLevel Level = iota
|
||||||
|
// FatalLevel level. Logs and then calls `logger.Exit(1)`. It will exit even if the
|
||||||
|
// logging level is set to Panic.
|
||||||
|
FatalLevel
|
||||||
|
// ErrorLevel level. Logs. Used for errors that should definitely be noted.
|
||||||
|
// Commonly used for hooks to send errors to an error tracking service.
|
||||||
|
ErrorLevel
|
||||||
|
// WarnLevel level. Non-critical entries that deserve eyes.
|
||||||
|
WarnLevel
|
||||||
|
// InfoLevel level. General operational entries about what's going on inside the
|
||||||
|
// application.
|
||||||
|
InfoLevel
|
||||||
|
// DebugLevel level. Usually only enabled when debugging. Very verbose logging.
|
||||||
|
DebugLevel
|
||||||
|
// TraceLevel level. Designates finer-grained informational events than the Debug.
|
||||||
|
TraceLevel
|
||||||
|
)
|
||||||
|
|
||||||
|
// Won't compile if StdLogger can't be realized by a log.Logger
|
||||||
|
var (
|
||||||
|
_ StdLogger = &log.Logger{}
|
||||||
|
_ StdLogger = &Entry{}
|
||||||
|
_ StdLogger = &Logger{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// StdLogger is what your logrus-enabled library should take, that way
|
||||||
|
// it'll accept a stdlib logger and a logrus logger. There's no standard
|
||||||
|
// interface, this is the closest we get, unfortunately.
|
||||||
|
type StdLogger interface {
|
||||||
|
Print(...interface{})
|
||||||
|
Printf(string, ...interface{})
|
||||||
|
Println(...interface{})
|
||||||
|
|
||||||
|
Fatal(...interface{})
|
||||||
|
Fatalf(string, ...interface{})
|
||||||
|
Fatalln(...interface{})
|
||||||
|
|
||||||
|
Panic(...interface{})
|
||||||
|
Panicf(string, ...interface{})
|
||||||
|
Panicln(...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FieldLogger interface generalizes the Entry and Logger types
|
||||||
|
type FieldLogger interface {
|
||||||
|
WithField(key string, value interface{}) *Entry
|
||||||
|
WithFields(fields Fields) *Entry
|
||||||
|
WithError(err error) *Entry
|
||||||
|
|
||||||
|
Debugf(format string, args ...interface{})
|
||||||
|
Infof(format string, args ...interface{})
|
||||||
|
Printf(format string, args ...interface{})
|
||||||
|
Warnf(format string, args ...interface{})
|
||||||
|
Warningf(format string, args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
Panicf(format string, args ...interface{})
|
||||||
|
|
||||||
|
Debug(args ...interface{})
|
||||||
|
Info(args ...interface{})
|
||||||
|
Print(args ...interface{})
|
||||||
|
Warn(args ...interface{})
|
||||||
|
Warning(args ...interface{})
|
||||||
|
Error(args ...interface{})
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Panic(args ...interface{})
|
||||||
|
|
||||||
|
Debugln(args ...interface{})
|
||||||
|
Infoln(args ...interface{})
|
||||||
|
Println(args ...interface{})
|
||||||
|
Warnln(args ...interface{})
|
||||||
|
Warningln(args ...interface{})
|
||||||
|
Errorln(args ...interface{})
|
||||||
|
Fatalln(args ...interface{})
|
||||||
|
Panicln(args ...interface{})
|
||||||
|
|
||||||
|
// IsDebugEnabled() bool
|
||||||
|
// IsInfoEnabled() bool
|
||||||
|
// IsWarnEnabled() bool
|
||||||
|
// IsErrorEnabled() bool
|
||||||
|
// IsFatalEnabled() bool
|
||||||
|
// IsPanicEnabled() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ext1FieldLogger (the first extension to FieldLogger) is superfluous, it is
|
||||||
|
// here for consistancy. Do not use. Use Logger or Entry instead.
|
||||||
|
type Ext1FieldLogger interface {
|
||||||
|
FieldLogger
|
||||||
|
Tracef(format string, args ...interface{})
|
||||||
|
Trace(args ...interface{})
|
||||||
|
Traceln(args ...interface{})
|
||||||
|
}
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
11
vendor/github.com/sirupsen/logrus/terminal_check_appengine.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
13
vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TIOCGETA
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
Normal file
7
vendor/github.com/sirupsen/logrus/terminal_check_js.go
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// +build js
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
Normal file
11
vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// +build js nacl plan9
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
Normal file
17
vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// +build !appengine,!js,!windows,!nacl,!plan9
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
|
switch v := w.(type) {
|
||||||
|
case *os.File:
|
||||||
|
return isTerminal(int(v.Fd()))
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
Normal file
11
vendor/github.com/sirupsen/logrus/terminal_check_solaris.go
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermio(fd, unix.TCGETA)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
Normal file
13
vendor/github.com/sirupsen/logrus/terminal_check_unix.go
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// +build linux aix zos
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import "golang.org/x/sys/unix"
|
||||||
|
|
||||||
|
const ioctlReadTermios = unix.TCGETS
|
||||||
|
|
||||||
|
func isTerminal(fd int) bool {
|
||||||
|
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
Normal file
27
vendor/github.com/sirupsen/logrus/terminal_check_windows.go
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// +build !appengine,!js,windows
|
||||||
|
|
||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkIfTerminal(w io.Writer) bool {
|
||||||
|
switch v := w.(type) {
|
||||||
|
case *os.File:
|
||||||
|
handle := windows.Handle(v.Fd())
|
||||||
|
var mode uint32
|
||||||
|
if err := windows.GetConsoleMode(handle, &mode); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING
|
||||||
|
if err := windows.SetConsoleMode(handle, mode); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
339
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
339
vendor/github.com/sirupsen/logrus/text_formatter.go
generated
vendored
Normal file
@@ -0,0 +1,339 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
red = 31
|
||||||
|
yellow = 33
|
||||||
|
blue = 36
|
||||||
|
gray = 37
|
||||||
|
)
|
||||||
|
|
||||||
|
var baseTimestamp time.Time
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
baseTimestamp = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextFormatter formats logs into text
|
||||||
|
type TextFormatter struct {
|
||||||
|
// Set to true to bypass checking for a TTY before outputting colors.
|
||||||
|
ForceColors bool
|
||||||
|
|
||||||
|
// Force disabling colors.
|
||||||
|
DisableColors bool
|
||||||
|
|
||||||
|
// Force quoting of all values
|
||||||
|
ForceQuote bool
|
||||||
|
|
||||||
|
// DisableQuote disables quoting for all values.
|
||||||
|
// DisableQuote will have a lower priority than ForceQuote.
|
||||||
|
// If both of them are set to true, quote will be forced on all values.
|
||||||
|
DisableQuote bool
|
||||||
|
|
||||||
|
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
|
||||||
|
EnvironmentOverrideColors bool
|
||||||
|
|
||||||
|
// Disable timestamp logging. useful when output is redirected to logging
|
||||||
|
// system that already adds timestamps.
|
||||||
|
DisableTimestamp bool
|
||||||
|
|
||||||
|
// Enable logging the full timestamp when a TTY is attached instead of just
|
||||||
|
// the time passed since beginning of execution.
|
||||||
|
FullTimestamp bool
|
||||||
|
|
||||||
|
// TimestampFormat to use for display when a full timestamp is printed.
|
||||||
|
// The format to use is the same than for time.Format or time.Parse from the standard
|
||||||
|
// library.
|
||||||
|
// The standard Library already provides a set of predefined format.
|
||||||
|
TimestampFormat string
|
||||||
|
|
||||||
|
// The fields are sorted by default for a consistent output. For applications
|
||||||
|
// that log extremely frequently and don't use the JSON formatter this may not
|
||||||
|
// be desired.
|
||||||
|
DisableSorting bool
|
||||||
|
|
||||||
|
// The keys sorting function, when uninitialized it uses sort.Strings.
|
||||||
|
SortingFunc func([]string)
|
||||||
|
|
||||||
|
// Disables the truncation of the level text to 4 characters.
|
||||||
|
DisableLevelTruncation bool
|
||||||
|
|
||||||
|
// PadLevelText Adds padding the level text so that all the levels output at the same length
|
||||||
|
// PadLevelText is a superset of the DisableLevelTruncation option
|
||||||
|
PadLevelText bool
|
||||||
|
|
||||||
|
// QuoteEmptyFields will wrap empty fields in quotes if true
|
||||||
|
QuoteEmptyFields bool
|
||||||
|
|
||||||
|
// Whether the logger's out is to a terminal
|
||||||
|
isTerminal bool
|
||||||
|
|
||||||
|
// FieldMap allows users to customize the names of keys for default fields.
|
||||||
|
// As an example:
|
||||||
|
// formatter := &TextFormatter{
|
||||||
|
// FieldMap: FieldMap{
|
||||||
|
// FieldKeyTime: "@timestamp",
|
||||||
|
// FieldKeyLevel: "@level",
|
||||||
|
// FieldKeyMsg: "@message"}}
|
||||||
|
FieldMap FieldMap
|
||||||
|
|
||||||
|
// CallerPrettyfier can be set by the user to modify the content
|
||||||
|
// of the function and file keys in the data when ReportCaller is
|
||||||
|
// activated. If any of the returned value is the empty string the
|
||||||
|
// corresponding key will be removed from fields.
|
||||||
|
CallerPrettyfier func(*runtime.Frame) (function string, file string)
|
||||||
|
|
||||||
|
terminalInitOnce sync.Once
|
||||||
|
|
||||||
|
// The max length of the level text, generated dynamically on init
|
||||||
|
levelTextMaxLength int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) init(entry *Entry) {
|
||||||
|
if entry.Logger != nil {
|
||||||
|
f.isTerminal = checkIfTerminal(entry.Logger.Out)
|
||||||
|
}
|
||||||
|
// Get the max length of the level text
|
||||||
|
for _, level := range AllLevels {
|
||||||
|
levelTextLength := utf8.RuneCount([]byte(level.String()))
|
||||||
|
if levelTextLength > f.levelTextMaxLength {
|
||||||
|
f.levelTextMaxLength = levelTextLength
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) isColored() bool {
|
||||||
|
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
|
||||||
|
|
||||||
|
if f.EnvironmentOverrideColors {
|
||||||
|
switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
|
||||||
|
case ok && force != "0":
|
||||||
|
isColored = true
|
||||||
|
case ok && force == "0", os.Getenv("CLICOLOR") == "0":
|
||||||
|
isColored = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isColored && !f.DisableColors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format renders a single log entry
|
||||||
|
func (f *TextFormatter) Format(entry *Entry) ([]byte, error) {
|
||||||
|
data := make(Fields)
|
||||||
|
for k, v := range entry.Data {
|
||||||
|
data[k] = v
|
||||||
|
}
|
||||||
|
prefixFieldClashes(data, f.FieldMap, entry.HasCaller())
|
||||||
|
keys := make([]string, 0, len(data))
|
||||||
|
for k := range data {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcVal, fileVal string
|
||||||
|
|
||||||
|
fixedKeys := make([]string, 0, 4+len(data))
|
||||||
|
if !f.DisableTimestamp {
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyTime))
|
||||||
|
}
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLevel))
|
||||||
|
if entry.Message != "" {
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyMsg))
|
||||||
|
}
|
||||||
|
if entry.err != "" {
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyLogrusError))
|
||||||
|
}
|
||||||
|
if entry.HasCaller() {
|
||||||
|
if f.CallerPrettyfier != nil {
|
||||||
|
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||||
|
} else {
|
||||||
|
funcVal = entry.Caller.Function
|
||||||
|
fileVal = fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if funcVal != "" {
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFunc))
|
||||||
|
}
|
||||||
|
if fileVal != "" {
|
||||||
|
fixedKeys = append(fixedKeys, f.FieldMap.resolve(FieldKeyFile))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.DisableSorting {
|
||||||
|
if f.SortingFunc == nil {
|
||||||
|
sort.Strings(keys)
|
||||||
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
|
} else {
|
||||||
|
if !f.isColored() {
|
||||||
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
|
f.SortingFunc(fixedKeys)
|
||||||
|
} else {
|
||||||
|
f.SortingFunc(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fixedKeys = append(fixedKeys, keys...)
|
||||||
|
}
|
||||||
|
|
||||||
|
var b *bytes.Buffer
|
||||||
|
if entry.Buffer != nil {
|
||||||
|
b = entry.Buffer
|
||||||
|
} else {
|
||||||
|
b = &bytes.Buffer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
f.terminalInitOnce.Do(func() { f.init(entry) })
|
||||||
|
|
||||||
|
timestampFormat := f.TimestampFormat
|
||||||
|
if timestampFormat == "" {
|
||||||
|
timestampFormat = defaultTimestampFormat
|
||||||
|
}
|
||||||
|
if f.isColored() {
|
||||||
|
f.printColored(b, entry, keys, data, timestampFormat)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
for _, key := range fixedKeys {
|
||||||
|
var value interface{}
|
||||||
|
switch {
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyTime):
|
||||||
|
value = entry.Time.Format(timestampFormat)
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyLevel):
|
||||||
|
value = entry.Level.String()
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyMsg):
|
||||||
|
value = entry.Message
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyLogrusError):
|
||||||
|
value = entry.err
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyFunc) && entry.HasCaller():
|
||||||
|
value = funcVal
|
||||||
|
case key == f.FieldMap.resolve(FieldKeyFile) && entry.HasCaller():
|
||||||
|
value = fileVal
|
||||||
|
default:
|
||||||
|
value = data[key]
|
||||||
|
}
|
||||||
|
f.appendKeyValue(b, key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteByte('\n')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []string, data Fields, timestampFormat string) {
|
||||||
|
var levelColor int
|
||||||
|
switch entry.Level {
|
||||||
|
case DebugLevel, TraceLevel:
|
||||||
|
levelColor = gray
|
||||||
|
case WarnLevel:
|
||||||
|
levelColor = yellow
|
||||||
|
case ErrorLevel, FatalLevel, PanicLevel:
|
||||||
|
levelColor = red
|
||||||
|
case InfoLevel:
|
||||||
|
levelColor = blue
|
||||||
|
default:
|
||||||
|
levelColor = blue
|
||||||
|
}
|
||||||
|
|
||||||
|
levelText := strings.ToUpper(entry.Level.String())
|
||||||
|
if !f.DisableLevelTruncation && !f.PadLevelText {
|
||||||
|
levelText = levelText[0:4]
|
||||||
|
}
|
||||||
|
if f.PadLevelText {
|
||||||
|
// Generates the format string used in the next line, for example "%-6s" or "%-7s".
|
||||||
|
// Based on the max level text length.
|
||||||
|
formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
|
||||||
|
// Formats the level text by appending spaces up to the max length, for example:
|
||||||
|
// - "INFO "
|
||||||
|
// - "WARNING"
|
||||||
|
levelText = fmt.Sprintf(formatString, levelText)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a single newline if it already exists in the message to keep
|
||||||
|
// the behavior of logrus text_formatter the same as the stdlib log package
|
||||||
|
entry.Message = strings.TrimSuffix(entry.Message, "\n")
|
||||||
|
|
||||||
|
caller := ""
|
||||||
|
if entry.HasCaller() {
|
||||||
|
funcVal := fmt.Sprintf("%s()", entry.Caller.Function)
|
||||||
|
fileVal := fmt.Sprintf("%s:%d", entry.Caller.File, entry.Caller.Line)
|
||||||
|
|
||||||
|
if f.CallerPrettyfier != nil {
|
||||||
|
funcVal, fileVal = f.CallerPrettyfier(entry.Caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
if fileVal == "" {
|
||||||
|
caller = funcVal
|
||||||
|
} else if funcVal == "" {
|
||||||
|
caller = fileVal
|
||||||
|
} else {
|
||||||
|
caller = fileVal + " " + funcVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case f.DisableTimestamp:
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
|
||||||
|
case !f.FullTimestamp:
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
v := data[k]
|
||||||
|
fmt.Fprintf(b, " \x1b[%dm%s\x1b[0m=", levelColor, k)
|
||||||
|
f.appendValue(b, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) needsQuoting(text string) bool {
|
||||||
|
if f.ForceQuote {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if f.QuoteEmptyFields && len(text) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if f.DisableQuote {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, ch := range text {
|
||||||
|
if !((ch >= 'a' && ch <= 'z') ||
|
||||||
|
(ch >= 'A' && ch <= 'Z') ||
|
||||||
|
(ch >= '0' && ch <= '9') ||
|
||||||
|
ch == '-' || ch == '.' || ch == '_' || ch == '/' || ch == '@' || ch == '^' || ch == '+') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendKeyValue(b *bytes.Buffer, key string, value interface{}) {
|
||||||
|
if b.Len() > 0 {
|
||||||
|
b.WriteByte(' ')
|
||||||
|
}
|
||||||
|
b.WriteString(key)
|
||||||
|
b.WriteByte('=')
|
||||||
|
f.appendValue(b, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TextFormatter) appendValue(b *bytes.Buffer, value interface{}) {
|
||||||
|
stringVal, ok := value.(string)
|
||||||
|
if !ok {
|
||||||
|
stringVal = fmt.Sprint(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !f.needsQuoting(stringVal) {
|
||||||
|
b.WriteString(stringVal)
|
||||||
|
} else {
|
||||||
|
b.WriteString(fmt.Sprintf("%q", stringVal))
|
||||||
|
}
|
||||||
|
}
|
||||||
102
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
Normal file
102
vendor/github.com/sirupsen/logrus/writer.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package logrus
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Writer at INFO level. See WriterLevel for details.
|
||||||
|
func (logger *Logger) Writer() *io.PipeWriter {
|
||||||
|
return logger.WriterLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriterLevel returns an io.Writer that can be used to write arbitrary text to
|
||||||
|
// the logger at the given log level. Each line written to the writer will be
|
||||||
|
// printed in the usual way using formatters and hooks. The writer is part of an
|
||||||
|
// io.Pipe and it is the callers responsibility to close the writer when done.
|
||||||
|
// This can be used to override the standard library logger easily.
|
||||||
|
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
|
||||||
|
return NewEntry(logger).WriterLevel(level)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Writer returns an io.Writer that writes to the logger at the info log level
|
||||||
|
func (entry *Entry) Writer() *io.PipeWriter {
|
||||||
|
return entry.WriterLevel(InfoLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriterLevel returns an io.Writer that writes to the logger at the given log level
|
||||||
|
func (entry *Entry) WriterLevel(level Level) *io.PipeWriter {
|
||||||
|
reader, writer := io.Pipe()
|
||||||
|
|
||||||
|
var printFunc func(args ...interface{})
|
||||||
|
|
||||||
|
// Determine which log function to use based on the specified log level
|
||||||
|
switch level {
|
||||||
|
case TraceLevel:
|
||||||
|
printFunc = entry.Trace
|
||||||
|
case DebugLevel:
|
||||||
|
printFunc = entry.Debug
|
||||||
|
case InfoLevel:
|
||||||
|
printFunc = entry.Info
|
||||||
|
case WarnLevel:
|
||||||
|
printFunc = entry.Warn
|
||||||
|
case ErrorLevel:
|
||||||
|
printFunc = entry.Error
|
||||||
|
case FatalLevel:
|
||||||
|
printFunc = entry.Fatal
|
||||||
|
case PanicLevel:
|
||||||
|
printFunc = entry.Panic
|
||||||
|
default:
|
||||||
|
printFunc = entry.Print
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a new goroutine to scan the input and write it to the logger using the specified print function.
|
||||||
|
// It splits the input into chunks of up to 64KB to avoid buffer overflows.
|
||||||
|
go entry.writerScanner(reader, printFunc)
|
||||||
|
|
||||||
|
// Set a finalizer function to close the writer when it is garbage collected
|
||||||
|
runtime.SetFinalizer(writer, writerFinalizer)
|
||||||
|
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// writerScanner scans the input from the reader and writes it to the logger
|
||||||
|
func (entry *Entry) writerScanner(reader *io.PipeReader, printFunc func(args ...interface{})) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
|
||||||
|
// Set the buffer size to the maximum token size to avoid buffer overflows
|
||||||
|
scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), bufio.MaxScanTokenSize)
|
||||||
|
|
||||||
|
// Define a split function to split the input into chunks of up to 64KB
|
||||||
|
chunkSize := bufio.MaxScanTokenSize // 64KB
|
||||||
|
splitFunc := func(data []byte, atEOF bool) (int, []byte, error) {
|
||||||
|
if len(data) >= chunkSize {
|
||||||
|
return chunkSize, data[:chunkSize], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bufio.ScanLines(data, atEOF)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the custom split function to split the input
|
||||||
|
scanner.Split(splitFunc)
|
||||||
|
|
||||||
|
// Scan the input and write it to the logger using the specified print function
|
||||||
|
for scanner.Scan() {
|
||||||
|
printFunc(strings.TrimRight(scanner.Text(), "\r\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there was an error while scanning the input, log an error
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
entry.Errorf("Error while reading from Writer: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the reader when we are done
|
||||||
|
reader.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriterFinalizer is a finalizer function that closes then given writer when it is garbage collected
|
||||||
|
func writerFinalizer(writer *io.PipeWriter) {
|
||||||
|
writer.Close()
|
||||||
|
}
|
||||||
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/crypto/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/crypto/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
77
vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package pbkdf2 implements the key derivation function PBKDF2 as defined in RFC
|
||||||
|
2898 / PKCS #5 v2.0.
|
||||||
|
|
||||||
|
A key derivation function is useful when encrypting data based on a password
|
||||||
|
or any other not-fully-random data. It uses a pseudorandom function to derive
|
||||||
|
a secure encryption key based on the password.
|
||||||
|
|
||||||
|
While v2.0 of the standard defines only one pseudorandom function to use,
|
||||||
|
HMAC-SHA1, the drafted v2.1 specification allows use of all five FIPS Approved
|
||||||
|
Hash Functions SHA-1, SHA-224, SHA-256, SHA-384 and SHA-512 for HMAC. To
|
||||||
|
choose, you can pass the `New` functions from the different SHA packages to
|
||||||
|
pbkdf2.Key.
|
||||||
|
*/
|
||||||
|
package pbkdf2 // import "golang.org/x/crypto/pbkdf2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"hash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key derives a key from the password, salt and iteration count, returning a
|
||||||
|
// []byte of length keylen that can be used as cryptographic key. The key is
|
||||||
|
// derived based on the method described as PBKDF2 with the HMAC variant using
|
||||||
|
// the supplied hash function.
|
||||||
|
//
|
||||||
|
// For example, to use a HMAC-SHA-1 based PBKDF2 key derivation function, you
|
||||||
|
// can get a derived key for e.g. AES-256 (which needs a 32-byte key) by
|
||||||
|
// doing:
|
||||||
|
//
|
||||||
|
// dk := pbkdf2.Key([]byte("some password"), salt, 4096, 32, sha1.New)
|
||||||
|
//
|
||||||
|
// Remember to get a good random salt. At least 8 bytes is recommended by the
|
||||||
|
// RFC.
|
||||||
|
//
|
||||||
|
// Using a higher iteration count will increase the cost of an exhaustive
|
||||||
|
// search but will also make derivation proportionally slower.
|
||||||
|
func Key(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||||
|
prf := hmac.New(h, password)
|
||||||
|
hashLen := prf.Size()
|
||||||
|
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||||
|
|
||||||
|
var buf [4]byte
|
||||||
|
dk := make([]byte, 0, numBlocks*hashLen)
|
||||||
|
U := make([]byte, hashLen)
|
||||||
|
for block := 1; block <= numBlocks; block++ {
|
||||||
|
// N.B.: || means concatenation, ^ means XOR
|
||||||
|
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||||
|
// U_1 = PRF(password, salt || uint(i))
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(salt)
|
||||||
|
buf[0] = byte(block >> 24)
|
||||||
|
buf[1] = byte(block >> 16)
|
||||||
|
buf[2] = byte(block >> 8)
|
||||||
|
buf[3] = byte(block)
|
||||||
|
prf.Write(buf[:4])
|
||||||
|
dk = prf.Sum(dk)
|
||||||
|
T := dk[len(dk)-hashLen:]
|
||||||
|
copy(U, T)
|
||||||
|
|
||||||
|
// U_n = PRF(password, U_(n-1))
|
||||||
|
for n := 2; n <= iter; n++ {
|
||||||
|
prf.Reset()
|
||||||
|
prf.Write(U)
|
||||||
|
U = U[:0]
|
||||||
|
U = prf.Sum(U)
|
||||||
|
for x := range U {
|
||||||
|
T[x] ^= U[x]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dk[:keyLen]
|
||||||
|
}
|
||||||
27
vendor/golang.org/x/exp/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/exp/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/exp/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/exp/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
50
vendor/golang.org/x/exp/constraints/constraints.go
generated
vendored
Normal file
50
vendor/golang.org/x/exp/constraints/constraints.go
generated
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package constraints defines a set of useful constraints to be used
|
||||||
|
// with type parameters.
|
||||||
|
package constraints
|
||||||
|
|
||||||
|
// Signed is a constraint that permits any signed integer type.
|
||||||
|
// If future releases of Go add new predeclared signed integer types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Signed interface {
|
||||||
|
~int | ~int8 | ~int16 | ~int32 | ~int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsigned is a constraint that permits any unsigned integer type.
|
||||||
|
// If future releases of Go add new predeclared unsigned integer types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Unsigned interface {
|
||||||
|
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer is a constraint that permits any integer type.
|
||||||
|
// If future releases of Go add new predeclared integer types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Integer interface {
|
||||||
|
Signed | Unsigned
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float is a constraint that permits any floating-point type.
|
||||||
|
// If future releases of Go add new predeclared floating-point types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Float interface {
|
||||||
|
~float32 | ~float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complex is a constraint that permits any complex numeric type.
|
||||||
|
// If future releases of Go add new predeclared complex numeric types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Complex interface {
|
||||||
|
~complex64 | ~complex128
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ordered is a constraint that permits any ordered type: any type
|
||||||
|
// that supports the operators < <= >= >.
|
||||||
|
// If future releases of Go add new ordered types,
|
||||||
|
// this constraint will be modified to include them.
|
||||||
|
type Ordered interface {
|
||||||
|
Integer | Float | ~string
|
||||||
|
}
|
||||||
282
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
Normal file
282
vendor/golang.org/x/exp/slices/slices.go
generated
vendored
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package slices defines various functions useful with slices of any type.
|
||||||
|
// Unless otherwise specified, these functions all apply to the elements
|
||||||
|
// of a slice at index 0 <= i < len(s).
|
||||||
|
//
|
||||||
|
// Note that the less function in IsSortedFunc, SortFunc, SortStableFunc requires a
|
||||||
|
// strict weak ordering (https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings),
|
||||||
|
// or the sorting may fail to sort correctly. A common case is when sorting slices of
|
||||||
|
// floating-point numbers containing NaN values.
|
||||||
|
package slices
|
||||||
|
|
||||||
|
import "golang.org/x/exp/constraints"
|
||||||
|
|
||||||
|
// Equal reports whether two slices are equal: the same length and all
|
||||||
|
// elements equal. If the lengths are different, Equal returns false.
|
||||||
|
// Otherwise, the elements are compared in increasing index order, and the
|
||||||
|
// comparison stops at the first unequal pair.
|
||||||
|
// Floating point NaNs are not considered equal.
|
||||||
|
func Equal[E comparable](s1, s2 []E) bool {
|
||||||
|
if len(s1) != len(s2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := range s1 {
|
||||||
|
if s1[i] != s2[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// EqualFunc reports whether two slices are equal using a comparison
|
||||||
|
// function on each pair of elements. If the lengths are different,
|
||||||
|
// EqualFunc returns false. Otherwise, the elements are compared in
|
||||||
|
// increasing index order, and the comparison stops at the first index
|
||||||
|
// for which eq returns false.
|
||||||
|
func EqualFunc[E1, E2 any](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
|
||||||
|
if len(s1) != len(s2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v1 := range s1 {
|
||||||
|
v2 := s2[i]
|
||||||
|
if !eq(v1, v2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare compares the elements of s1 and s2.
|
||||||
|
// The elements are compared sequentially, starting at index 0,
|
||||||
|
// until one element is not equal to the other.
|
||||||
|
// The result of comparing the first non-matching elements is returned.
|
||||||
|
// If both slices are equal until one of them ends, the shorter slice is
|
||||||
|
// considered less than the longer one.
|
||||||
|
// The result is 0 if s1 == s2, -1 if s1 < s2, and +1 if s1 > s2.
|
||||||
|
// Comparisons involving floating point NaNs are ignored.
|
||||||
|
func Compare[E constraints.Ordered](s1, s2 []E) int {
|
||||||
|
s2len := len(s2)
|
||||||
|
for i, v1 := range s1 {
|
||||||
|
if i >= s2len {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
v2 := s2[i]
|
||||||
|
switch {
|
||||||
|
case v1 < v2:
|
||||||
|
return -1
|
||||||
|
case v1 > v2:
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s1) < s2len {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompareFunc is like Compare but uses a comparison function
|
||||||
|
// on each pair of elements. The elements are compared in increasing
|
||||||
|
// index order, and the comparisons stop after the first time cmp
|
||||||
|
// returns non-zero.
|
||||||
|
// The result is the first non-zero result of cmp; if cmp always
|
||||||
|
// returns 0 the result is 0 if len(s1) == len(s2), -1 if len(s1) < len(s2),
|
||||||
|
// and +1 if len(s1) > len(s2).
|
||||||
|
func CompareFunc[E1, E2 any](s1 []E1, s2 []E2, cmp func(E1, E2) int) int {
|
||||||
|
s2len := len(s2)
|
||||||
|
for i, v1 := range s1 {
|
||||||
|
if i >= s2len {
|
||||||
|
return +1
|
||||||
|
}
|
||||||
|
v2 := s2[i]
|
||||||
|
if c := cmp(v1, v2); c != 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(s1) < s2len {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index returns the index of the first occurrence of v in s,
|
||||||
|
// or -1 if not present.
|
||||||
|
func Index[E comparable](s []E, v E) int {
|
||||||
|
for i := range s {
|
||||||
|
if v == s[i] {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndexFunc returns the first index i satisfying f(s[i]),
|
||||||
|
// or -1 if none do.
|
||||||
|
func IndexFunc[E any](s []E, f func(E) bool) int {
|
||||||
|
for i := range s {
|
||||||
|
if f(s[i]) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains reports whether v is present in s.
|
||||||
|
func Contains[E comparable](s []E, v E) bool {
|
||||||
|
return Index(s, v) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContainsFunc reports whether at least one
|
||||||
|
// element e of s satisfies f(e).
|
||||||
|
func ContainsFunc[E any](s []E, f func(E) bool) bool {
|
||||||
|
return IndexFunc(s, f) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert inserts the values v... into s at index i,
|
||||||
|
// returning the modified slice.
|
||||||
|
// In the returned slice r, r[i] == v[0].
|
||||||
|
// Insert panics if i is out of range.
|
||||||
|
// This function is O(len(s) + len(v)).
|
||||||
|
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
|
||||||
|
tot := len(s) + len(v)
|
||||||
|
if tot <= cap(s) {
|
||||||
|
s2 := s[:tot]
|
||||||
|
copy(s2[i+len(v):], s[i:])
|
||||||
|
copy(s2[i:], v)
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
s2 := make(S, tot)
|
||||||
|
copy(s2, s[:i])
|
||||||
|
copy(s2[i:], v)
|
||||||
|
copy(s2[i+len(v):], s[i:])
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes the elements s[i:j] from s, returning the modified slice.
|
||||||
|
// Delete panics if s[i:j] is not a valid slice of s.
|
||||||
|
// Delete modifies the contents of the slice s; it does not create a new slice.
|
||||||
|
// Delete is O(len(s)-j), so if many items must be deleted, it is better to
|
||||||
|
// make a single call deleting them all together than to delete one at a time.
|
||||||
|
// Delete might not modify the elements s[len(s)-(j-i):len(s)]. If those
|
||||||
|
// elements contain pointers you might consider zeroing those elements so that
|
||||||
|
// objects they reference can be garbage collected.
|
||||||
|
func Delete[S ~[]E, E any](s S, i, j int) S {
|
||||||
|
_ = s[i:j] // bounds check
|
||||||
|
|
||||||
|
return append(s[:i], s[j:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFunc removes any elements from s for which del returns true,
|
||||||
|
// returning the modified slice.
|
||||||
|
// When DeleteFunc removes m elements, it might not modify the elements
|
||||||
|
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
|
||||||
|
// zeroing those elements so that objects they reference can be garbage
|
||||||
|
// collected.
|
||||||
|
func DeleteFunc[S ~[]E, E any](s S, del func(E) bool) S {
|
||||||
|
// Don't start copying elements until we find one to delete.
|
||||||
|
for i, v := range s {
|
||||||
|
if del(v) {
|
||||||
|
j := i
|
||||||
|
for i++; i < len(s); i++ {
|
||||||
|
v = s[i]
|
||||||
|
if !del(v) {
|
||||||
|
s[j] = v
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace replaces the elements s[i:j] by the given v, and returns the
|
||||||
|
// modified slice. Replace panics if s[i:j] is not a valid slice of s.
|
||||||
|
func Replace[S ~[]E, E any](s S, i, j int, v ...E) S {
|
||||||
|
_ = s[i:j] // verify that i:j is a valid subslice
|
||||||
|
tot := len(s[:i]) + len(v) + len(s[j:])
|
||||||
|
if tot <= cap(s) {
|
||||||
|
s2 := s[:tot]
|
||||||
|
copy(s2[i+len(v):], s[j:])
|
||||||
|
copy(s2[i:], v)
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
s2 := make(S, tot)
|
||||||
|
copy(s2, s[:i])
|
||||||
|
copy(s2[i:], v)
|
||||||
|
copy(s2[i+len(v):], s[j:])
|
||||||
|
return s2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone returns a copy of the slice.
|
||||||
|
// The elements are copied using assignment, so this is a shallow clone.
|
||||||
|
func Clone[S ~[]E, E any](s S) S {
|
||||||
|
// Preserve nil in case it matters.
|
||||||
|
if s == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return append(S([]E{}), s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compact replaces consecutive runs of equal elements with a single copy.
|
||||||
|
// This is like the uniq command found on Unix.
|
||||||
|
// Compact modifies the contents of the slice s; it does not create a new slice.
|
||||||
|
// When Compact discards m elements in total, it might not modify the elements
|
||||||
|
// s[len(s)-m:len(s)]. If those elements contain pointers you might consider
|
||||||
|
// zeroing those elements so that objects they reference can be garbage collected.
|
||||||
|
func Compact[S ~[]E, E comparable](s S) S {
|
||||||
|
if len(s) < 2 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for k := 1; k < len(s); k++ {
|
||||||
|
if s[k] != s[k-1] {
|
||||||
|
if i != k {
|
||||||
|
s[i] = s[k]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompactFunc is like Compact but uses a comparison function.
|
||||||
|
func CompactFunc[S ~[]E, E any](s S, eq func(E, E) bool) S {
|
||||||
|
if len(s) < 2 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
i := 1
|
||||||
|
for k := 1; k < len(s); k++ {
|
||||||
|
if !eq(s[k], s[k-1]) {
|
||||||
|
if i != k {
|
||||||
|
s[i] = s[k]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s[:i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grow increases the slice's capacity, if necessary, to guarantee space for
|
||||||
|
// another n elements. After Grow(n), at least n elements can be appended
|
||||||
|
// to the slice without another allocation. If n is negative or too large to
|
||||||
|
// allocate the memory, Grow panics.
|
||||||
|
func Grow[S ~[]E, E any](s S, n int) S {
|
||||||
|
if n < 0 {
|
||||||
|
panic("cannot be negative")
|
||||||
|
}
|
||||||
|
if n -= cap(s) - len(s); n > 0 {
|
||||||
|
// TODO(https://go.dev/issue/53888): Make using []E instead of S
|
||||||
|
// to workaround a compiler bug where the runtime.growslice optimization
|
||||||
|
// does not take effect. Revert when the compiler is fixed.
|
||||||
|
s = append([]E(s)[:cap(s)], make([]E, n)...)[:len(s)]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clip removes unused capacity from the slice, returning s[:len(s):len(s)].
|
||||||
|
func Clip[S ~[]E, E any](s S) S {
|
||||||
|
return s[:len(s):len(s)]
|
||||||
|
}
|
||||||
128
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
Normal file
128
vendor/golang.org/x/exp/slices/sort.go
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package slices
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/bits"
|
||||||
|
|
||||||
|
"golang.org/x/exp/constraints"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Sort sorts a slice of any ordered type in ascending order.
|
||||||
|
// Sort may fail to sort correctly when sorting slices of floating-point
|
||||||
|
// numbers containing Not-a-number (NaN) values.
|
||||||
|
// Use slices.SortFunc(x, func(a, b float64) bool {return a < b || (math.IsNaN(a) && !math.IsNaN(b))})
|
||||||
|
// instead if the input may contain NaNs.
|
||||||
|
func Sort[E constraints.Ordered](x []E) {
|
||||||
|
n := len(x)
|
||||||
|
pdqsortOrdered(x, 0, n, bits.Len(uint(n)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortFunc sorts the slice x in ascending order as determined by the less function.
|
||||||
|
// This sort is not guaranteed to be stable.
|
||||||
|
//
|
||||||
|
// SortFunc requires that less is a strict weak ordering.
|
||||||
|
// See https://en.wikipedia.org/wiki/Weak_ordering#Strict_weak_orderings.
|
||||||
|
func SortFunc[E any](x []E, less func(a, b E) bool) {
|
||||||
|
n := len(x)
|
||||||
|
pdqsortLessFunc(x, 0, n, bits.Len(uint(n)), less)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SortStableFunc sorts the slice x while keeping the original order of equal
|
||||||
|
// elements, using less to compare elements.
|
||||||
|
func SortStableFunc[E any](x []E, less func(a, b E) bool) {
|
||||||
|
stableLessFunc(x, len(x), less)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSorted reports whether x is sorted in ascending order.
|
||||||
|
func IsSorted[E constraints.Ordered](x []E) bool {
|
||||||
|
for i := len(x) - 1; i > 0; i-- {
|
||||||
|
if x[i] < x[i-1] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSortedFunc reports whether x is sorted in ascending order, with less as the
|
||||||
|
// comparison function.
|
||||||
|
func IsSortedFunc[E any](x []E, less func(a, b E) bool) bool {
|
||||||
|
for i := len(x) - 1; i > 0; i-- {
|
||||||
|
if less(x[i], x[i-1]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinarySearch searches for target in a sorted slice and returns the position
|
||||||
|
// where target is found, or the position where target would appear in the
|
||||||
|
// sort order; it also returns a bool saying whether the target is really found
|
||||||
|
// in the slice. The slice must be sorted in increasing order.
|
||||||
|
func BinarySearch[E constraints.Ordered](x []E, target E) (int, bool) {
|
||||||
|
// Inlining is faster than calling BinarySearchFunc with a lambda.
|
||||||
|
n := len(x)
|
||||||
|
// Define x[-1] < target and x[n] >= target.
|
||||||
|
// Invariant: x[i-1] < target, x[j] >= target.
|
||||||
|
i, j := 0, n
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1) // avoid overflow when computing h
|
||||||
|
// i ≤ h < j
|
||||||
|
if x[h] < target {
|
||||||
|
i = h + 1 // preserves x[i-1] < target
|
||||||
|
} else {
|
||||||
|
j = h // preserves x[j] >= target
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// i == j, x[i-1] < target, and x[j] (= x[i]) >= target => answer is i.
|
||||||
|
return i, i < n && x[i] == target
|
||||||
|
}
|
||||||
|
|
||||||
|
// BinarySearchFunc works like BinarySearch, but uses a custom comparison
|
||||||
|
// function. The slice must be sorted in increasing order, where "increasing"
|
||||||
|
// is defined by cmp. cmp should return 0 if the slice element matches
|
||||||
|
// the target, a negative number if the slice element precedes the target,
|
||||||
|
// or a positive number if the slice element follows the target.
|
||||||
|
// cmp must implement the same ordering as the slice, such that if
|
||||||
|
// cmp(a, t) < 0 and cmp(b, t) >= 0, then a must precede b in the slice.
|
||||||
|
func BinarySearchFunc[E, T any](x []E, target T, cmp func(E, T) int) (int, bool) {
|
||||||
|
n := len(x)
|
||||||
|
// Define cmp(x[-1], target) < 0 and cmp(x[n], target) >= 0 .
|
||||||
|
// Invariant: cmp(x[i - 1], target) < 0, cmp(x[j], target) >= 0.
|
||||||
|
i, j := 0, n
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1) // avoid overflow when computing h
|
||||||
|
// i ≤ h < j
|
||||||
|
if cmp(x[h], target) < 0 {
|
||||||
|
i = h + 1 // preserves cmp(x[i - 1], target) < 0
|
||||||
|
} else {
|
||||||
|
j = h // preserves cmp(x[j], target) >= 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// i == j, cmp(x[i-1], target) < 0, and cmp(x[j], target) (= cmp(x[i], target)) >= 0 => answer is i.
|
||||||
|
return i, i < n && cmp(x[i], target) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortedHint int // hint for pdqsort when choosing the pivot
|
||||||
|
|
||||||
|
const (
|
||||||
|
unknownHint sortedHint = iota
|
||||||
|
increasingHint
|
||||||
|
decreasingHint
|
||||||
|
)
|
||||||
|
|
||||||
|
// xorshift paper: https://www.jstatsoft.org/article/view/v008i14/xorshift.pdf
|
||||||
|
type xorshift uint64
|
||||||
|
|
||||||
|
func (r *xorshift) Next() uint64 {
|
||||||
|
*r ^= *r << 13
|
||||||
|
*r ^= *r >> 17
|
||||||
|
*r ^= *r << 5
|
||||||
|
return uint64(*r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func nextPowerOfTwo(length int) uint {
|
||||||
|
return 1 << bits.Len(uint(length))
|
||||||
|
}
|
||||||
479
vendor/golang.org/x/exp/slices/zsortfunc.go
generated
vendored
Normal file
479
vendor/golang.org/x/exp/slices/zsortfunc.go
generated
vendored
Normal file
@@ -0,0 +1,479 @@
|
|||||||
|
// Code generated by gen_sort_variants.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package slices
|
||||||
|
|
||||||
|
// insertionSortLessFunc sorts data[a:b] using insertion sort.
|
||||||
|
func insertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) {
|
||||||
|
for i := a + 1; i < b; i++ {
|
||||||
|
for j := i; j > a && less(data[j], data[j-1]); j-- {
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// siftDownLessFunc implements the heap property on data[lo:hi].
|
||||||
|
// first is an offset into the array where the root of the heap lies.
|
||||||
|
func siftDownLessFunc[E any](data []E, lo, hi, first int, less func(a, b E) bool) {
|
||||||
|
root := lo
|
||||||
|
for {
|
||||||
|
child := 2*root + 1
|
||||||
|
if child >= hi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if child+1 < hi && less(data[first+child], data[first+child+1]) {
|
||||||
|
child++
|
||||||
|
}
|
||||||
|
if !less(data[first+root], data[first+child]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data[first+root], data[first+child] = data[first+child], data[first+root]
|
||||||
|
root = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func heapSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) {
|
||||||
|
first := a
|
||||||
|
lo := 0
|
||||||
|
hi := b - a
|
||||||
|
|
||||||
|
// Build heap with greatest element at top.
|
||||||
|
for i := (hi - 1) / 2; i >= 0; i-- {
|
||||||
|
siftDownLessFunc(data, i, hi, first, less)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop elements, largest first, into end of data.
|
||||||
|
for i := hi - 1; i >= 0; i-- {
|
||||||
|
data[first], data[first+i] = data[first+i], data[first]
|
||||||
|
siftDownLessFunc(data, lo, i, first, less)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdqsortLessFunc sorts data[a:b].
|
||||||
|
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
|
||||||
|
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
|
||||||
|
// C++ implementation: https://github.com/orlp/pdqsort
|
||||||
|
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
|
||||||
|
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
|
||||||
|
func pdqsortLessFunc[E any](data []E, a, b, limit int, less func(a, b E) bool) {
|
||||||
|
const maxInsertion = 12
|
||||||
|
|
||||||
|
var (
|
||||||
|
wasBalanced = true // whether the last partitioning was reasonably balanced
|
||||||
|
wasPartitioned = true // whether the slice was already partitioned
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
length := b - a
|
||||||
|
|
||||||
|
if length <= maxInsertion {
|
||||||
|
insertionSortLessFunc(data, a, b, less)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to heapsort if too many bad choices were made.
|
||||||
|
if limit == 0 {
|
||||||
|
heapSortLessFunc(data, a, b, less)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last partitioning was imbalanced, we need to breaking patterns.
|
||||||
|
if !wasBalanced {
|
||||||
|
breakPatternsLessFunc(data, a, b, less)
|
||||||
|
limit--
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot, hint := choosePivotLessFunc(data, a, b, less)
|
||||||
|
if hint == decreasingHint {
|
||||||
|
reverseRangeLessFunc(data, a, b, less)
|
||||||
|
// The chosen pivot was pivot-a elements after the start of the array.
|
||||||
|
// After reversing it is pivot-a elements before the end of the array.
|
||||||
|
// The idea came from Rust's implementation.
|
||||||
|
pivot = (b - 1) - (pivot - a)
|
||||||
|
hint = increasingHint
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slice is likely already sorted.
|
||||||
|
if wasBalanced && wasPartitioned && hint == increasingHint {
|
||||||
|
if partialInsertionSortLessFunc(data, a, b, less) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably the slice contains many duplicate elements, partition the slice into
|
||||||
|
// elements equal to and elements greater than the pivot.
|
||||||
|
if a > 0 && !less(data[a-1], data[pivot]) {
|
||||||
|
mid := partitionEqualLessFunc(data, a, b, pivot, less)
|
||||||
|
a = mid
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mid, alreadyPartitioned := partitionLessFunc(data, a, b, pivot, less)
|
||||||
|
wasPartitioned = alreadyPartitioned
|
||||||
|
|
||||||
|
leftLen, rightLen := mid-a, b-mid
|
||||||
|
balanceThreshold := length / 8
|
||||||
|
if leftLen < rightLen {
|
||||||
|
wasBalanced = leftLen >= balanceThreshold
|
||||||
|
pdqsortLessFunc(data, a, mid, limit, less)
|
||||||
|
a = mid + 1
|
||||||
|
} else {
|
||||||
|
wasBalanced = rightLen >= balanceThreshold
|
||||||
|
pdqsortLessFunc(data, mid+1, b, limit, less)
|
||||||
|
b = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// partitionLessFunc does one quicksort partition.
|
||||||
|
// Let p = data[pivot]
|
||||||
|
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
|
||||||
|
// On return, data[newpivot] = p
|
||||||
|
func partitionLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int, alreadyPartitioned bool) {
|
||||||
|
data[a], data[pivot] = data[pivot], data[a]
|
||||||
|
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
||||||
|
|
||||||
|
for i <= j && less(data[i], data[a]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && !less(data[j], data[a]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
data[j], data[a] = data[a], data[j]
|
||||||
|
return j, true
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i <= j && less(data[i], data[a]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && !less(data[j], data[a]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
data[j], data[a] = data[a], data[j]
|
||||||
|
return j, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// partitionEqualLessFunc partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
|
||||||
|
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
|
||||||
|
func partitionEqualLessFunc[E any](data []E, a, b, pivot int, less func(a, b E) bool) (newpivot int) {
|
||||||
|
data[a], data[pivot] = data[pivot], data[a]
|
||||||
|
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i <= j && !less(data[a], data[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && less(data[a], data[j]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// partialInsertionSortLessFunc partially sorts a slice, returns true if the slice is sorted at the end.
|
||||||
|
func partialInsertionSortLessFunc[E any](data []E, a, b int, less func(a, b E) bool) bool {
|
||||||
|
const (
|
||||||
|
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
|
||||||
|
shortestShifting = 50 // don't shift any elements on short arrays
|
||||||
|
)
|
||||||
|
i := a + 1
|
||||||
|
for j := 0; j < maxSteps; j++ {
|
||||||
|
for i < b && !less(data[i], data[i-1]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if b-a < shortestShifting {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i], data[i-1] = data[i-1], data[i]
|
||||||
|
|
||||||
|
// Shift the smaller one to the left.
|
||||||
|
if i-a >= 2 {
|
||||||
|
for j := i - 1; j >= 1; j-- {
|
||||||
|
if !less(data[j], data[j-1]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shift the greater one to the right.
|
||||||
|
if b-i >= 2 {
|
||||||
|
for j := i + 1; j < b; j++ {
|
||||||
|
if !less(data[j], data[j-1]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// breakPatternsLessFunc scatters some elements around in an attempt to break some patterns
|
||||||
|
// that might cause imbalanced partitions in quicksort.
|
||||||
|
func breakPatternsLessFunc[E any](data []E, a, b int, less func(a, b E) bool) {
|
||||||
|
length := b - a
|
||||||
|
if length >= 8 {
|
||||||
|
random := xorshift(length)
|
||||||
|
modulus := nextPowerOfTwo(length)
|
||||||
|
|
||||||
|
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
|
||||||
|
other := int(uint(random.Next()) & (modulus - 1))
|
||||||
|
if other >= length {
|
||||||
|
other -= length
|
||||||
|
}
|
||||||
|
data[idx], data[a+other] = data[a+other], data[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// choosePivotLessFunc chooses a pivot in data[a:b].
|
||||||
|
//
|
||||||
|
// [0,8): chooses a static pivot.
|
||||||
|
// [8,shortestNinther): uses the simple median-of-three method.
|
||||||
|
// [shortestNinther,∞): uses the Tukey ninther method.
|
||||||
|
func choosePivotLessFunc[E any](data []E, a, b int, less func(a, b E) bool) (pivot int, hint sortedHint) {
|
||||||
|
const (
|
||||||
|
shortestNinther = 50
|
||||||
|
maxSwaps = 4 * 3
|
||||||
|
)
|
||||||
|
|
||||||
|
l := b - a
|
||||||
|
|
||||||
|
var (
|
||||||
|
swaps int
|
||||||
|
i = a + l/4*1
|
||||||
|
j = a + l/4*2
|
||||||
|
k = a + l/4*3
|
||||||
|
)
|
||||||
|
|
||||||
|
if l >= 8 {
|
||||||
|
if l >= shortestNinther {
|
||||||
|
// Tukey ninther method, the idea came from Rust's implementation.
|
||||||
|
i = medianAdjacentLessFunc(data, i, &swaps, less)
|
||||||
|
j = medianAdjacentLessFunc(data, j, &swaps, less)
|
||||||
|
k = medianAdjacentLessFunc(data, k, &swaps, less)
|
||||||
|
}
|
||||||
|
// Find the median among i, j, k and stores it into j.
|
||||||
|
j = medianLessFunc(data, i, j, k, &swaps, less)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch swaps {
|
||||||
|
case 0:
|
||||||
|
return j, increasingHint
|
||||||
|
case maxSwaps:
|
||||||
|
return j, decreasingHint
|
||||||
|
default:
|
||||||
|
return j, unknownHint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// order2LessFunc returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
|
||||||
|
func order2LessFunc[E any](data []E, a, b int, swaps *int, less func(a, b E) bool) (int, int) {
|
||||||
|
if less(data[b], data[a]) {
|
||||||
|
*swaps++
|
||||||
|
return b, a
|
||||||
|
}
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// medianLessFunc returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
|
||||||
|
func medianLessFunc[E any](data []E, a, b, c int, swaps *int, less func(a, b E) bool) int {
|
||||||
|
a, b = order2LessFunc(data, a, b, swaps, less)
|
||||||
|
b, c = order2LessFunc(data, b, c, swaps, less)
|
||||||
|
a, b = order2LessFunc(data, a, b, swaps, less)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// medianAdjacentLessFunc finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
|
||||||
|
func medianAdjacentLessFunc[E any](data []E, a int, swaps *int, less func(a, b E) bool) int {
|
||||||
|
return medianLessFunc(data, a-1, a, a+1, swaps, less)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseRangeLessFunc[E any](data []E, a, b int, less func(a, b E) bool) {
|
||||||
|
i := a
|
||||||
|
j := b - 1
|
||||||
|
for i < j {
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func swapRangeLessFunc[E any](data []E, a, b, n int, less func(a, b E) bool) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
data[a+i], data[b+i] = data[b+i], data[a+i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stableLessFunc[E any](data []E, n int, less func(a, b E) bool) {
|
||||||
|
blockSize := 20 // must be > 0
|
||||||
|
a, b := 0, blockSize
|
||||||
|
for b <= n {
|
||||||
|
insertionSortLessFunc(data, a, b, less)
|
||||||
|
a = b
|
||||||
|
b += blockSize
|
||||||
|
}
|
||||||
|
insertionSortLessFunc(data, a, n, less)
|
||||||
|
|
||||||
|
for blockSize < n {
|
||||||
|
a, b = 0, 2*blockSize
|
||||||
|
for b <= n {
|
||||||
|
symMergeLessFunc(data, a, a+blockSize, b, less)
|
||||||
|
a = b
|
||||||
|
b += 2 * blockSize
|
||||||
|
}
|
||||||
|
if m := a + blockSize; m < n {
|
||||||
|
symMergeLessFunc(data, a, m, n, less)
|
||||||
|
}
|
||||||
|
blockSize *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// symMergeLessFunc merges the two sorted subsequences data[a:m] and data[m:b] using
|
||||||
|
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
|
||||||
|
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
|
||||||
|
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
|
||||||
|
// Computer Science, pages 714-723. Springer, 2004.
|
||||||
|
//
|
||||||
|
// Let M = m-a and N = b-n. Wolog M < N.
|
||||||
|
// The recursion depth is bound by ceil(log(N+M)).
|
||||||
|
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
|
||||||
|
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
|
||||||
|
//
|
||||||
|
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
|
||||||
|
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
|
||||||
|
// in the paper carries through for Swap operations, especially as the block
|
||||||
|
// swapping rotate uses only O(M+N) Swaps.
|
||||||
|
//
|
||||||
|
// symMerge assumes non-degenerate arguments: a < m && m < b.
|
||||||
|
// Having the caller check this condition eliminates many leaf recursion calls,
|
||||||
|
// which improves performance.
|
||||||
|
func symMergeLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) {
|
||||||
|
// Avoid unnecessary recursions of symMerge
|
||||||
|
// by direct insertion of data[a] into data[m:b]
|
||||||
|
// if data[a:m] only contains one element.
|
||||||
|
if m-a == 1 {
|
||||||
|
// Use binary search to find the lowest index i
|
||||||
|
// such that data[i] >= data[a] for m <= i < b.
|
||||||
|
// Exit the search loop with i == b in case no such index exists.
|
||||||
|
i := m
|
||||||
|
j := b
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1)
|
||||||
|
if less(data[h], data[a]) {
|
||||||
|
i = h + 1
|
||||||
|
} else {
|
||||||
|
j = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Swap values until data[a] reaches the position before i.
|
||||||
|
for k := a; k < i-1; k++ {
|
||||||
|
data[k], data[k+1] = data[k+1], data[k]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unnecessary recursions of symMerge
|
||||||
|
// by direct insertion of data[m] into data[a:m]
|
||||||
|
// if data[m:b] only contains one element.
|
||||||
|
if b-m == 1 {
|
||||||
|
// Use binary search to find the lowest index i
|
||||||
|
// such that data[i] > data[m] for a <= i < m.
|
||||||
|
// Exit the search loop with i == m in case no such index exists.
|
||||||
|
i := a
|
||||||
|
j := m
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1)
|
||||||
|
if !less(data[m], data[h]) {
|
||||||
|
i = h + 1
|
||||||
|
} else {
|
||||||
|
j = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Swap values until data[m] reaches the position i.
|
||||||
|
for k := m; k > i; k-- {
|
||||||
|
data[k], data[k-1] = data[k-1], data[k]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mid := int(uint(a+b) >> 1)
|
||||||
|
n := mid + m
|
||||||
|
var start, r int
|
||||||
|
if m > mid {
|
||||||
|
start = n - b
|
||||||
|
r = mid
|
||||||
|
} else {
|
||||||
|
start = a
|
||||||
|
r = m
|
||||||
|
}
|
||||||
|
p := n - 1
|
||||||
|
|
||||||
|
for start < r {
|
||||||
|
c := int(uint(start+r) >> 1)
|
||||||
|
if !less(data[p-c], data[c]) {
|
||||||
|
start = c + 1
|
||||||
|
} else {
|
||||||
|
r = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := n - start
|
||||||
|
if start < m && m < end {
|
||||||
|
rotateLessFunc(data, start, m, end, less)
|
||||||
|
}
|
||||||
|
if a < start && start < mid {
|
||||||
|
symMergeLessFunc(data, a, start, mid, less)
|
||||||
|
}
|
||||||
|
if mid < end && end < b {
|
||||||
|
symMergeLessFunc(data, mid, end, b, less)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateLessFunc rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
|
||||||
|
// Data of the form 'x u v y' is changed to 'x v u y'.
|
||||||
|
// rotate performs at most b-a many calls to data.Swap,
|
||||||
|
// and it assumes non-degenerate arguments: a < m && m < b.
|
||||||
|
func rotateLessFunc[E any](data []E, a, m, b int, less func(a, b E) bool) {
|
||||||
|
i := m - a
|
||||||
|
j := b - m
|
||||||
|
|
||||||
|
for i != j {
|
||||||
|
if i > j {
|
||||||
|
swapRangeLessFunc(data, m-i, m, j, less)
|
||||||
|
i -= j
|
||||||
|
} else {
|
||||||
|
swapRangeLessFunc(data, m-i, m+j-i, i, less)
|
||||||
|
j -= i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// i == j
|
||||||
|
swapRangeLessFunc(data, m-i, m, i, less)
|
||||||
|
}
|
||||||
481
vendor/golang.org/x/exp/slices/zsortordered.go
generated
vendored
Normal file
481
vendor/golang.org/x/exp/slices/zsortordered.go
generated
vendored
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
// Code generated by gen_sort_variants.go; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package slices
|
||||||
|
|
||||||
|
import "golang.org/x/exp/constraints"
|
||||||
|
|
||||||
|
// insertionSortOrdered sorts data[a:b] using insertion sort.
|
||||||
|
func insertionSortOrdered[E constraints.Ordered](data []E, a, b int) {
|
||||||
|
for i := a + 1; i < b; i++ {
|
||||||
|
for j := i; j > a && (data[j] < data[j-1]); j-- {
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// siftDownOrdered implements the heap property on data[lo:hi].
|
||||||
|
// first is an offset into the array where the root of the heap lies.
|
||||||
|
func siftDownOrdered[E constraints.Ordered](data []E, lo, hi, first int) {
|
||||||
|
root := lo
|
||||||
|
for {
|
||||||
|
child := 2*root + 1
|
||||||
|
if child >= hi {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if child+1 < hi && (data[first+child] < data[first+child+1]) {
|
||||||
|
child++
|
||||||
|
}
|
||||||
|
if !(data[first+root] < data[first+child]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data[first+root], data[first+child] = data[first+child], data[first+root]
|
||||||
|
root = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func heapSortOrdered[E constraints.Ordered](data []E, a, b int) {
|
||||||
|
first := a
|
||||||
|
lo := 0
|
||||||
|
hi := b - a
|
||||||
|
|
||||||
|
// Build heap with greatest element at top.
|
||||||
|
for i := (hi - 1) / 2; i >= 0; i-- {
|
||||||
|
siftDownOrdered(data, i, hi, first)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pop elements, largest first, into end of data.
|
||||||
|
for i := hi - 1; i >= 0; i-- {
|
||||||
|
data[first], data[first+i] = data[first+i], data[first]
|
||||||
|
siftDownOrdered(data, lo, i, first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pdqsortOrdered sorts data[a:b].
|
||||||
|
// The algorithm based on pattern-defeating quicksort(pdqsort), but without the optimizations from BlockQuicksort.
|
||||||
|
// pdqsort paper: https://arxiv.org/pdf/2106.05123.pdf
|
||||||
|
// C++ implementation: https://github.com/orlp/pdqsort
|
||||||
|
// Rust implementation: https://docs.rs/pdqsort/latest/pdqsort/
|
||||||
|
// limit is the number of allowed bad (very unbalanced) pivots before falling back to heapsort.
|
||||||
|
func pdqsortOrdered[E constraints.Ordered](data []E, a, b, limit int) {
|
||||||
|
const maxInsertion = 12
|
||||||
|
|
||||||
|
var (
|
||||||
|
wasBalanced = true // whether the last partitioning was reasonably balanced
|
||||||
|
wasPartitioned = true // whether the slice was already partitioned
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
length := b - a
|
||||||
|
|
||||||
|
if length <= maxInsertion {
|
||||||
|
insertionSortOrdered(data, a, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to heapsort if too many bad choices were made.
|
||||||
|
if limit == 0 {
|
||||||
|
heapSortOrdered(data, a, b)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the last partitioning was imbalanced, we need to breaking patterns.
|
||||||
|
if !wasBalanced {
|
||||||
|
breakPatternsOrdered(data, a, b)
|
||||||
|
limit--
|
||||||
|
}
|
||||||
|
|
||||||
|
pivot, hint := choosePivotOrdered(data, a, b)
|
||||||
|
if hint == decreasingHint {
|
||||||
|
reverseRangeOrdered(data, a, b)
|
||||||
|
// The chosen pivot was pivot-a elements after the start of the array.
|
||||||
|
// After reversing it is pivot-a elements before the end of the array.
|
||||||
|
// The idea came from Rust's implementation.
|
||||||
|
pivot = (b - 1) - (pivot - a)
|
||||||
|
hint = increasingHint
|
||||||
|
}
|
||||||
|
|
||||||
|
// The slice is likely already sorted.
|
||||||
|
if wasBalanced && wasPartitioned && hint == increasingHint {
|
||||||
|
if partialInsertionSortOrdered(data, a, b) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably the slice contains many duplicate elements, partition the slice into
|
||||||
|
// elements equal to and elements greater than the pivot.
|
||||||
|
if a > 0 && !(data[a-1] < data[pivot]) {
|
||||||
|
mid := partitionEqualOrdered(data, a, b, pivot)
|
||||||
|
a = mid
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
mid, alreadyPartitioned := partitionOrdered(data, a, b, pivot)
|
||||||
|
wasPartitioned = alreadyPartitioned
|
||||||
|
|
||||||
|
leftLen, rightLen := mid-a, b-mid
|
||||||
|
balanceThreshold := length / 8
|
||||||
|
if leftLen < rightLen {
|
||||||
|
wasBalanced = leftLen >= balanceThreshold
|
||||||
|
pdqsortOrdered(data, a, mid, limit)
|
||||||
|
a = mid + 1
|
||||||
|
} else {
|
||||||
|
wasBalanced = rightLen >= balanceThreshold
|
||||||
|
pdqsortOrdered(data, mid+1, b, limit)
|
||||||
|
b = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// partitionOrdered does one quicksort partition.
|
||||||
|
// Let p = data[pivot]
|
||||||
|
// Moves elements in data[a:b] around, so that data[i]<p and data[j]>=p for i<newpivot and j>newpivot.
|
||||||
|
// On return, data[newpivot] = p
|
||||||
|
func partitionOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int, alreadyPartitioned bool) {
|
||||||
|
data[a], data[pivot] = data[pivot], data[a]
|
||||||
|
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
||||||
|
|
||||||
|
for i <= j && (data[i] < data[a]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && !(data[j] < data[a]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
data[j], data[a] = data[a], data[j]
|
||||||
|
return j, true
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i <= j && (data[i] < data[a]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && !(data[j] < data[a]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
data[j], data[a] = data[a], data[j]
|
||||||
|
return j, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// partitionEqualOrdered partitions data[a:b] into elements equal to data[pivot] followed by elements greater than data[pivot].
|
||||||
|
// It assumed that data[a:b] does not contain elements smaller than the data[pivot].
|
||||||
|
func partitionEqualOrdered[E constraints.Ordered](data []E, a, b, pivot int) (newpivot int) {
|
||||||
|
data[a], data[pivot] = data[pivot], data[a]
|
||||||
|
i, j := a+1, b-1 // i and j are inclusive of the elements remaining to be partitioned
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i <= j && !(data[a] < data[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
for i <= j && (data[a] < data[j]) {
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
if i > j {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
// partialInsertionSortOrdered partially sorts a slice, returns true if the slice is sorted at the end.
|
||||||
|
func partialInsertionSortOrdered[E constraints.Ordered](data []E, a, b int) bool {
|
||||||
|
const (
|
||||||
|
maxSteps = 5 // maximum number of adjacent out-of-order pairs that will get shifted
|
||||||
|
shortestShifting = 50 // don't shift any elements on short arrays
|
||||||
|
)
|
||||||
|
i := a + 1
|
||||||
|
for j := 0; j < maxSteps; j++ {
|
||||||
|
for i < b && !(data[i] < data[i-1]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == b {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if b-a < shortestShifting {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
data[i], data[i-1] = data[i-1], data[i]
|
||||||
|
|
||||||
|
// Shift the smaller one to the left.
|
||||||
|
if i-a >= 2 {
|
||||||
|
for j := i - 1; j >= 1; j-- {
|
||||||
|
if !(data[j] < data[j-1]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shift the greater one to the right.
|
||||||
|
if b-i >= 2 {
|
||||||
|
for j := i + 1; j < b; j++ {
|
||||||
|
if !(data[j] < data[j-1]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
data[j], data[j-1] = data[j-1], data[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// breakPatternsOrdered scatters some elements around in an attempt to break some patterns
|
||||||
|
// that might cause imbalanced partitions in quicksort.
|
||||||
|
func breakPatternsOrdered[E constraints.Ordered](data []E, a, b int) {
|
||||||
|
length := b - a
|
||||||
|
if length >= 8 {
|
||||||
|
random := xorshift(length)
|
||||||
|
modulus := nextPowerOfTwo(length)
|
||||||
|
|
||||||
|
for idx := a + (length/4)*2 - 1; idx <= a+(length/4)*2+1; idx++ {
|
||||||
|
other := int(uint(random.Next()) & (modulus - 1))
|
||||||
|
if other >= length {
|
||||||
|
other -= length
|
||||||
|
}
|
||||||
|
data[idx], data[a+other] = data[a+other], data[idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// choosePivotOrdered chooses a pivot in data[a:b].
|
||||||
|
//
|
||||||
|
// [0,8): chooses a static pivot.
|
||||||
|
// [8,shortestNinther): uses the simple median-of-three method.
|
||||||
|
// [shortestNinther,∞): uses the Tukey ninther method.
|
||||||
|
func choosePivotOrdered[E constraints.Ordered](data []E, a, b int) (pivot int, hint sortedHint) {
|
||||||
|
const (
|
||||||
|
shortestNinther = 50
|
||||||
|
maxSwaps = 4 * 3
|
||||||
|
)
|
||||||
|
|
||||||
|
l := b - a
|
||||||
|
|
||||||
|
var (
|
||||||
|
swaps int
|
||||||
|
i = a + l/4*1
|
||||||
|
j = a + l/4*2
|
||||||
|
k = a + l/4*3
|
||||||
|
)
|
||||||
|
|
||||||
|
if l >= 8 {
|
||||||
|
if l >= shortestNinther {
|
||||||
|
// Tukey ninther method, the idea came from Rust's implementation.
|
||||||
|
i = medianAdjacentOrdered(data, i, &swaps)
|
||||||
|
j = medianAdjacentOrdered(data, j, &swaps)
|
||||||
|
k = medianAdjacentOrdered(data, k, &swaps)
|
||||||
|
}
|
||||||
|
// Find the median among i, j, k and stores it into j.
|
||||||
|
j = medianOrdered(data, i, j, k, &swaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch swaps {
|
||||||
|
case 0:
|
||||||
|
return j, increasingHint
|
||||||
|
case maxSwaps:
|
||||||
|
return j, decreasingHint
|
||||||
|
default:
|
||||||
|
return j, unknownHint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// order2Ordered returns x,y where data[x] <= data[y], where x,y=a,b or x,y=b,a.
|
||||||
|
func order2Ordered[E constraints.Ordered](data []E, a, b int, swaps *int) (int, int) {
|
||||||
|
if data[b] < data[a] {
|
||||||
|
*swaps++
|
||||||
|
return b, a
|
||||||
|
}
|
||||||
|
return a, b
|
||||||
|
}
|
||||||
|
|
||||||
|
// medianOrdered returns x where data[x] is the median of data[a],data[b],data[c], where x is a, b, or c.
|
||||||
|
func medianOrdered[E constraints.Ordered](data []E, a, b, c int, swaps *int) int {
|
||||||
|
a, b = order2Ordered(data, a, b, swaps)
|
||||||
|
b, c = order2Ordered(data, b, c, swaps)
|
||||||
|
a, b = order2Ordered(data, a, b, swaps)
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// medianAdjacentOrdered finds the median of data[a - 1], data[a], data[a + 1] and stores the index into a.
|
||||||
|
func medianAdjacentOrdered[E constraints.Ordered](data []E, a int, swaps *int) int {
|
||||||
|
return medianOrdered(data, a-1, a, a+1, swaps)
|
||||||
|
}
|
||||||
|
|
||||||
|
func reverseRangeOrdered[E constraints.Ordered](data []E, a, b int) {
|
||||||
|
i := a
|
||||||
|
j := b - 1
|
||||||
|
for i < j {
|
||||||
|
data[i], data[j] = data[j], data[i]
|
||||||
|
i++
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func swapRangeOrdered[E constraints.Ordered](data []E, a, b, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
data[a+i], data[b+i] = data[b+i], data[a+i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stableOrdered[E constraints.Ordered](data []E, n int) {
|
||||||
|
blockSize := 20 // must be > 0
|
||||||
|
a, b := 0, blockSize
|
||||||
|
for b <= n {
|
||||||
|
insertionSortOrdered(data, a, b)
|
||||||
|
a = b
|
||||||
|
b += blockSize
|
||||||
|
}
|
||||||
|
insertionSortOrdered(data, a, n)
|
||||||
|
|
||||||
|
for blockSize < n {
|
||||||
|
a, b = 0, 2*blockSize
|
||||||
|
for b <= n {
|
||||||
|
symMergeOrdered(data, a, a+blockSize, b)
|
||||||
|
a = b
|
||||||
|
b += 2 * blockSize
|
||||||
|
}
|
||||||
|
if m := a + blockSize; m < n {
|
||||||
|
symMergeOrdered(data, a, m, n)
|
||||||
|
}
|
||||||
|
blockSize *= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// symMergeOrdered merges the two sorted subsequences data[a:m] and data[m:b] using
|
||||||
|
// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum
|
||||||
|
// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz
|
||||||
|
// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in
|
||||||
|
// Computer Science, pages 714-723. Springer, 2004.
|
||||||
|
//
|
||||||
|
// Let M = m-a and N = b-n. Wolog M < N.
|
||||||
|
// The recursion depth is bound by ceil(log(N+M)).
|
||||||
|
// The algorithm needs O(M*log(N/M + 1)) calls to data.Less.
|
||||||
|
// The algorithm needs O((M+N)*log(M)) calls to data.Swap.
|
||||||
|
//
|
||||||
|
// The paper gives O((M+N)*log(M)) as the number of assignments assuming a
|
||||||
|
// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation
|
||||||
|
// in the paper carries through for Swap operations, especially as the block
|
||||||
|
// swapping rotate uses only O(M+N) Swaps.
|
||||||
|
//
|
||||||
|
// symMerge assumes non-degenerate arguments: a < m && m < b.
|
||||||
|
// Having the caller check this condition eliminates many leaf recursion calls,
|
||||||
|
// which improves performance.
|
||||||
|
func symMergeOrdered[E constraints.Ordered](data []E, a, m, b int) {
|
||||||
|
// Avoid unnecessary recursions of symMerge
|
||||||
|
// by direct insertion of data[a] into data[m:b]
|
||||||
|
// if data[a:m] only contains one element.
|
||||||
|
if m-a == 1 {
|
||||||
|
// Use binary search to find the lowest index i
|
||||||
|
// such that data[i] >= data[a] for m <= i < b.
|
||||||
|
// Exit the search loop with i == b in case no such index exists.
|
||||||
|
i := m
|
||||||
|
j := b
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1)
|
||||||
|
if data[h] < data[a] {
|
||||||
|
i = h + 1
|
||||||
|
} else {
|
||||||
|
j = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Swap values until data[a] reaches the position before i.
|
||||||
|
for k := a; k < i-1; k++ {
|
||||||
|
data[k], data[k+1] = data[k+1], data[k]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Avoid unnecessary recursions of symMerge
|
||||||
|
// by direct insertion of data[m] into data[a:m]
|
||||||
|
// if data[m:b] only contains one element.
|
||||||
|
if b-m == 1 {
|
||||||
|
// Use binary search to find the lowest index i
|
||||||
|
// such that data[i] > data[m] for a <= i < m.
|
||||||
|
// Exit the search loop with i == m in case no such index exists.
|
||||||
|
i := a
|
||||||
|
j := m
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1)
|
||||||
|
if !(data[m] < data[h]) {
|
||||||
|
i = h + 1
|
||||||
|
} else {
|
||||||
|
j = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Swap values until data[m] reaches the position i.
|
||||||
|
for k := m; k > i; k-- {
|
||||||
|
data[k], data[k-1] = data[k-1], data[k]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mid := int(uint(a+b) >> 1)
|
||||||
|
n := mid + m
|
||||||
|
var start, r int
|
||||||
|
if m > mid {
|
||||||
|
start = n - b
|
||||||
|
r = mid
|
||||||
|
} else {
|
||||||
|
start = a
|
||||||
|
r = m
|
||||||
|
}
|
||||||
|
p := n - 1
|
||||||
|
|
||||||
|
for start < r {
|
||||||
|
c := int(uint(start+r) >> 1)
|
||||||
|
if !(data[p-c] < data[c]) {
|
||||||
|
start = c + 1
|
||||||
|
} else {
|
||||||
|
r = c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
end := n - start
|
||||||
|
if start < m && m < end {
|
||||||
|
rotateOrdered(data, start, m, end)
|
||||||
|
}
|
||||||
|
if a < start && start < mid {
|
||||||
|
symMergeOrdered(data, a, start, mid)
|
||||||
|
}
|
||||||
|
if mid < end && end < b {
|
||||||
|
symMergeOrdered(data, mid, end, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rotateOrdered rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data:
|
||||||
|
// Data of the form 'x u v y' is changed to 'x v u y'.
|
||||||
|
// rotate performs at most b-a many calls to data.Swap,
|
||||||
|
// and it assumes non-degenerate arguments: a < m && m < b.
|
||||||
|
func rotateOrdered[E constraints.Ordered](data []E, a, m, b int) {
|
||||||
|
i := m - a
|
||||||
|
j := b - m
|
||||||
|
|
||||||
|
for i != j {
|
||||||
|
if i > j {
|
||||||
|
swapRangeOrdered(data, m-i, m, j)
|
||||||
|
i -= j
|
||||||
|
} else {
|
||||||
|
swapRangeOrdered(data, m-i, m+j-i, i)
|
||||||
|
j -= i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// i == j
|
||||||
|
swapRangeOrdered(data, m-i, m, i)
|
||||||
|
}
|
||||||
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/sys/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
22
vendor/golang.org/x/sys/PATENTS
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Additional IP Rights Grant (Patents)
|
||||||
|
|
||||||
|
"This implementation" means the copyrightable works distributed by
|
||||||
|
Google as part of the Go project.
|
||||||
|
|
||||||
|
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||||
|
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||||
|
patent license to make, have made, use, offer to sell, sell, import,
|
||||||
|
transfer and otherwise run, modify and propagate the contents of this
|
||||||
|
implementation of Go, where such license applies only to those patent
|
||||||
|
claims, both currently owned or controlled by Google and acquired in
|
||||||
|
the future, licensable by Google that are necessarily infringed by this
|
||||||
|
implementation of Go. This grant does not include claims that would be
|
||||||
|
infringed only as a consequence of further modification of this
|
||||||
|
implementation. If you or your agent or exclusive licensee institute or
|
||||||
|
order or agree to the institution of patent litigation against any
|
||||||
|
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||||
|
that this implementation of Go or any code incorporated within this
|
||||||
|
implementation of Go constitutes direct or contributory patent
|
||||||
|
infringement, or inducement of patent infringement, then any patent
|
||||||
|
rights granted to you under this License for this implementation of Go
|
||||||
|
shall terminate as of the date such litigation is filed.
|
||||||
30
vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go
generated
vendored
Normal file
30
vendor/golang.org/x/sys/internal/unsafeheader/unsafeheader.go
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// Copyright 2020 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package unsafeheader contains header declarations for the Go runtime's
|
||||||
|
// slice and string implementations.
|
||||||
|
//
|
||||||
|
// This package allows x/sys to use types equivalent to
|
||||||
|
// reflect.SliceHeader and reflect.StringHeader without introducing
|
||||||
|
// a dependency on the (relatively heavy) "reflect" package.
|
||||||
|
package unsafeheader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Slice is the runtime representation of a slice.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type Slice struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
Cap int
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is the runtime representation of a string.
|
||||||
|
// It cannot be used safely or portably and its representation may change in a later release.
|
||||||
|
type String struct {
|
||||||
|
Data unsafe.Pointer
|
||||||
|
Len int
|
||||||
|
}
|
||||||
2
vendor/golang.org/x/sys/unix/.gitignore
generated
vendored
Normal file
2
vendor/golang.org/x/sys/unix/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
_obj/
|
||||||
|
unix.test
|
||||||
184
vendor/golang.org/x/sys/unix/README.md
generated
vendored
Normal file
184
vendor/golang.org/x/sys/unix/README.md
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
# Building `sys/unix`
|
||||||
|
|
||||||
|
The sys/unix package provides access to the raw system call interface of the
|
||||||
|
underlying operating system. See: https://godoc.org/golang.org/x/sys/unix
|
||||||
|
|
||||||
|
Porting Go to a new architecture/OS combination or adding syscalls, types, or
|
||||||
|
constants to an existing architecture/OS pair requires some manual effort;
|
||||||
|
however, there are tools that automate much of the process.
|
||||||
|
|
||||||
|
## Build Systems
|
||||||
|
|
||||||
|
There are currently two ways we generate the necessary files. We are currently
|
||||||
|
migrating the build system to use containers so the builds are reproducible.
|
||||||
|
This is being done on an OS-by-OS basis. Please update this documentation as
|
||||||
|
components of the build system change.
|
||||||
|
|
||||||
|
### Old Build System (currently for `GOOS != "linux"`)
|
||||||
|
|
||||||
|
The old build system generates the Go files based on the C header files
|
||||||
|
present on your system. This means that files
|
||||||
|
for a given GOOS/GOARCH pair must be generated on a system with that OS and
|
||||||
|
architecture. This also means that the generated code can differ from system
|
||||||
|
to system, based on differences in the header files.
|
||||||
|
|
||||||
|
To avoid this, if you are using the old build system, only generate the Go
|
||||||
|
files on an installation with unmodified header files. It is also important to
|
||||||
|
keep track of which version of the OS the files were generated from (ex.
|
||||||
|
Darwin 14 vs Darwin 15). This makes it easier to track the progress of changes
|
||||||
|
and have each OS upgrade correspond to a single change.
|
||||||
|
|
||||||
|
To build the files for your current OS and architecture, make sure GOOS and
|
||||||
|
GOARCH are set correctly and run `mkall.sh`. This will generate the files for
|
||||||
|
your specific system. Running `mkall.sh -n` shows the commands that will be run.
|
||||||
|
|
||||||
|
Requirements: bash, go
|
||||||
|
|
||||||
|
### New Build System (currently for `GOOS == "linux"`)
|
||||||
|
|
||||||
|
The new build system uses a Docker container to generate the go files directly
|
||||||
|
from source checkouts of the kernel and various system libraries. This means
|
||||||
|
that on any platform that supports Docker, all the files using the new build
|
||||||
|
system can be generated at once, and generated files will not change based on
|
||||||
|
what the person running the scripts has installed on their computer.
|
||||||
|
|
||||||
|
The OS specific files for the new build system are located in the `${GOOS}`
|
||||||
|
directory, and the build is coordinated by the `${GOOS}/mkall.go` program. When
|
||||||
|
the kernel or system library updates, modify the Dockerfile at
|
||||||
|
`${GOOS}/Dockerfile` to checkout the new release of the source.
|
||||||
|
|
||||||
|
To build all the files under the new build system, you must be on an amd64/Linux
|
||||||
|
system and have your GOOS and GOARCH set accordingly. Running `mkall.sh` will
|
||||||
|
then generate all of the files for all of the GOOS/GOARCH pairs in the new build
|
||||||
|
system. Running `mkall.sh -n` shows the commands that will be run.
|
||||||
|
|
||||||
|
Requirements: bash, go, docker
|
||||||
|
|
||||||
|
## Component files
|
||||||
|
|
||||||
|
This section describes the various files used in the code generation process.
|
||||||
|
It also contains instructions on how to modify these files to add a new
|
||||||
|
architecture/OS or to add additional syscalls, types, or constants. Note that
|
||||||
|
if you are using the new build system, the scripts/programs cannot be called normally.
|
||||||
|
They must be called from within the docker container.
|
||||||
|
|
||||||
|
### asm files
|
||||||
|
|
||||||
|
The hand-written assembly file at `asm_${GOOS}_${GOARCH}.s` implements system
|
||||||
|
call dispatch. There are three entry points:
|
||||||
|
```
|
||||||
|
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||||
|
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr)
|
||||||
|
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr)
|
||||||
|
```
|
||||||
|
The first and second are the standard ones; they differ only in how many
|
||||||
|
arguments can be passed to the kernel. The third is for low-level use by the
|
||||||
|
ForkExec wrapper. Unlike the first two, it does not call into the scheduler to
|
||||||
|
let it know that a system call is running.
|
||||||
|
|
||||||
|
When porting Go to a new architecture/OS, this file must be implemented for
|
||||||
|
each GOOS/GOARCH pair.
|
||||||
|
|
||||||
|
### mksysnum
|
||||||
|
|
||||||
|
Mksysnum is a Go program located at `${GOOS}/mksysnum.go` (or `mksysnum_${GOOS}.go`
|
||||||
|
for the old system). This program takes in a list of header files containing the
|
||||||
|
syscall number declarations and parses them to produce the corresponding list of
|
||||||
|
Go numeric constants. See `zsysnum_${GOOS}_${GOARCH}.go` for the generated
|
||||||
|
constants.
|
||||||
|
|
||||||
|
Adding new syscall numbers is mostly done by running the build on a sufficiently
|
||||||
|
new installation of the target OS (or updating the source checkouts for the
|
||||||
|
new build system). However, depending on the OS, you may need to update the
|
||||||
|
parsing in mksysnum.
|
||||||
|
|
||||||
|
### mksyscall.go
|
||||||
|
|
||||||
|
The `syscall.go`, `syscall_${GOOS}.go`, `syscall_${GOOS}_${GOARCH}.go` are
|
||||||
|
hand-written Go files which implement system calls (for unix, the specific OS,
|
||||||
|
or the specific OS/Architecture pair respectively) that need special handling
|
||||||
|
and list `//sys` comments giving prototypes for ones that can be generated.
|
||||||
|
|
||||||
|
The mksyscall.go program takes the `//sys` and `//sysnb` comments and converts
|
||||||
|
them into syscalls. This requires the name of the prototype in the comment to
|
||||||
|
match a syscall number in the `zsysnum_${GOOS}_${GOARCH}.go` file. The function
|
||||||
|
prototype can be exported (capitalized) or not.
|
||||||
|
|
||||||
|
Adding a new syscall often just requires adding a new `//sys` function prototype
|
||||||
|
with the desired arguments and a capitalized name so it is exported. However, if
|
||||||
|
you want the interface to the syscall to be different, often one will make an
|
||||||
|
unexported `//sys` prototype, and then write a custom wrapper in
|
||||||
|
`syscall_${GOOS}.go`.
|
||||||
|
|
||||||
|
### types files
|
||||||
|
|
||||||
|
For each OS, there is a hand-written Go file at `${GOOS}/types.go` (or
|
||||||
|
`types_${GOOS}.go` on the old system). This file includes standard C headers and
|
||||||
|
creates Go type aliases to the corresponding C types. The file is then fed
|
||||||
|
through godef to get the Go compatible definitions. Finally, the generated code
|
||||||
|
is fed though mkpost.go to format the code correctly and remove any hidden or
|
||||||
|
private identifiers. This cleaned-up code is written to
|
||||||
|
`ztypes_${GOOS}_${GOARCH}.go`.
|
||||||
|
|
||||||
|
The hardest part about preparing this file is figuring out which headers to
|
||||||
|
include and which symbols need to be `#define`d to get the actual data
|
||||||
|
structures that pass through to the kernel system calls. Some C libraries
|
||||||
|
preset alternate versions for binary compatibility and translate them on the
|
||||||
|
way in and out of system calls, but there is almost always a `#define` that can
|
||||||
|
get the real ones.
|
||||||
|
See `types_darwin.go` and `linux/types.go` for examples.
|
||||||
|
|
||||||
|
To add a new type, add in the necessary include statement at the top of the
|
||||||
|
file (if it is not already there) and add in a type alias line. Note that if
|
||||||
|
your type is significantly different on different architectures, you may need
|
||||||
|
some `#if/#elif` macros in your include statements.
|
||||||
|
|
||||||
|
### mkerrors.sh
|
||||||
|
|
||||||
|
This script is used to generate the system's various constants. This doesn't
|
||||||
|
just include the error numbers and error strings, but also the signal numbers
|
||||||
|
and a wide variety of miscellaneous constants. The constants come from the list
|
||||||
|
of include files in the `includes_${uname}` variable. A regex then picks out
|
||||||
|
the desired `#define` statements, and generates the corresponding Go constants.
|
||||||
|
The error numbers and strings are generated from `#include <errno.h>`, and the
|
||||||
|
signal numbers and strings are generated from `#include <signal.h>`. All of
|
||||||
|
these constants are written to `zerrors_${GOOS}_${GOARCH}.go` via a C program,
|
||||||
|
`_errors.c`, which prints out all the constants.
|
||||||
|
|
||||||
|
To add a constant, add the header that includes it to the appropriate variable.
|
||||||
|
Then, edit the regex (if necessary) to match the desired constant. Avoid making
|
||||||
|
the regex too broad to avoid matching unintended constants.
|
||||||
|
|
||||||
|
### internal/mkmerge
|
||||||
|
|
||||||
|
This program is used to extract duplicate const, func, and type declarations
|
||||||
|
from the generated architecture-specific files listed below, and merge these
|
||||||
|
into a common file for each OS.
|
||||||
|
|
||||||
|
The merge is performed in the following steps:
|
||||||
|
1. Construct the set of common code that is idential in all architecture-specific files.
|
||||||
|
2. Write this common code to the merged file.
|
||||||
|
3. Remove the common code from all architecture-specific files.
|
||||||
|
|
||||||
|
|
||||||
|
## Generated files
|
||||||
|
|
||||||
|
### `zerrors_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing all of the system's generated error numbers, error strings,
|
||||||
|
signal numbers, and constants. Generated by `mkerrors.sh` (see above).
|
||||||
|
|
||||||
|
### `zsyscall_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing all the generated syscalls for a specific GOOS and GOARCH.
|
||||||
|
Generated by `mksyscall.go` (see above).
|
||||||
|
|
||||||
|
### `zsysnum_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A list of numeric constants for all the syscall number of the specific GOOS
|
||||||
|
and GOARCH. Generated by mksysnum (see above).
|
||||||
|
|
||||||
|
### `ztypes_${GOOS}_${GOARCH}.go`
|
||||||
|
|
||||||
|
A file containing Go types for passing into (or returning from) syscalls.
|
||||||
|
Generated by godefs and the types file (see above).
|
||||||
86
vendor/golang.org/x/sys/unix/affinity_linux.go
generated
vendored
Normal file
86
vendor/golang.org/x/sys/unix/affinity_linux.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// CPU affinity functions
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/bits"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const cpuSetSize = _CPU_SETSIZE / _NCPUBITS
|
||||||
|
|
||||||
|
// CPUSet represents a CPU affinity mask.
|
||||||
|
type CPUSet [cpuSetSize]cpuMask
|
||||||
|
|
||||||
|
func schedAffinity(trap uintptr, pid int, set *CPUSet) error {
|
||||||
|
_, _, e := RawSyscall(trap, uintptr(pid), uintptr(unsafe.Sizeof(*set)), uintptr(unsafe.Pointer(set)))
|
||||||
|
if e != 0 {
|
||||||
|
return errnoErr(e)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedGetaffinity gets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
func SchedGetaffinity(pid int, set *CPUSet) error {
|
||||||
|
return schedAffinity(SYS_SCHED_GETAFFINITY, pid, set)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SchedSetaffinity sets the CPU affinity mask of the thread specified by pid.
|
||||||
|
// If pid is 0 the calling thread is used.
|
||||||
|
func SchedSetaffinity(pid int, set *CPUSet) error {
|
||||||
|
return schedAffinity(SYS_SCHED_SETAFFINITY, pid, set)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero clears the set s, so that it contains no CPUs.
|
||||||
|
func (s *CPUSet) Zero() {
|
||||||
|
for i := range s {
|
||||||
|
s[i] = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuBitsIndex(cpu int) int {
|
||||||
|
return cpu / _NCPUBITS
|
||||||
|
}
|
||||||
|
|
||||||
|
func cpuBitsMask(cpu int) cpuMask {
|
||||||
|
return cpuMask(1 << (uint(cpu) % _NCPUBITS))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set adds cpu to the set s.
|
||||||
|
func (s *CPUSet) Set(cpu int) {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
s[i] |= cpuBitsMask(cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes cpu from the set s.
|
||||||
|
func (s *CPUSet) Clear(cpu int) {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
s[i] &^= cpuBitsMask(cpu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet reports whether cpu is in the set s.
|
||||||
|
func (s *CPUSet) IsSet(cpu int) bool {
|
||||||
|
i := cpuBitsIndex(cpu)
|
||||||
|
if i < len(s) {
|
||||||
|
return s[i]&cpuBitsMask(cpu) != 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of CPUs in the set s.
|
||||||
|
func (s *CPUSet) Count() int {
|
||||||
|
c := 0
|
||||||
|
for _, b := range s {
|
||||||
|
c += bits.OnesCount64(uint64(b))
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
15
vendor/golang.org/x/sys/unix/aliases.go
generated
vendored
Normal file
15
vendor/golang.org/x/sys/unix/aliases.go
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || zos) && go1.9
|
||||||
|
// +build aix darwin dragonfly freebsd linux netbsd openbsd solaris zos
|
||||||
|
// +build go1.9
|
||||||
|
|
||||||
|
package unix
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
type Signal = syscall.Signal
|
||||||
|
type Errno = syscall.Errno
|
||||||
|
type SysProcAttr = syscall.SysProcAttr
|
||||||
18
vendor/golang.org/x/sys/unix/asm_aix_ppc64.s
generated
vendored
Normal file
18
vendor/golang.org/x/sys/unix/asm_aix_ppc64.s
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// Copyright 2018 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build gc
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System calls for ppc64, AIX are implemented in runtime/syscall_aix.go
|
||||||
|
//
|
||||||
|
|
||||||
|
TEXT ·syscall6(SB),NOSPLIT,$0-88
|
||||||
|
JMP syscall·syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·rawSyscall6(SB),NOSPLIT,$0-88
|
||||||
|
JMP syscall·rawSyscall6(SB)
|
||||||
29
vendor/golang.org/x/sys/unix/asm_bsd_386.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_bsd_386.s
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for 386 BSD
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-52
|
||||||
|
JMP syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
29
vendor/golang.org/x/sys/unix/asm_bsd_amd64.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_bsd_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (darwin || dragonfly || freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build darwin dragonfly freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for AMD64 BSD
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||||
|
JMP syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
29
vendor/golang.org/x/sys/unix/asm_bsd_arm.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_bsd_arm.s
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for ARM BSD
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
|
B syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
|
B syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-52
|
||||||
|
B syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
B syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
B syscall·RawSyscall6(SB)
|
||||||
29
vendor/golang.org/x/sys/unix/asm_bsd_arm64.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_bsd_arm64.s
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build darwin freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for ARM64 BSD
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||||
|
JMP syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
31
vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s
generated
vendored
Normal file
31
vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build darwin freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System call support for ppc64, BSD
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||||
|
JMP syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
29
vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s
generated
vendored
Normal file
29
vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2021 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build (darwin || freebsd || netbsd || openbsd) && gc
|
||||||
|
// +build darwin freebsd netbsd openbsd
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// System call support for RISCV64 BSD
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||||
|
JMP syscall·Syscall9(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
66
vendor/golang.org/x/sys/unix/asm_linux_386.s
generated
vendored
Normal file
66
vendor/golang.org/x/sys/unix/asm_linux_386.s
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build gc
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System calls for 386, Linux
|
||||||
|
//
|
||||||
|
|
||||||
|
// See ../runtime/sys_linux_386.s for the reason why we always use int 0x80
|
||||||
|
// instead of the glibc-specific "CALL 0x10(GS)".
|
||||||
|
#define INVOKE_SYSCALL INT $0x80
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24
|
||||||
|
CALL runtime·entersyscall(SB)
|
||||||
|
MOVL trap+0(FP), AX // syscall entry
|
||||||
|
MOVL a1+4(FP), BX
|
||||||
|
MOVL a2+8(FP), CX
|
||||||
|
MOVL a3+12(FP), DX
|
||||||
|
MOVL $0, SI
|
||||||
|
MOVL $0, DI
|
||||||
|
INVOKE_SYSCALL
|
||||||
|
MOVL AX, r1+16(FP)
|
||||||
|
MOVL DX, r2+20(FP)
|
||||||
|
CALL runtime·exitsyscall(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24
|
||||||
|
MOVL trap+0(FP), AX // syscall entry
|
||||||
|
MOVL a1+4(FP), BX
|
||||||
|
MOVL a2+8(FP), CX
|
||||||
|
MOVL a3+12(FP), DX
|
||||||
|
MOVL $0, SI
|
||||||
|
MOVL $0, DI
|
||||||
|
INVOKE_SYSCALL
|
||||||
|
MOVL AX, r1+16(FP)
|
||||||
|
MOVL DX, r2+20(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·socketcall(SB),NOSPLIT,$0-36
|
||||||
|
JMP syscall·socketcall(SB)
|
||||||
|
|
||||||
|
TEXT ·rawsocketcall(SB),NOSPLIT,$0-36
|
||||||
|
JMP syscall·rawsocketcall(SB)
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-28
|
||||||
|
JMP syscall·seek(SB)
|
||||||
58
vendor/golang.org/x/sys/unix/asm_linux_amd64.s
generated
vendored
Normal file
58
vendor/golang.org/x/sys/unix/asm_linux_amd64.s
generated
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build gc
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System calls for AMD64, Linux
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
CALL runtime·entersyscall(SB)
|
||||||
|
MOVQ a1+8(FP), DI
|
||||||
|
MOVQ a2+16(FP), SI
|
||||||
|
MOVQ a3+24(FP), DX
|
||||||
|
MOVQ $0, R10
|
||||||
|
MOVQ $0, R8
|
||||||
|
MOVQ $0, R9
|
||||||
|
MOVQ trap+0(FP), AX // syscall entry
|
||||||
|
SYSCALL
|
||||||
|
MOVQ AX, r1+32(FP)
|
||||||
|
MOVQ DX, r2+40(FP)
|
||||||
|
CALL runtime·exitsyscall(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
MOVQ a1+8(FP), DI
|
||||||
|
MOVQ a2+16(FP), SI
|
||||||
|
MOVQ a3+24(FP), DX
|
||||||
|
MOVQ $0, R10
|
||||||
|
MOVQ $0, R8
|
||||||
|
MOVQ $0, R9
|
||||||
|
MOVQ trap+0(FP), AX // syscall entry
|
||||||
|
SYSCALL
|
||||||
|
MOVQ AX, r1+32(FP)
|
||||||
|
MOVQ DX, r2+40(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·gettimeofday(SB),NOSPLIT,$0-16
|
||||||
|
JMP syscall·gettimeofday(SB)
|
||||||
57
vendor/golang.org/x/sys/unix/asm_linux_arm.s
generated
vendored
Normal file
57
vendor/golang.org/x/sys/unix/asm_linux_arm.s
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build gc
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
//
|
||||||
|
// System calls for arm, Linux
|
||||||
|
//
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-28
|
||||||
|
B syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-40
|
||||||
|
B syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·SyscallNoError(SB),NOSPLIT,$0-24
|
||||||
|
BL runtime·entersyscall(SB)
|
||||||
|
MOVW trap+0(FP), R7
|
||||||
|
MOVW a1+4(FP), R0
|
||||||
|
MOVW a2+8(FP), R1
|
||||||
|
MOVW a3+12(FP), R2
|
||||||
|
MOVW $0, R3
|
||||||
|
MOVW $0, R4
|
||||||
|
MOVW $0, R5
|
||||||
|
SWI $0
|
||||||
|
MOVW R0, r1+16(FP)
|
||||||
|
MOVW $0, R0
|
||||||
|
MOVW R0, r2+20(FP)
|
||||||
|
BL runtime·exitsyscall(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-28
|
||||||
|
B syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40
|
||||||
|
B syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-24
|
||||||
|
MOVW trap+0(FP), R7 // syscall entry
|
||||||
|
MOVW a1+4(FP), R0
|
||||||
|
MOVW a2+8(FP), R1
|
||||||
|
MOVW a3+12(FP), R2
|
||||||
|
SWI $0
|
||||||
|
MOVW R0, r1+16(FP)
|
||||||
|
MOVW $0, R0
|
||||||
|
MOVW R0, r2+20(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·seek(SB),NOSPLIT,$0-28
|
||||||
|
B syscall·seek(SB)
|
||||||
53
vendor/golang.org/x/sys/unix/asm_linux_arm64.s
generated
vendored
Normal file
53
vendor/golang.org/x/sys/unix/asm_linux_arm64.s
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// Copyright 2015 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && arm64 && gc
|
||||||
|
// +build linux
|
||||||
|
// +build arm64
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
B syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
B syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
BL runtime·entersyscall(SB)
|
||||||
|
MOVD a1+8(FP), R0
|
||||||
|
MOVD a2+16(FP), R1
|
||||||
|
MOVD a3+24(FP), R2
|
||||||
|
MOVD $0, R3
|
||||||
|
MOVD $0, R4
|
||||||
|
MOVD $0, R5
|
||||||
|
MOVD trap+0(FP), R8 // syscall entry
|
||||||
|
SVC
|
||||||
|
MOVD R0, r1+32(FP) // r1
|
||||||
|
MOVD R1, r2+40(FP) // r2
|
||||||
|
BL runtime·exitsyscall(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
B syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
B syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
MOVD a1+8(FP), R0
|
||||||
|
MOVD a2+16(FP), R1
|
||||||
|
MOVD a3+24(FP), R2
|
||||||
|
MOVD $0, R3
|
||||||
|
MOVD $0, R4
|
||||||
|
MOVD $0, R5
|
||||||
|
MOVD trap+0(FP), R8 // syscall entry
|
||||||
|
SVC
|
||||||
|
MOVD R0, r1+32(FP)
|
||||||
|
MOVD R1, r2+40(FP)
|
||||||
|
RET
|
||||||
54
vendor/golang.org/x/sys/unix/asm_linux_loong64.s
generated
vendored
Normal file
54
vendor/golang.org/x/sys/unix/asm_linux_loong64.s
generated
vendored
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Copyright 2022 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
//go:build linux && loong64 && gc
|
||||||
|
// +build linux
|
||||||
|
// +build loong64
|
||||||
|
// +build gc
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
|
||||||
|
// Just jump to package syscall's implementation for all these functions.
|
||||||
|
// The runtime may know about them.
|
||||||
|
|
||||||
|
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·Syscall(SB)
|
||||||
|
|
||||||
|
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·Syscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·SyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
JAL runtime·entersyscall(SB)
|
||||||
|
MOVV a1+8(FP), R4
|
||||||
|
MOVV a2+16(FP), R5
|
||||||
|
MOVV a3+24(FP), R6
|
||||||
|
MOVV R0, R7
|
||||||
|
MOVV R0, R8
|
||||||
|
MOVV R0, R9
|
||||||
|
MOVV trap+0(FP), R11 // syscall entry
|
||||||
|
SYSCALL
|
||||||
|
MOVV R4, r1+32(FP)
|
||||||
|
MOVV R0, r2+40(FP) // r2 is not used. Always set to 0
|
||||||
|
JAL runtime·exitsyscall(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||||
|
JMP syscall·RawSyscall(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||||
|
JMP syscall·RawSyscall6(SB)
|
||||||
|
|
||||||
|
TEXT ·RawSyscallNoError(SB),NOSPLIT,$0-48
|
||||||
|
MOVV a1+8(FP), R4
|
||||||
|
MOVV a2+16(FP), R5
|
||||||
|
MOVV a3+24(FP), R6
|
||||||
|
MOVV R0, R7
|
||||||
|
MOVV R0, R8
|
||||||
|
MOVV R0, R9
|
||||||
|
MOVV trap+0(FP), R11 // syscall entry
|
||||||
|
SYSCALL
|
||||||
|
MOVV R4, r1+32(FP)
|
||||||
|
MOVV R0, r2+40(FP) // r2 is not used. Always set to 0
|
||||||
|
RET
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user