diff --git a/.gitignore b/.gitignore
index adac6a6..f38a348 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
# Binaries for programs and plugins
+supervisor-eventlistener
*.exe
*.dll
*.so
@@ -11,5 +12,7 @@
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
-.glide/
-.idear
+.*/
+vendor/**/*.yml
+vendor/**/.gitignore
+build/
diff --git a/LICENSE b/LICENSE
index 2316abe..12c3281 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2017 qiang.ou
+Copyright (c) 2017-2021 qiang.ou
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..464b535
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,58 @@
+project_name:=supervisor-eventlistener
+project_version:=1.2.3
+root_dir := $(abspath $(CURDIR))
+build_dir := $(root_dir)/build
+GOPATH := ${HOME}/go
+
+all: clean release
+
+
+clean:
+ rm -fr $(build_dir)
+
+
+build-bydocker:
+ sudo docker run -it --rm \
+ -v $(GOPATH)/:/root/go/ \
+ -v $(root_dir)/:/$(project_name) \
+ -w /$(project_name)/ \
+ golang:1.17.0-buster \
+ make build
+
+
+build:
+ GO111MODULE=on go build -o $(project_name) ./main.go
+
+
+release: clean build-bydocker
+ mkdir -p $(build_dir)/$(project_name)/
+ mv ./supervisor-eventlistener $(build_dir)/$(project_name)/
+ cp ./conf/config.toml $(build_dir)/$(project_name)/
+ cd $(build_dir) && tar -zcvf $(project_name)-$(project_version).tar.gz $(project_name)
+ @echo ...created $(build_dir)/$(project_name)-$(project_version).tar.gz
+ @echo ...done.
+
+
+log:
+ tmux new-session -d -s dev
+ tmux split-window -t "dev:0"
+ tmux split-window -t "dev:0.0" -h
+ tmux split-window -t "dev:0.2" -h
+ tmux send-keys -t "dev:0.0" "bash -c 'tail -f /tmp/supervisor-eventlistener.log'" Enter
+ tmux send-keys -t "dev:0.1" "bash -c 'sudo supervisorctl tail -f $(project_name)'" Enter
+ tmux set-option -g mouse on
+ tmux attach -t dev
+ tmux kill-session -t dev
+
+
+test-integration:
+ go build
+ sudo supervisorctl stop $(project_name)
+ sudo cp ./$(project_name) /usr/local/bin/
+ sudo cp ./tests/supervisor-app.ini /etc/supervisor.d/
+ sudo supervisorctl remove $(project_name)
+ sudo supervisorctl update $(project_name)
+ sudo supervisorctl tail -f $(project_name) stderr
+
+
+.PHONY: clean build build-bydocker release log test-integration
diff --git a/README.md b/README.md
index f98e7be..2fa6492 100644
--- a/README.md
+++ b/README.md
@@ -1,79 +1,57 @@
-# supervisor-event-listener
-Supervisor事件通知, 支持邮件, Slack, WebHook
-
-## 简介
-Supervisor是*nix环境下的进程管理工具, 可以把前台进程转换为守护进程, 当进程异常退出时自动重启.
-supervisor-event-listener监听进程异常退出事件, 并发送通知.
-
-## 下载
-[v1.0](https://github.com/ouqiang/supervisor-event-listener/releases)
-
-### 源码安装
-* `go get -u github.com/ouqiang/supervisor-event-listener`
-
-## Supervisor配置
-```ini
-[eventlistener:supervisor-event-listener]
-; 默认读取配置文件/etc/supervisor-event-listener.ini
-command=/path/to/supervisor-event-listener
-; 指定配置文件路径
-;command=/path/to/supervisor-event-listener -c /path/to/supervisor-event-listener.ini
-events=PROCESS_STATE_EXITED
-......
-```
-
-## 配置文件, 默认读取`/etc/supervisor-event-listener.ini`
-
-```ini
-[default]
-# 通知类型 mail,slack,webhook 只能选择一种
-notify_type = mail
-
-# 邮件服务器配置
-mail.server.user = test@163.com
-mail.server.password = 123456
-mail.server.host = smtp.163.com
-mail.server.port = 25
-
-# 邮件收件人配置, 多个收件人, 逗号分隔
-mail.user = hello@163.com
-
-# Slack配置
-slack.webhook_url = https://hooks.slack.com/services/xxxx/xxx/xxxx
-slack.channel = exception
-
-# WebHook通知URL配置
-webhook_url = http://my.webhook.com
-
-```
-
-## 通知内容
-邮件、Slack
-```shell
-Host: ip(hostname)
-Process: process-name
-PID: 6152
-EXITED FROM state: RUNNING
-```
-WebHook, Post请求, 字段含义查看Supervisor文档
-```json
-{
- "Header": {
- "Ver": "3.0",
- "Server": "supervisor",
- "Serial": 11,
- "Pool": "supervisor-listener",
- "PoolSerial": 11,
- "EventName": "PROCESS_STATE_EXITED",
- "Len": 84
- },
- "Payload": {
- "Ip": "ip(hostname)",
- "ProcessName": "process-name",
- "GroupName": "group-name",
- "FromState": "RUNNING",
- "Expected": 0,
- "Pid": 6371
- }
-}
-```
+# Introduction
+A `eventlistener` for supervisor, it may listen and redirect events to e-mail, webhook, slack and so on.
+More details http://supervisord.org/events.html
+
+# Features
+* support e-mail, webhook, slack
+* support bearychat, lack(feishu)
+
+# Usage
+
+1. setup supervisor-eventlistener
+ ```toml
+ [mail]
+ receivers = ["hello@163.com", "world@163.com"]
+ server_user = "test@163.com"
+ server_password = "123456"
+ server_host = "smtp.163.com"
+ server_port = 25
+
+ [slack]
+ url = "https://hooks.slack.com/services/xxxx/xxx/xxxx"
+ channel = "exception"
+ timeout = 6
+
+ [webhook]
+ url = "http://my.webhook.com"
+ timeout = 6
+
+ [bearychat]
+ url = "https://hook.bearychat.com/xxx/xxxx"
+ channel = "alert"
+ timeout = 6
+
+ [feishu]
+ url = "https://hook.feishu.com/xxx/xxxx?signKey=it_is_optional"
+ timeout = 6
+ ```
+
+2. setup supervisor
+ ```ini
+ [eventlistener:supervisor-event-listener]
+ command=/usr/local/bin/supervisor-event-listener -c /etc/supervisor-event-listener.toml
+ user=nobody
+ group=nobody
+ events=
+ PROCESS_STATE_EXITED,
+ PROCESS_STATE_FATAL,
+ PROCESS_STATE_STOPPED,
+ PROCESS_STATE_RUNNING
+ ```
+
+3. start supervisor-eventlistener
+ ```bash
+ supervisorctl start supervisor-event-listener
+ ```
+
+That's all.
\ No newline at end of file
diff --git a/build.sh b/build.sh
deleted file mode 100644
index 8062b5c..0000000
--- a/build.sh
+++ /dev/null
@@ -1,96 +0,0 @@
-#!/usr/bin/env bash
-
-# set -x -u
-# 构建应用, supervisor-event-listener.tar.gz
-# ./build.sh -p linux -a amd64
-# 参数含义
-# -p 指定平台(linux|darwin)
-# -a 指定体系架构(amd64|386), 默认amd64
-
-
-TEMP_DIR=`date +%s`-temp-`echo $RANDOM`
-
-# 目标平台 linux,darwin
-OS=''
-# 目标平台架构
-ARCH=''
-# 应用名称
-APP_NAME='supervisor-event-listener'
-# 可执行文件名
-EXEC_NAME=''
-# 压缩包名称
-COMPRESS_FILE=''
-
-
-# -p 平台 -a 架构
-while getopts "p:a:" OPT;
-do
- case $OPT in
- p) OS=$OPTARG
- ;;
- a) ARCH=$OPTARG
- ;;
- esac
-done
-
-if [[ -z $OS ]];then
- echo "平台不能为空"
- exit 1
-fi
-
-if [[ $OS && $OS != 'linux' && $OS != 'darwin' ]];then
- echo '平台错误,支持的平台 linux darmin(osx)'
- exit 1
-fi
-
-if [[ -z $ARCH ]];then
- ARCH='amd64'
-fi
-
-if [[ $ARCH != '386' && $ARCH != 'amd64' ]];then
- echo 'arch错误,仅支持 386 amd64'
- exit 1
-fi
-
-echo '开始编译'
-
-GOOS=$OS GOARCH=$ARCH go build -ldflags '-w'
-
-if [[ $? != 0 ]];then
- exit 1
-fi
-echo '编译完成'
-
-
-EXEC_NAME=${APP_NAME}
-COMPRESS_FILE=${APP_NAME}-${OS}-${ARCH}.tar.gz
-
-mkdir -p $TEMP_DIR/$APP_NAME
-if [[ $? != 0 ]]; then
- exit 1
-fi
-
-# 需要打包的文件
-PACKAGE_FILENAME=(supervisor-event-listener.ini ${EXEC_NAME})
-
-echo '复制文件到临时目录'
-# 复制文件到临时目录
-for i in ${PACKAGE_FILENAME[*]}
-do
- cp -r $i $TEMP_DIR/$APP_NAME
-done
-
-
-echo '压缩文件'
-# 压缩文件
-cd $TEMP_DIR
-
-tar czf $COMPRESS_FILE *
-mv $COMPRESS_FILE ../
-cd ../
-
-rm $EXEC_NAME
-rm -rf $TEMP_DIR
-
-echo '打包完成'
-echo '生成压缩文件--' $COMPRESS_FILE
\ No newline at end of file
diff --git a/conf/config.go b/conf/config.go
new file mode 100644
index 0000000..f5c13c1
--- /dev/null
+++ b/conf/config.go
@@ -0,0 +1,56 @@
+package conf
+
+import (
+ "fmt"
+ "sync/atomic"
+
+ validator "github.com/go-playground/validator/v10"
+)
+
+var (
+ // c = &Config{}
+ cfg atomic.Value
+)
+
+type Config struct {
+ DryRun *DryRun
+ WebHook *WebHook
+ Mail *Mail
+ Slack *Slack
+ BearyChat *BearyChat
+ Feishu *Feishu
+}
+
+func (c *Config) Validate() error {
+ if c == nil {
+ return fmt.Errorf("nil config")
+ }
+
+ if c.BearyChat != nil {
+ if c.BearyChat.URL == "" {
+ return fmt.Errorf("Invalid bearcychat config")
+ }
+ }
+ if c.Mail != nil {
+ if len(c.Mail.Receivers) <= 0 {
+ return fmt.Errorf("Invalid mail config")
+ }
+ }
+ if c.Slack != nil {
+ if c.Slack.URL == "" {
+ return fmt.Errorf("Invalid slack config")
+ }
+ }
+
+ if c.WebHook != nil {
+ if c.WebHook.URL == "" {
+ return fmt.Errorf("Invalid slack config")
+ }
+ }
+
+ validate := validator.New()
+ if err := validate.Struct(c); err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/conf/config.toml b/conf/config.toml
new file mode 100644
index 0000000..9e7ec34
--- /dev/null
+++ b/conf/config.toml
@@ -0,0 +1,24 @@
+[mail]
+receivers = ["hello@163.com", "world@163.com"]
+server_user = "test@163.com"
+server_password = "123456"
+server_host = "smtp.163.com"
+server_port = 25
+
+[slack]
+url = "https://hooks.slack.com/services/xxxx/xxx/xxxx"
+channel = "exception"
+timeout = 6
+
+[webhook]
+url = "http://my.webhook.com"
+timeout = 6
+
+[bearychat]
+url = "https://hook.bearychat.com/xxx/xxxx"
+channel = "alert"
+timeout = 6
+
+[feishu]
+url = "https://hook.feishu.com/xxx/xxxx?signKey=it_is_optional"
+timeout = 6
\ No newline at end of file
diff --git a/conf/init.go b/conf/init.go
new file mode 100644
index 0000000..e4882b1
--- /dev/null
+++ b/conf/init.go
@@ -0,0 +1,36 @@
+package conf
+
+import (
+ "io"
+ "os"
+
+ "github.com/pelletier/go-toml"
+)
+
+func Init(fpath string) error {
+ f, err := os.Open(fpath)
+ if err != nil {
+ return err
+ }
+ data, err := io.ReadAll(f)
+ if err != nil {
+ return err
+ }
+ c := Config{}
+ if err := toml.Unmarshal(data, &c); err != nil {
+ return err
+ }
+ if err := c.Validate(); err != nil {
+ return err
+ }
+ cfg.Store(&c)
+ return nil
+}
+
+func Reload(fpath string) error {
+ return Init(fpath)
+}
+
+func Get() *Config {
+ return cfg.Load().(*Config)
+}
diff --git a/conf/listeners.go b/conf/listeners.go
new file mode 100644
index 0000000..bdd92ef
--- /dev/null
+++ b/conf/listeners.go
@@ -0,0 +1,35 @@
+package conf
+
+type DryRun struct {
+ Text string `toml:"text"`
+}
+
+// 邮件服务器
+type Mail struct {
+ Receivers []string `toml:"receivers" validate:"required"`
+ ServerUser string `toml:"server_user" validate:"required"`
+ ServerPassword string `toml:"server_password" validate:"required"`
+ ServerHost string `toml:"server_host" validate:"required"`
+ ServerPort int `toml:"server_port" validate:"required"`
+}
+
+type WebHook struct {
+ URL string `toml:"url" validate:"required"`
+ Timeout int `toml:"timeout"`
+}
+
+type Slack struct {
+ URL string `toml:"url" validate:"required"`
+ Channel string `toml:"channel" validate:"required"`
+}
+
+type BearyChat struct {
+ URL string `toml:"url" validate:"required"`
+ Channel string `toml:"channel"`
+ Timeout int `toml:"timeout"`
+}
+
+type Feishu struct {
+ URL string `toml:"url" validate:"required"`
+ Timeout int `toml:"timeout"`
+}
diff --git a/config/config.go b/config/config.go
deleted file mode 100644
index 06475a8..0000000
--- a/config/config.go
+++ /dev/null
@@ -1,140 +0,0 @@
-package config
-
-import (
- "flag"
- "fmt"
- "github.com/ouqiang/supervisor-event-listener/utils"
- "gopkg.in/ini.v1"
- "os"
- "strings"
-)
-
-type Config struct {
- NotifyType string
- WebHook WebHook
- MailServer MailServer
- MailUser MailUser
- Slack Slack
-}
-
-type WebHook struct {
- Url string
-}
-
-type Slack struct {
- WebHookUrl string
- Channel string
-}
-
-// 邮件服务器
-type MailServer struct {
- User string
- Password string
- Host string
- Port int
-}
-
-// 接收邮件的用户
-type MailUser struct {
- Email []string
-}
-
-func ParseConfig() *Config {
- var configFile string
- flag.StringVar(&configFile, "c", "/etc/supervisor-event-listener.ini", "config file")
- flag.Parse()
- configFile = strings.TrimSpace(configFile)
- if configFile == "" {
- Exit("请指定配置文件路径")
- }
- file, err := ini.Load(configFile)
- if err != nil {
- Exit("读取配置文件失败#" + err.Error())
- }
- section := file.Section("default")
- notifyType := section.Key("notify_type").String()
- notifyType = strings.TrimSpace(notifyType)
- if !utils.InStringSlice([]string{"mail", "slack", "webhook"}, notifyType) {
- Exit("不支持的通知类型-" + notifyType)
- }
-
- config := &Config{}
- config.NotifyType = notifyType
- switch notifyType {
- case "mail":
- config.MailServer = parseMailServer(section)
- config.MailUser = parseMailUser(section)
- case "slack":
- config.Slack = parseSlack(section)
- case "webhook":
- config.WebHook = parseWebHook(section)
- }
-
- return config
-}
-
-func parseMailServer(section *ini.Section) MailServer {
- user := section.Key("mail.server.user").String()
- user = strings.TrimSpace(user)
- password := section.Key("mail.server.password").String()
- password = strings.TrimSpace(password)
- host := section.Key("mail.server.host").String()
- host = strings.TrimSpace(host)
- port, portErr := section.Key("mail.server.port").Int()
- if user == "" || password == "" || host == "" || portErr != nil {
- Exit("邮件服务器配置错误")
- }
-
- mailServer := MailServer{}
- mailServer.User = user
- mailServer.Password = password
- mailServer.Host = host
- mailServer.Port = port
-
- return mailServer
-}
-
-func parseMailUser(section *ini.Section) MailUser {
- user := section.Key("mail.user").String()
- user = strings.TrimSpace(user)
- if user == "" {
- Exit("邮件收件人配置错误")
- }
- mailUser := MailUser{}
- mailUser.Email = strings.Split(user, ",")
-
- return mailUser
-}
-
-func parseSlack(section *ini.Section) Slack {
- webHookUrl := section.Key("slack.webhook_url").String()
- webHookUrl = strings.TrimSpace(webHookUrl)
- channel := section.Key("slack.channel").String()
- channel = strings.TrimSpace(channel)
- if webHookUrl == "" || channel == "" {
- Exit("Slack配置错误")
- }
-
- slack := Slack{}
- slack.WebHookUrl = webHookUrl
- slack.Channel = channel
-
- return slack
-}
-
-func parseWebHook(section *ini.Section) WebHook {
- url := section.Key("webhook_url").String()
- url = strings.TrimSpace(url)
- if url == "" {
- Exit("WebHookUrl配置错误")
- }
- webHook := WebHook{}
- webHook.Url = url
-
- return webHook
-}
-
-func Exit(msg string) {
- fmt.Fprintln(os.Stderr, msg)
- os.Exit(1)
-}
diff --git a/event/errs.go b/event/errs.go
new file mode 100644
index 0000000..fc7f277
--- /dev/null
+++ b/event/errs.go
@@ -0,0 +1,11 @@
+package event
+
+import (
+ "errors"
+)
+
+var (
+ ErrParseHeader = errors.New("parse header failed")
+ ErrParsePayload = errors.New("parse payload failed")
+ ErrPayloadLength = errors.New("invalid payload length")
+)
diff --git a/event/event.go b/event/event.go
deleted file mode 100644
index bf07cc0..0000000
--- a/event/event.go
+++ /dev/null
@@ -1,110 +0,0 @@
-package event
-
-import (
- "errors"
- "fmt"
- "github.com/ouqiang/supervisor-event-listener/utils"
- "os"
- "strconv"
- "strings"
-)
-
-// Message 消息格式
-type Message struct {
- Header *Header
- Payload *Payload
-}
-
-func (msg *Message) String() string {
- return fmt.Sprintf("Host: %s\nProcess: %s\nPID: %d\nEXITED FROM state: %s", msg.Payload.Ip, msg.Payload.ProcessName, msg.Payload.Pid, msg.Payload.FromState)
-
-}
-
-// Header Supervisord触发事件时会先发送Header,根据Header中len字段去读取Payload
-type Header struct {
- Ver string
- Server string
- Serial int
- Pool string
- PoolSerial int
- EventName string // 事件名称
- Len int // Payload长度
-}
-
-// Payload
-type Payload struct {
- Ip string
- ProcessName string // 进程名称
- GroupName string // 进程组名称
- FromState string
- Expected int
- Pid int
-}
-
-// Fields
-type Fields map[string]string
-
-var (
- ErrParseHeader = errors.New("解析Header失败")
- ErrParsePayload = errors.New("解析Payload失败")
-)
-
-func ParseHeader(header string) (*Header, error) {
- h := &Header{}
- fields := parseFields(header)
- if len(fields) == 0 {
- return h, ErrParseHeader
- }
-
- h.Ver = fields["ver"]
- h.Server = fields["server"]
- h.Serial, _ = strconv.Atoi(fields["serial"])
- h.Pool = fields["pool"]
- h.PoolSerial, _ = strconv.Atoi(fields["poolserial"])
- h.EventName = fields["eventname"]
- h.Len, _ = strconv.Atoi(fields["len"])
-
- return h, nil
-}
-
-func ParsePayload(payload string) (*Payload, error) {
- p := &Payload{}
- fields := parseFields(payload)
- if len(fields) == 0 {
- return p, ErrParsePayload
- }
- hostname, _ := os.Hostname()
- p.Ip = fmt.Sprintf("%s(%s)", utils.GetLocalIp(), hostname)
- p.ProcessName = fields["processname"]
- p.GroupName = fields["groupname"]
- p.FromState = fields["from_state"]
- p.Expected, _ = strconv.Atoi(fields["expected"])
- p.Pid, _ = strconv.Atoi(fields["pid"])
-
- return p, nil
-}
-
-func parseFields(data string) Fields {
- fields := make(Fields)
- data = strings.TrimSpace(data)
- if data == "" {
- return fields
- }
- // 格式如下
- // ver:3.0 server:supervisor serial:5
- slice := strings.Split(data, " ")
- if len(slice) == 0 {
- return fields
- }
- for _, item := range slice {
- group := strings.Split(item, ":")
- if len(group) < 2 {
- continue
- }
- key := strings.TrimSpace(group[0])
- value := strings.TrimSpace(group[1])
- fields[key] = value
- }
-
- return fields
-}
diff --git a/event/header.go b/event/header.go
new file mode 100644
index 0000000..2981363
--- /dev/null
+++ b/event/header.go
@@ -0,0 +1,31 @@
+package event
+
+import "strconv"
+
+type Header struct {
+ Ver string
+ Server string
+ Serial int
+ Pool string
+ PoolSerial int
+ EventName string // 事件名称
+ Len int // Payload长度
+}
+
+func ParseHeader(header string) (*Header, error) {
+ h := &Header{}
+ fields := parseFields(header)
+ if len(fields) == 0 {
+ return h, ErrParseHeader
+ }
+
+ h.Ver = fields["ver"]
+ h.Server = fields["server"]
+ h.Serial, _ = strconv.Atoi(fields["serial"])
+ h.Pool = fields["pool"]
+ h.PoolSerial, _ = strconv.Atoi(fields["poolserial"])
+ h.EventName = fields["eventname"]
+ h.Len, _ = strconv.Atoi(fields["len"])
+
+ return h, nil
+}
diff --git a/event/message.go b/event/message.go
new file mode 100644
index 0000000..9da905e
--- /dev/null
+++ b/event/message.go
@@ -0,0 +1,113 @@
+package event
+
+import (
+ "bufio"
+ "encoding/json"
+ "fmt"
+ "time"
+
+ "github.com/ouqiang/supervisor-event-listener/utils/errlog"
+ "github.com/pkg/errors"
+)
+
+type Message struct {
+ TS time.Time
+ Header *Header
+ Payload *Payload
+}
+
+func NewMessage(h *Header, p *Payload) Message {
+ return Message{
+ TS: time.Now(),
+ Header: h,
+ Payload: p,
+ }
+}
+
+func ReadMessage(reader *bufio.Reader) (Message, error) {
+ header, err := readHeader(reader)
+ if err != nil {
+ errlog.Error("header:%+v err:%+v", header, err)
+ return Message{}, err
+ }
+ payload, err := readPayload(reader, header.Len)
+ if err != nil {
+ errlog.Error("payload:%+v err:%+v", payload, err)
+ return Message{}, err
+ }
+ return NewMessage(header, payload), nil
+}
+
+// 读取header
+func readHeader(reader *bufio.Reader) (*Header, error) {
+ // 读取Header
+ data, err := reader.ReadString('\n')
+ if err != nil {
+ return nil, err
+ }
+ // 解析Header
+ header, err := ParseHeader(data)
+ if err != nil {
+ return nil, err
+ }
+ return header, nil
+}
+
+// 读取payload
+func readPayload(reader *bufio.Reader, payloadLen int) (*Payload, error) {
+ // 读取payload
+ buf := make([]byte, payloadLen)
+ length, err := reader.Read(buf)
+ if err != nil {
+ return nil, err
+ }
+ if payloadLen != length {
+ err := ErrPayloadLength
+ err = errors.Wrapf(err, " payloadLen:%d != length:%d", payloadLen, length)
+ return nil, err
+ }
+ // 解析payload
+ payload, err := ParsePayload(string(buf))
+ if err != nil {
+ return nil, err
+ }
+ return payload, nil
+}
+
+func (msg *Message) String() string {
+ tmpl := `Proc: %s
+Host: %s
+PID: %d State: %s
+Date: %s`
+ return fmt.Sprintf(tmpl,
+ msg.Payload.ProcessName,
+ msg.Payload.IP,
+ msg.Payload.PID, msg.Payload.FromState,
+ msg.TS.Format(time.RFC3339),
+ )
+}
+
+func (msg *Message) ToJson(indent ...int) string {
+ realIndent := 0
+ if len(indent) > 0 {
+ realIndent = indent[0]
+ }
+ t := ""
+ switch realIndent {
+ case 0:
+ case 1:
+ t = " "
+ case 2:
+ t = " "
+ case 3:
+ t = " "
+ case 4:
+ t = " "
+ default:
+ t = " "
+ }
+ _bytes, _ := json.MarshalIndent(msg, "", t)
+ return string(_bytes)
+}
+
+// Header Supervisord触发事件时会先发送Header,根据Header中len字段去读取Payload
diff --git a/event/payload.go b/event/payload.go
new file mode 100644
index 0000000..75cbe39
--- /dev/null
+++ b/event/payload.go
@@ -0,0 +1,62 @@
+package event
+
+import (
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+
+ "github.com/ouqiang/supervisor-event-listener/utils"
+)
+
+type Payload struct {
+ IP string
+ ProcessName string // 进程名称
+ GroupName string // 进程组名称
+ FromState string
+ Expected int
+ PID int
+}
+
+type Fields map[string]string
+
+func ParsePayload(payload string) (*Payload, error) {
+ p := &Payload{}
+ fields := parseFields(payload)
+ if len(fields) == 0 {
+ return nil, ErrParsePayload
+ }
+ hostname, _ := os.Hostname()
+ p.IP = fmt.Sprintf("%s(%s)", utils.GetLocalIp(), hostname)
+ p.ProcessName = fields["processname"]
+ p.GroupName = fields["groupname"]
+ p.FromState = fields["from_state"]
+ p.Expected, _ = strconv.Atoi(fields["expected"])
+ p.PID, _ = strconv.Atoi(fields["pid"])
+ return p, nil
+}
+
+func parseFields(data string) Fields {
+ fields := make(Fields)
+ data = strings.TrimSpace(data)
+ if data == "" {
+ return fields
+ }
+ // 格式如下
+ // ver:3.0 server:supervisor serial:5
+ slice := strings.Split(data, " ")
+ if len(slice) == 0 {
+ return fields
+ }
+ for _, item := range slice {
+ group := strings.Split(item, ":")
+ if len(group) < 2 {
+ continue
+ }
+ key := strings.TrimSpace(group[0])
+ value := strings.TrimSpace(group[1])
+ fields[key] = value
+ }
+
+ return fields
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..7fb2136
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,21 @@
+module github.com/ouqiang/supervisor-event-listener
+
+go 1.17
+
+require (
+ github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
+ github.com/go-playground/validator/v10 v10.9.0
+ github.com/pelletier/go-toml v1.9.4
+ github.com/pkg/errors v0.9.1
+)
+
+require (
+ github.com/go-playground/locales v0.14.0 // indirect
+ github.com/go-playground/universal-translator v0.18.0 // indirect
+ github.com/leodido/go-urn v1.2.1 // indirect
+ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 // indirect
+ golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 // indirect
+ golang.org/x/text v0.3.6 // indirect
+ gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
+ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..413d5a5
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,58 @@
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+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/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df h1:Bao6dhmbTA1KFVxmJ6nBoMuOJit2yjEgLJpIMYpop0E=
+github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df/go.mod h1:GJr+FCSXshIwgHBtLglIg9M2l2kQSi6QjVAngtzI08Y=
+github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
+github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
+github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
+github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
+github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
+github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
+github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
+github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
+github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
+github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+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.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU=
+golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
+gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
+gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/listener/listener.go b/listener/listener.go
index 39b9bce..8944431 100644
--- a/listener/listener.go
+++ b/listener/listener.go
@@ -4,23 +4,34 @@ import (
"bufio"
"errors"
"fmt"
- "github.com/ouqiang/supervisor-event-listener/event"
- "github.com/ouqiang/supervisor-event-listener/listener/notify"
"log"
"os"
+ "time"
+
+ "github.com/ouqiang/supervisor-event-listener/event"
+ "github.com/ouqiang/supervisor-event-listener/notify"
)
var (
+ // ErrPayloadLength ...
ErrPayloadLength = errors.New("Header中len长度与实际读取长度不一致")
)
+// Start ...
func Start() {
- defer func() {
- if err := recover(); err != nil {
- log.Print("panic", err)
- }
- }()
- listen()
+ go run()
+}
+
+func run() {
+ for {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Print("panic", err)
+ }
+ }()
+ listen()
+ time.Sleep(time.Second)
+ }
}
// 监听事件, 从标准输入获取事件内容
@@ -28,60 +39,16 @@ func listen() {
reader := bufio.NewReader(os.Stdin)
for {
ready()
- header, err := readHeader(reader)
- if err != nil {
- failure(err)
- continue
- }
- payload, err := readPayload(reader, header.Len)
+ msg, err := event.ReadMessage(reader)
if err != nil {
failure(err)
continue
}
- // 只处理进程异常退出事件
- if header.EventName == "PROCESS_STATE_EXITED" {
- notify.Push(header, payload)
- }
success()
+ notify.Push(&msg)
}
}
-// 读取header
-func readHeader(reader *bufio.Reader) (*event.Header, error) {
- // 读取Header
- data, err := reader.ReadString('\n')
- if err != nil {
- return nil, err
- }
- // 解析Header
- header, err := event.ParseHeader(data)
- if err != nil {
- return nil, err
- }
-
- return header, nil
-}
-
-// 读取payload
-func readPayload(reader *bufio.Reader, payloadLen int) (*event.Payload, error) {
- // 读取payload
- buf := make([]byte, payloadLen)
- length, err := reader.Read(buf)
- if err != nil {
- return nil, err
- }
- if payloadLen != length {
- return nil, ErrPayloadLength
- }
- // 解析payload
- payload, err := event.ParsePayload(string(buf))
- if err != nil {
- return nil, err
- }
-
- return payload, nil
-}
-
func ready() {
fmt.Fprint(os.Stdout, "READY\n")
}
@@ -92,5 +59,5 @@ func success() {
func failure(err error) {
fmt.Fprintln(os.Stderr, err)
- fmt.Fprint(os.Stdout, "Result 2\nFAIL")
+ fmt.Fprint(os.Stdout, "RESULT 2\nFAIL")
}
diff --git a/listener/notify/mail.go b/listener/notify/mail.go
deleted file mode 100644
index dd3907a..0000000
--- a/listener/notify/mail.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package notify
-
-import (
- "errors"
- "fmt"
- "github.com/go-gomail/gomail"
- "github.com/ouqiang/supervisor-event-listener/event"
- "strings"
-)
-
-type Mail struct{}
-
-func (mail *Mail) Send(message event.Message) error {
- body := message.String()
- body = strings.Replace(body, "\n", "
", -1)
- gomailMessage := gomail.NewMessage()
- gomailMessage.SetHeader("From", Conf.MailServer.User)
- gomailMessage.SetHeader("To", Conf.MailUser.Email...)
- gomailMessage.SetHeader("Subject", "Supervisor事件通知")
- gomailMessage.SetBody("text/html", body)
- mailer := gomail.NewPlainDialer(
- Conf.MailServer.Host,
- Conf.MailServer.Port,
- Conf.MailServer.User,
- Conf.MailServer.Password,
- )
- err := mailer.DialAndSend(gomailMessage)
- if err == nil {
- return nil
- }
- errorMessage := fmt.Sprintf("邮件发送失败#%s", err.Error())
-
- return errors.New(errorMessage)
-}
diff --git a/listener/notify/notify.go b/listener/notify/notify.go
deleted file mode 100644
index 7c8a654..0000000
--- a/listener/notify/notify.go
+++ /dev/null
@@ -1,65 +0,0 @@
-package notify
-
-import (
- "github.com/ouqiang/supervisor-event-listener/config"
- "github.com/ouqiang/supervisor-event-listener/event"
-
- "fmt"
- "os"
- "time"
-)
-
-var (
- Conf *config.Config
- queue chan event.Message
-)
-
-func init() {
- Conf = config.ParseConfig()
- queue = make(chan event.Message, 10)
- go start()
-}
-
-type Notifiable interface {
- Send(event.Message) error
-}
-
-func Push(header *event.Header, payload *event.Payload) {
- queue <- event.Message{header, payload}
-}
-
-func start() {
- var message event.Message
- var notifyHandler Notifiable
- for {
- message = <-queue
- switch Conf.NotifyType {
- case "mail":
- notifyHandler = &Mail{}
- case "slack":
- notifyHandler = &Slack{}
- case "webhook":
- notifyHandler = &WebHook{}
- }
- if notifyHandler == nil {
- continue
- }
- go send(notifyHandler, message)
- time.Sleep(1 * time.Second)
- }
-}
-
-func send(notifyHandler Notifiable, message event.Message) {
- // 最多重试3次
- tryTimes := 3
- i := 0
- for i < tryTimes {
- err := notifyHandler.Send(message)
- if err == nil {
- break
- }
- fmt.Fprintln(os.Stderr, err)
- time.Sleep(30 * time.Second)
- i++
- }
-}
diff --git a/listener/notify/webhook.go b/listener/notify/webhook.go
deleted file mode 100644
index 8f04ace..0000000
--- a/listener/notify/webhook.go
+++ /dev/null
@@ -1,26 +0,0 @@
-package notify
-
-import (
- "encoding/json"
- "errors"
- "fmt"
- "github.com/ouqiang/supervisor-event-listener/event"
- "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
-)
-
-type WebHook struct{}
-
-func (hook *WebHook) Send(message event.Message) error {
- encodeMessage, err := json.Marshal(message)
- if err != nil {
- return err
- }
- timeout := 60
- response := httpclient.PostJson(Conf.WebHook.Url, string(encodeMessage), timeout)
-
- if response.StatusCode == 200 {
- return nil
- }
- errorMessage := fmt.Sprintf("webhook执行失败#HTTP状态码-%d#HTTP-BODY-%s", response.StatusCode, response.Body)
- return errors.New(errorMessage)
-}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..9cee757
--- /dev/null
+++ b/main.go
@@ -0,0 +1,61 @@
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+ "os/signal"
+ "syscall"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/listener"
+ "github.com/ouqiang/supervisor-event-listener/notify"
+ "github.com/ouqiang/supervisor-event-listener/utils/errlog"
+)
+
+var (
+ chanSig = make(chan os.Signal, 128)
+)
+
+func main() {
+ defer func() {
+ if err := recover(); err != nil {
+ log.Print("panic", err)
+ os.Exit(127)
+ }
+ }()
+
+ var configFile = flag.String("c", "/etc/supervisor-event-listener.ini", "config file")
+ var dryRun = flag.Bool("dryRun", false, "dry run, lint config file")
+ flag.Parse()
+
+ if err := conf.Init(*configFile); err != nil {
+ errlog.Error("config init failed. err: %v", err)
+ os.Exit(127)
+ return
+ }
+ if err := notify.Init(conf.Get()); err != nil {
+ errlog.Error("notify init failed. err: %v", err)
+ os.Exit(127)
+ return
+ }
+ if *dryRun {
+ return
+ }
+ notify.Start()
+ listener.Start()
+
+ signal.Notify(chanSig, syscall.SIGHUP)
+ for sig := range chanSig {
+ switch sig {
+ case syscall.SIGHUP:
+ if err := conf.Reload(*configFile); err != nil {
+ errlog.Error("config init failed. err: %v", err)
+ continue
+ }
+ notify.Reload(conf.Get())
+ default:
+ errlog.Info("unexpected signal %v", sig)
+ }
+ }
+}
diff --git a/notify/bearychat.go b/notify/bearychat.go
new file mode 100644
index 0000000..d33fe8a
--- /dev/null
+++ b/notify/bearychat.go
@@ -0,0 +1,41 @@
+package notify
+
+import (
+ "encoding/json"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/event"
+ "github.com/ouqiang/supervisor-event-listener/utils/errlog"
+ "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
+)
+
+type BearyChat conf.BearyChat
+
+func (this *BearyChat) Send(msg *event.Message) error {
+ url := this.URL
+ channel := this.Channel
+ timeout := this.Timeout
+
+ params := map[string]interface{}{
+ "text": this.format(msg),
+ }
+ if channel != "" {
+ params["channel"] = channel
+ }
+
+ body, err := json.Marshal(params)
+ if err != nil {
+ return err
+ }
+ resp := httpclient.PostJson(url, string(body), timeout)
+ if !resp.IsOK() {
+ errlog.Error("params: %v err: %v", params, resp.Error())
+ return resp
+ }
+ return nil
+}
+
+func (this *BearyChat) format(msg *event.Message) string {
+ // return msg.ToJson(4)
+ return msg.String()
+}
diff --git a/notify/feishu.go b/notify/feishu.go
new file mode 100644
index 0000000..1d03162
--- /dev/null
+++ b/notify/feishu.go
@@ -0,0 +1,80 @@
+package notify
+
+import (
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/event"
+ "github.com/ouqiang/supervisor-event-listener/utils/errlog"
+ "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
+)
+
+type Feishu conf.Feishu
+
+func (this *Feishu) Send(msg *event.Message) error {
+ url := this.URL
+ timeout := this.Timeout
+ return send2feishu(url, msg.String(), timeout)
+}
+
+func send2feishu(_url string, text string, timeout int) error {
+ parse := func(_url string) (string, string) {
+ tmpArr := strings.Split(_url, "?")
+ if len(tmpArr) == 1 {
+ return _url, ""
+ } else if len(tmpArr) == 2 {
+ _url = tmpArr[0]
+ q, err := url.ParseQuery(tmpArr[1])
+ if err != nil {
+ panic(err)
+ }
+ return _url, q.Get("signKey")
+ } else {
+ panic(fmt.Errorf("invalid url: %s", _url))
+ }
+ }
+
+ sign := func(secret string, timestamp int64) string {
+ //timestamp + key do sha256, then base64 encode
+ stringToSign := fmt.Sprintf("%v\n%s", timestamp, secret)
+
+ var data []byte
+ h := hmac.New(sha256.New, []byte(stringToSign))
+ _, err := h.Write(data)
+ if err != nil {
+ panic(err)
+ }
+ return base64.StdEncoding.EncodeToString(h.Sum(nil))
+ }
+
+ params := map[string]interface{}{
+ "msg_type": "text",
+ "content": map[string]interface{}{
+ "text": text,
+ },
+ }
+
+ _url, signKey := parse(_url)
+ if signKey != "" {
+ ts := time.Now().Unix()
+ params["timestamp"] = ts
+ params["sign"] = sign(signKey, ts)
+ }
+ body, err := json.Marshal(params)
+ if err != nil {
+ return err
+ }
+ resp := httpclient.PostJson(_url, string(body), timeout)
+ if !resp.IsOK() {
+ errlog.Error("params: %v err: %v", params, resp.Error())
+ return resp
+ }
+ return nil
+}
diff --git a/notify/init.go b/notify/init.go
new file mode 100644
index 0000000..db473c8
--- /dev/null
+++ b/notify/init.go
@@ -0,0 +1,116 @@
+package notify
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/event"
+ "github.com/ouqiang/supervisor-event-listener/utils/errlog"
+)
+
+var (
+ chanMsg = make(chan *event.Message, 2048)
+ chanReload = make(chan *conf.Config, 16)
+ // notifiables map[string]Notifiable
+ notifiables atomic.Value
+)
+
+func Init(conf *conf.Config) error {
+ if conf == nil {
+ return fmt.Errorf("nil config")
+ }
+
+ m := map[string]Notifiable{}
+ if conf.WebHook != nil {
+ m["webhook"] = (*WebHook)(conf.WebHook)
+ }
+ if conf.Mail != nil {
+ m["mail"] = (*Mail)(conf.Mail)
+ }
+ if conf.Slack != nil {
+ m["slack"] = (*Slack)(conf.Slack)
+ }
+ if conf.BearyChat != nil {
+ m["bearychat"] = (*BearyChat)(conf.BearyChat)
+ }
+ if conf.Feishu != nil {
+ m["feishu"] = (*Feishu)(conf.Feishu)
+ }
+ notifiables.Store(m)
+ return nil
+}
+
+func Reload(conf *conf.Config) {
+ chanReload <- conf
+}
+
+func reload(conf *conf.Config) error {
+ if err := Init(conf); err != nil {
+ errlog.Info("loading config failed. %v", err)
+ return err
+ }
+ names := []string{}
+ for name := range Get() {
+ names = append(names, name)
+ }
+ errlog.Info("reloaded config: %s", strings.Join(names, " "))
+ return nil
+}
+
+func Push(msg *event.Message) {
+ chanMsg <- msg
+}
+
+func Start() {
+ go run()
+}
+
+func run() {
+ for {
+ select {
+ case msg := <-chanMsg:
+ errlog.Info("msg=%s", msg.ToJson(2))
+ handleMessage(msg)
+ time.Sleep(50 * time.Millisecond)
+ case conf := <-chanReload:
+ _ = reload(conf)
+ default:
+ time.Sleep(50 * time.Millisecond)
+ }
+ }
+}
+
+func handleMessage(msg *event.Message) error {
+ errlog.Debug("message: %+v\n", msg)
+ m := Get()
+ for name, notifyHandler := range m {
+ if notifyHandler == nil {
+ errlog.Error("nil notify handler %s", name)
+ }
+ go send(notifyHandler, msg)
+ }
+ return nil
+}
+
+func send(notifyHandler Notifiable, msg *event.Message) {
+ // 最多重试3次
+ tryTimes := 3
+ i := 0
+ for i < tryTimes {
+ err := notifyHandler.Send(msg)
+ if err == nil {
+ break
+ }
+ fmt.Fprintln(os.Stderr, err)
+ time.Sleep(30 * time.Second)
+ i++
+ }
+}
+
+func Get() map[string]Notifiable {
+ return notifiables.Load().(map[string]Notifiable)
+}
diff --git a/notify/mail.go b/notify/mail.go
new file mode 100644
index 0000000..e7f3f43
--- /dev/null
+++ b/notify/mail.go
@@ -0,0 +1,33 @@
+package notify
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/go-gomail/gomail"
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/event"
+)
+
+type Mail conf.Mail
+
+func (mail *Mail) Send(msg *event.Message) error {
+ body := msg.String()
+ body = strings.Replace(body, "\n", "
", -1)
+ m := gomail.NewMessage()
+ m.SetHeader("From", mail.ServerUser)
+ m.SetHeader("To", mail.Receivers...)
+ m.SetHeader("Subject", "Supervisor事件通知")
+ m.SetBody("text/html", body)
+ mailer := gomail.NewPlainDialer(
+ mail.ServerHost,
+ mail.ServerPort,
+ mail.ServerUser,
+ mail.ServerPassword,
+ )
+ err := mailer.DialAndSend(m)
+ if err == nil {
+ return nil
+ }
+ return fmt.Errorf("邮件发送失败#%v", err)
+}
diff --git a/notify/notifiable.go b/notify/notifiable.go
new file mode 100644
index 0000000..1feff16
--- /dev/null
+++ b/notify/notifiable.go
@@ -0,0 +1,7 @@
+package notify
+
+import "github.com/ouqiang/supervisor-event-listener/event"
+
+type Notifiable interface {
+ Send(*event.Message) error
+}
diff --git a/listener/notify/slack.go b/notify/slack.go
similarity index 53%
rename from listener/notify/slack.go
rename to notify/slack.go
index 6076faf..a253445 100644
--- a/listener/notify/slack.go
+++ b/notify/slack.go
@@ -1,31 +1,28 @@
package notify
import (
- "errors"
"fmt"
- "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
"github.com/ouqiang/supervisor-event-listener/event"
"github.com/ouqiang/supervisor-event-listener/utils"
+ "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
)
-type Slack struct{}
+type Slack conf.Slack
-func (slack *Slack) Send(message event.Message) error {
- body := slack.format(message.String(), Conf.Slack.Channel)
+func (s *Slack) Send(msg *event.Message) error {
+ body := s.format(msg.String(), s.Channel)
timeout := 120
- response := httpclient.PostJson(Conf.Slack.WebHookUrl, body, timeout)
- if response.StatusCode == 200 {
- return nil
+ resp := httpclient.PostJson(s.URL, body, timeout)
+ if !resp.IsOK() {
+ return resp
}
-
- errorMessage := fmt.Sprintf("发送Slack消息失败#HTTP状态码-%d#HTTP-Body-%s",
- response.StatusCode, response.Body)
-
- return errors.New(errorMessage)
+ return nil
}
// 格式化消息内容
-func (slack *Slack) format(content string, channel string) string {
+func (s *Slack) format(content string, channel string) string {
content = utils.EscapeJson(content)
specialChars := []string{"&", "<", ">"}
replaceChars := []string{"&", "<", ">"}
diff --git a/notify/webhook.go b/notify/webhook.go
new file mode 100644
index 0000000..c73095f
--- /dev/null
+++ b/notify/webhook.go
@@ -0,0 +1,25 @@
+package notify
+
+import (
+ "encoding/json"
+
+ "github.com/ouqiang/supervisor-event-listener/conf"
+ "github.com/ouqiang/supervisor-event-listener/event"
+ "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
+)
+
+type WebHook conf.WebHook
+
+func (hook *WebHook) Send(msg *event.Message) error {
+ url := hook.URL
+ timeout := hook.Timeout
+ data, err := json.Marshal(msg)
+ if err != nil {
+ return err
+ }
+ resp := httpclient.PostJson(url, string(data), timeout)
+ if !resp.IsOK() {
+ return resp
+ }
+ return nil
+}
diff --git a/supervisor-event-listener.go b/supervisor-event-listener.go
deleted file mode 100644
index 0982b21..0000000
--- a/supervisor-event-listener.go
+++ /dev/null
@@ -1,11 +0,0 @@
-package main
-
-import (
- "github.com/ouqiang/supervisor-event-listener/listener"
-)
-
-func main() {
- for {
- listener.Start()
- }
-}
diff --git a/supervisor-event-listener.ini b/supervisor-event-listener.ini
deleted file mode 100644
index dbe00ed..0000000
--- a/supervisor-event-listener.ini
+++ /dev/null
@@ -1,19 +0,0 @@
-[default]
-# 通知类型 mail,slack,webhook 只能选择一种
-notify_type = mail
-
-# 邮件服务器配置
-mail.server.user = test@163.com
-mail.server.password = 123456
-mail.server.host = smtp.163.com
-mail.server.port = 25
-
-# 邮件收件人配置, 多个收件人, 逗号分隔
-mail.user = hello@163.com
-
-# Slack配置
-slack.webhook_url = https://hooks.slack.com/services/xxxx/xxx/xxxx
-slack.channel = exception
-
-# WebHook通知URL配置
-webhook_url = http://my.webhook.com
\ No newline at end of file
diff --git a/tests/supervisor-app.ini b/tests/supervisor-app.ini
new file mode 100644
index 0000000..7e6f2e2
--- /dev/null
+++ b/tests/supervisor-app.ini
@@ -0,0 +1,13 @@
+[eventlistener:supervisor-event-listener]
+command=/usr/local/bin/supervisor-event-listener
+ -c /etc/supervisor-event-listener.toml
+events=
+ PROCESS_STATE_EXITED,
+ PROCESS_STATE_FATAL,
+ PROCESS_STATE_STOPPED,
+ PROCESS_STATE_RUNNING
+user=nobody
+group=nobody
+
+[program:sleep-then-exit]
+command=sleep 3 && exit 1
diff --git a/utils/errlog/log.go b/utils/errlog/log.go
new file mode 100644
index 0000000..ec98b54
--- /dev/null
+++ b/utils/errlog/log.go
@@ -0,0 +1,80 @@
+package errlog
+
+import (
+ "fmt"
+ "os"
+ "path"
+ "runtime"
+ "time"
+)
+
+const (
+ OFF = iota
+ FATAL
+ ERROR
+ WARN
+ INFO
+ DEBUG
+ ALL
+)
+
+var LEVELS_NAME = map[int]string{
+ OFF: "off",
+ FATAL: "fatal",
+ ERROR: "error",
+ WARN: "warn",
+ INFO: "info",
+ DEBUG: "debug",
+ ALL: "all",
+}
+
+var f = os.Stderr
+var curLogLevel = INFO
+
+func init() {
+ // fpath := "/tmp/supervisor-event-listener.log"
+ // f = newLogFile(fpath)
+}
+
+func newLogFile(fpath string) *os.File {
+ mode := os.O_CREATE | os.O_WRONLY
+ file, err := os.OpenFile(fpath, mode, 0644)
+ if err != nil {
+ panic(err)
+ }
+ return file
+}
+
+func logFormat(level int, _fmt string, args ...interface{}) {
+ if level > curLogLevel {
+ return
+ }
+
+ _, fn, lineno, _ := runtime.Caller(2)
+ fn = path.Base(fn)
+ now := time.Now()
+ levelName := LEVELS_NAME[level]
+ prefix := fmt.Sprintf("%s [%s] %12s:%d ",
+ now.Format(time.RFC3339), levelName, fn, lineno)
+ f.WriteString(prefix)
+ f.WriteString(fmt.Sprintf(_fmt, args...))
+ f.WriteString("\n")
+}
+
+func Debug(fmt string, args ...interface{}) {
+ logFormat(DEBUG, fmt, args...)
+}
+
+func Info(fmt string, args ...interface{}) {
+ logFormat(INFO, fmt, args...)
+}
+
+func Error(fmt string, args ...interface{}) {
+ logFormat(ERROR, fmt, args...)
+}
+
+func Fatal(fmt string, args ...interface{}) {
+ logFormat(FATAL, fmt, args...)
+ f.Close()
+ os.Exit(127)
+}
diff --git a/utils/httpclient/http_client.go b/utils/httpclient/http_client.go
index 29f4334..2b0808d 100644
--- a/utils/httpclient/http_client.go
+++ b/utils/httpclient/http_client.go
@@ -10,13 +10,42 @@ import (
"time"
)
+var (
+ client = &http.Client{Timeout: 6 * time.Second}
+)
+
type ResponseWrapper struct {
StatusCode int
Body string
Header http.Header
}
-func Get(url string, timeout int) ResponseWrapper {
+func (this *ResponseWrapper) IsOK() bool {
+ return this != nil && this.StatusCode == 200
+}
+
+func (this *ResponseWrapper) String() string {
+ if this == nil {
+ return "null"
+ }
+ body := this.Body
+ if runes := []rune(this.Body); len(runes) > 256 {
+ body = string(runes[0:256])
+ }
+ return fmt.Sprintf("resp[%d] %s", this.StatusCode, body)
+}
+
+func (this *ResponseWrapper) Error() string {
+ if this == nil {
+ return "null"
+ }
+ if this.Body == "" {
+ return "empty body"
+ }
+ return this.Body
+}
+
+func Get(url string, timeout int) *ResponseWrapper {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return createRequestError(err)
@@ -25,7 +54,7 @@ func Get(url string, timeout int) ResponseWrapper {
return request(req, timeout)
}
-func PostParams(url string, params string, timeout int) ResponseWrapper {
+func PostParams(url string, params string, timeout int) *ResponseWrapper {
buf := bytes.NewBufferString(params)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
@@ -36,7 +65,7 @@ func PostParams(url string, params string, timeout int) ResponseWrapper {
return request(req, timeout)
}
-func PostJson(url string, body string, timeout int) ResponseWrapper {
+func PostJson(url string, body string, timeout int) *ResponseWrapper {
buf := bytes.NewBufferString(body)
req, err := http.NewRequest("POST", url, buf)
if err != nil {
@@ -47,9 +76,8 @@ func PostJson(url string, body string, timeout int) ResponseWrapper {
return request(req, timeout)
}
-func request(req *http.Request, timeout int) ResponseWrapper {
+func request(req *http.Request, timeout int) *ResponseWrapper {
wrapper := ResponseWrapper{StatusCode: 0, Body: "", Header: make(http.Header)}
- client := &http.Client{}
if timeout > 0 {
client.Timeout = time.Duration(timeout) * time.Second
}
@@ -57,19 +85,18 @@ func request(req *http.Request, timeout int) ResponseWrapper {
resp, err := client.Do(req)
if err != nil {
wrapper.Body = fmt.Sprintf("执行HTTP请求错误-%s", err.Error())
- return wrapper
+ return &wrapper
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
wrapper.Body = fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error())
- return wrapper
+ return &wrapper
}
wrapper.StatusCode = resp.StatusCode
wrapper.Body = string(body)
wrapper.Header = resp.Header
-
- return wrapper
+ return &wrapper
}
func setRequestHeader(req *http.Request) {
@@ -77,7 +104,7 @@ func setRequestHeader(req *http.Request) {
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36 golang/gocron")
}
-func createRequestError(err error) ResponseWrapper {
+func createRequestError(err error) *ResponseWrapper {
errorMessage := fmt.Sprintf("创建HTTP请求错误-%s", err.Error())
- return ResponseWrapper{0, errorMessage, make(http.Header)}
+ return &ResponseWrapper{0, errorMessage, make(http.Header)}
}
diff --git a/vendor/github.com/go-gomail/gomail/CHANGELOG.md b/vendor/github.com/go-gomail/gomail/CHANGELOG.md
deleted file mode 100644
index a797ab4..0000000
--- a/vendor/github.com/go-gomail/gomail/CHANGELOG.md
+++ /dev/null
@@ -1,20 +0,0 @@
-# Change Log
-All notable changes to this project will be documented in this file.
-This project adheres to [Semantic Versioning](http://semver.org/).
-
-## [2.0.0] - 2015-09-02
-
-- Mailer has been removed. It has been replaced by Dialer and Sender.
-- `File` type and the `CreateFile` and `OpenFile` functions have been removed.
-- `Message.Attach` and `Message.Embed` have a new signature.
-- `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
-instead.
-- `Message.Export` has been removed. `Message.WriteTo` can be used instead.
-- `Message.DelHeader` has been removed.
-- The `Bcc` header field is no longer sent. It is far more simpler and
-efficient: the same message is sent to all recipients instead of sending a
-different email to each Bcc address.
-- LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN
-authentication mechanism when needed.
-- Go 1.2 is now required instead of Go 1.3. No external dependency are used when
-using Go 1.5.
diff --git a/vendor/github.com/go-gomail/gomail/CONTRIBUTING.md b/vendor/github.com/go-gomail/gomail/CONTRIBUTING.md
deleted file mode 100644
index d5601c2..0000000
--- a/vendor/github.com/go-gomail/gomail/CONTRIBUTING.md
+++ /dev/null
@@ -1,20 +0,0 @@
-Thank you for contributing to Gomail! Here are a few guidelines:
-
-## Bugs
-
-If you think you found a bug, create an issue and supply the minimum amount
-of code triggering the bug so it can be reproduced.
-
-
-## Fixing a bug
-
-If you want to fix a bug, you can send a pull request. It should contains a
-new test or update an existing one to cover that bug.
-
-
-## New feature proposal
-
-If you think Gomail lacks a feature, you can open an issue or send a pull
-request. I want to keep Gomail code and API as simple as possible so please
-describe your needs so we can discuss whether this feature should be added to
-Gomail or not.
diff --git a/vendor/github.com/go-gomail/gomail/LICENSE b/vendor/github.com/go-gomail/gomail/LICENSE
deleted file mode 100644
index 5f5c12a..0000000
--- a/vendor/github.com/go-gomail/gomail/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Alexandre Cesaro
-
-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.
diff --git a/vendor/github.com/go-gomail/gomail/README.md b/vendor/github.com/go-gomail/gomail/README.md
deleted file mode 100644
index b3be9e1..0000000
--- a/vendor/github.com/go-gomail/gomail/README.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# Gomail
-[](https://travis-ci.org/go-gomail/gomail) [](http://gocover.io/gopkg.in/gomail.v2) [](https://godoc.org/gopkg.in/gomail.v2)
-
-## Introduction
-
-Gomail is a simple and efficient package to send emails. It is well tested and
-documented.
-
-Gomail can only send emails using an SMTP server. But the API is flexible and it
-is easy to implement other methods for sending emails using a local Postfix, an
-API, etc.
-
-It is versioned using [gopkg.in](https://gopkg.in) so I promise
-there will never be backward incompatible changes within each version.
-
-It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used.
-
-
-## Features
-
-Gomail supports:
-- Attachments
-- Embedded images
-- HTML and text templates
-- Automatic encoding of special characters
-- SSL and TLS
-- Sending multiple emails with the same SMTP connection
-
-
-## Documentation
-
-https://godoc.org/gopkg.in/gomail.v2
-
-
-## Download
-
- go get gopkg.in/gomail.v2
-
-
-## Examples
-
-See the [examples in the documentation](https://godoc.org/gopkg.in/gomail.v2#example-package).
-
-
-## FAQ
-
-### x509: certificate signed by unknown authority
-
-If you get this error it means the certificate used by the SMTP server is not
-considered valid by the client running Gomail. As a quick workaround you can
-bypass the verification of the server's certificate chain and host name by using
-`SetTLSConfig`:
-
- package main
-
- import (
- "crypto/tls"
-
- "gopkg.in/gomail.v2"
- )
-
- func main() {
- d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
- d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
-
- // Send emails using d.
- }
-
-Note, however, that this is insecure and should not be used in production.
-
-
-## Contribute
-
-Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
-more info.
-
-
-## Change log
-
-See [CHANGELOG.md](CHANGELOG.md).
-
-
-## License
-
-[MIT](LICENSE)
-
-
-## Contact
-
-You can ask questions on the [Gomail
-thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
-in the Go mailing-list.
diff --git a/vendor/github.com/go-gomail/gomail/auth.go b/vendor/github.com/go-gomail/gomail/auth.go
deleted file mode 100644
index d28b83a..0000000
--- a/vendor/github.com/go-gomail/gomail/auth.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package gomail
-
-import (
- "bytes"
- "errors"
- "fmt"
- "net/smtp"
-)
-
-// loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
-type loginAuth struct {
- username string
- password string
- host string
-}
-
-func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
- if !server.TLS {
- advertised := false
- for _, mechanism := range server.Auth {
- if mechanism == "LOGIN" {
- advertised = true
- break
- }
- }
- if !advertised {
- return "", nil, errors.New("gomail: unencrypted connection")
- }
- }
- if server.Name != a.host {
- return "", nil, errors.New("gomail: wrong host name")
- }
- return "LOGIN", nil, nil
-}
-
-func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
- if !more {
- return nil, nil
- }
-
- switch {
- case bytes.Equal(fromServer, []byte("Username:")):
- return []byte(a.username), nil
- case bytes.Equal(fromServer, []byte("Password:")):
- return []byte(a.password), nil
- default:
- return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
- }
-}
diff --git a/vendor/github.com/go-gomail/gomail/doc.go b/vendor/github.com/go-gomail/gomail/doc.go
deleted file mode 100644
index a8f5091..0000000
--- a/vendor/github.com/go-gomail/gomail/doc.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// Package gomail provides a simple interface to compose emails and to mail them
-// efficiently.
-//
-// More info on Github: https://github.com/go-gomail/gomail
-package gomail
diff --git a/vendor/github.com/go-gomail/gomail/message.go b/vendor/github.com/go-gomail/gomail/message.go
deleted file mode 100644
index 4bffb1e..0000000
--- a/vendor/github.com/go-gomail/gomail/message.go
+++ /dev/null
@@ -1,322 +0,0 @@
-package gomail
-
-import (
- "bytes"
- "io"
- "os"
- "path/filepath"
- "time"
-)
-
-// Message represents an email.
-type Message struct {
- header header
- parts []*part
- attachments []*file
- embedded []*file
- charset string
- encoding Encoding
- hEncoder mimeEncoder
- buf bytes.Buffer
-}
-
-type header map[string][]string
-
-type part struct {
- contentType string
- copier func(io.Writer) error
- encoding Encoding
-}
-
-// NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
-// by default.
-func NewMessage(settings ...MessageSetting) *Message {
- m := &Message{
- header: make(header),
- charset: "UTF-8",
- encoding: QuotedPrintable,
- }
-
- m.applySettings(settings)
-
- if m.encoding == Base64 {
- m.hEncoder = bEncoding
- } else {
- m.hEncoder = qEncoding
- }
-
- return m
-}
-
-// Reset resets the message so it can be reused. The message keeps its previous
-// settings so it is in the same state that after a call to NewMessage.
-func (m *Message) Reset() {
- for k := range m.header {
- delete(m.header, k)
- }
- m.parts = nil
- m.attachments = nil
- m.embedded = nil
-}
-
-func (m *Message) applySettings(settings []MessageSetting) {
- for _, s := range settings {
- s(m)
- }
-}
-
-// A MessageSetting can be used as an argument in NewMessage to configure an
-// email.
-type MessageSetting func(m *Message)
-
-// SetCharset is a message setting to set the charset of the email.
-func SetCharset(charset string) MessageSetting {
- return func(m *Message) {
- m.charset = charset
- }
-}
-
-// SetEncoding is a message setting to set the encoding of the email.
-func SetEncoding(enc Encoding) MessageSetting {
- return func(m *Message) {
- m.encoding = enc
- }
-}
-
-// Encoding represents a MIME encoding scheme like quoted-printable or base64.
-type Encoding string
-
-const (
- // QuotedPrintable represents the quoted-printable encoding as defined in
- // RFC 2045.
- QuotedPrintable Encoding = "quoted-printable"
- // Base64 represents the base64 encoding as defined in RFC 2045.
- Base64 Encoding = "base64"
- // Unencoded can be used to avoid encoding the body of an email. The headers
- // will still be encoded using quoted-printable encoding.
- Unencoded Encoding = "8bit"
-)
-
-// SetHeader sets a value to the given header field.
-func (m *Message) SetHeader(field string, value ...string) {
- m.encodeHeader(value)
- m.header[field] = value
-}
-
-func (m *Message) encodeHeader(values []string) {
- for i := range values {
- values[i] = m.encodeString(values[i])
- }
-}
-
-func (m *Message) encodeString(value string) string {
- return m.hEncoder.Encode(m.charset, value)
-}
-
-// SetHeaders sets the message headers.
-func (m *Message) SetHeaders(h map[string][]string) {
- for k, v := range h {
- m.SetHeader(k, v...)
- }
-}
-
-// SetAddressHeader sets an address to the given header field.
-func (m *Message) SetAddressHeader(field, address, name string) {
- m.header[field] = []string{m.FormatAddress(address, name)}
-}
-
-// FormatAddress formats an address and a name as a valid RFC 5322 address.
-func (m *Message) FormatAddress(address, name string) string {
- if name == "" {
- return address
- }
-
- enc := m.encodeString(name)
- if enc == name {
- m.buf.WriteByte('"')
- for i := 0; i < len(name); i++ {
- b := name[i]
- if b == '\\' || b == '"' {
- m.buf.WriteByte('\\')
- }
- m.buf.WriteByte(b)
- }
- m.buf.WriteByte('"')
- } else if hasSpecials(name) {
- m.buf.WriteString(bEncoding.Encode(m.charset, name))
- } else {
- m.buf.WriteString(enc)
- }
- m.buf.WriteString(" <")
- m.buf.WriteString(address)
- m.buf.WriteByte('>')
-
- addr := m.buf.String()
- m.buf.Reset()
- return addr
-}
-
-func hasSpecials(text string) bool {
- for i := 0; i < len(text); i++ {
- switch c := text[i]; c {
- case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
- return true
- }
- }
-
- return false
-}
-
-// SetDateHeader sets a date to the given header field.
-func (m *Message) SetDateHeader(field string, date time.Time) {
- m.header[field] = []string{m.FormatDate(date)}
-}
-
-// FormatDate formats a date as a valid RFC 5322 date.
-func (m *Message) FormatDate(date time.Time) string {
- return date.Format(time.RFC1123Z)
-}
-
-// GetHeader gets a header field.
-func (m *Message) GetHeader(field string) []string {
- return m.header[field]
-}
-
-// SetBody sets the body of the message. It replaces any content previously set
-// by SetBody, AddAlternative or AddAlternativeWriter.
-func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
- m.parts = []*part{m.newPart(contentType, newCopier(body), settings)}
-}
-
-// AddAlternative adds an alternative part to the message.
-//
-// It is commonly used to send HTML emails that default to the plain text
-// version for backward compatibility. AddAlternative appends the new part to
-// the end of the message. So the plain text part should be added before the
-// HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
-func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
- m.AddAlternativeWriter(contentType, newCopier(body), settings...)
-}
-
-func newCopier(s string) func(io.Writer) error {
- return func(w io.Writer) error {
- _, err := io.WriteString(w, s)
- return err
- }
-}
-
-// AddAlternativeWriter adds an alternative part to the message. It can be
-// useful with the text/template or html/template packages.
-func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
- m.parts = append(m.parts, m.newPart(contentType, f, settings))
-}
-
-func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
- p := &part{
- contentType: contentType,
- copier: f,
- encoding: m.encoding,
- }
-
- for _, s := range settings {
- s(p)
- }
-
- return p
-}
-
-// A PartSetting can be used as an argument in Message.SetBody,
-// Message.AddAlternative or Message.AddAlternativeWriter to configure the part
-// added to a message.
-type PartSetting func(*part)
-
-// SetPartEncoding sets the encoding of the part added to the message. By
-// default, parts use the same encoding than the message.
-func SetPartEncoding(e Encoding) PartSetting {
- return PartSetting(func(p *part) {
- p.encoding = e
- })
-}
-
-type file struct {
- Name string
- Header map[string][]string
- CopyFunc func(w io.Writer) error
-}
-
-func (f *file) setHeader(field, value string) {
- f.Header[field] = []string{value}
-}
-
-// A FileSetting can be used as an argument in Message.Attach or Message.Embed.
-type FileSetting func(*file)
-
-// SetHeader is a file setting to set the MIME header of the message part that
-// contains the file content.
-//
-// Mandatory headers are automatically added if they are not set when sending
-// the email.
-func SetHeader(h map[string][]string) FileSetting {
- return func(f *file) {
- for k, v := range h {
- f.Header[k] = v
- }
- }
-}
-
-// Rename is a file setting to set the name of the attachment if the name is
-// different than the filename on disk.
-func Rename(name string) FileSetting {
- return func(f *file) {
- f.Name = name
- }
-}
-
-// SetCopyFunc is a file setting to replace the function that runs when the
-// message is sent. It should copy the content of the file to the io.Writer.
-//
-// The default copy function opens the file with the given filename, and copy
-// its content to the io.Writer.
-func SetCopyFunc(f func(io.Writer) error) FileSetting {
- return func(fi *file) {
- fi.CopyFunc = f
- }
-}
-
-func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file {
- f := &file{
- Name: filepath.Base(name),
- Header: make(map[string][]string),
- CopyFunc: func(w io.Writer) error {
- h, err := os.Open(name)
- if err != nil {
- return err
- }
- if _, err := io.Copy(w, h); err != nil {
- h.Close()
- return err
- }
- return h.Close()
- },
- }
-
- for _, s := range settings {
- s(f)
- }
-
- if list == nil {
- return []*file{f}
- }
-
- return append(list, f)
-}
-
-// Attach attaches the files to the email.
-func (m *Message) Attach(filename string, settings ...FileSetting) {
- m.attachments = m.appendFile(m.attachments, filename, settings)
-}
-
-// Embed embeds the images to the email.
-func (m *Message) Embed(filename string, settings ...FileSetting) {
- m.embedded = m.appendFile(m.embedded, filename, settings)
-}
diff --git a/vendor/github.com/go-gomail/gomail/mime.go b/vendor/github.com/go-gomail/gomail/mime.go
deleted file mode 100644
index 194d4a7..0000000
--- a/vendor/github.com/go-gomail/gomail/mime.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// +build go1.5
-
-package gomail
-
-import (
- "mime"
- "mime/quotedprintable"
- "strings"
-)
-
-var newQPWriter = quotedprintable.NewWriter
-
-type mimeEncoder struct {
- mime.WordEncoder
-}
-
-var (
- bEncoding = mimeEncoder{mime.BEncoding}
- qEncoding = mimeEncoder{mime.QEncoding}
- lastIndexByte = strings.LastIndexByte
-)
diff --git a/vendor/github.com/go-gomail/gomail/mime_go14.go b/vendor/github.com/go-gomail/gomail/mime_go14.go
deleted file mode 100644
index 3dc26aa..0000000
--- a/vendor/github.com/go-gomail/gomail/mime_go14.go
+++ /dev/null
@@ -1,25 +0,0 @@
-// +build !go1.5
-
-package gomail
-
-import "gopkg.in/alexcesaro/quotedprintable.v3"
-
-var newQPWriter = quotedprintable.NewWriter
-
-type mimeEncoder struct {
- quotedprintable.WordEncoder
-}
-
-var (
- bEncoding = mimeEncoder{quotedprintable.BEncoding}
- qEncoding = mimeEncoder{quotedprintable.QEncoding}
- lastIndexByte = func(s string, c byte) int {
- for i := len(s) - 1; i >= 0; i-- {
-
- if s[i] == c {
- return i
- }
- }
- return -1
- }
-)
diff --git a/vendor/github.com/go-gomail/gomail/send.go b/vendor/github.com/go-gomail/gomail/send.go
deleted file mode 100644
index 9115ebe..0000000
--- a/vendor/github.com/go-gomail/gomail/send.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package gomail
-
-import (
- "errors"
- "fmt"
- "io"
- "net/mail"
-)
-
-// Sender is the interface that wraps the Send method.
-//
-// Send sends an email to the given addresses.
-type Sender interface {
- Send(from string, to []string, msg io.WriterTo) error
-}
-
-// SendCloser is the interface that groups the Send and Close methods.
-type SendCloser interface {
- Sender
- Close() error
-}
-
-// A SendFunc is a function that sends emails to the given addresses.
-//
-// The SendFunc type is an adapter to allow the use of ordinary functions as
-// email senders. If f is a function with the appropriate signature, SendFunc(f)
-// is a Sender object that calls f.
-type SendFunc func(from string, to []string, msg io.WriterTo) error
-
-// Send calls f(from, to, msg).
-func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
- return f(from, to, msg)
-}
-
-// Send sends emails using the given Sender.
-func Send(s Sender, msg ...*Message) error {
- for i, m := range msg {
- if err := send(s, m); err != nil {
- return fmt.Errorf("gomail: could not send email %d: %v", i+1, err)
- }
- }
-
- return nil
-}
-
-func send(s Sender, m *Message) error {
- from, err := m.getFrom()
- if err != nil {
- return err
- }
-
- to, err := m.getRecipients()
- if err != nil {
- return err
- }
-
- if err := s.Send(from, to, m); err != nil {
- return err
- }
-
- return nil
-}
-
-func (m *Message) getFrom() (string, error) {
- from := m.header["Sender"]
- if len(from) == 0 {
- from = m.header["From"]
- if len(from) == 0 {
- return "", errors.New(`gomail: invalid message, "From" field is absent`)
- }
- }
-
- return parseAddress(from[0])
-}
-
-func (m *Message) getRecipients() ([]string, error) {
- n := 0
- for _, field := range []string{"To", "Cc", "Bcc"} {
- if addresses, ok := m.header[field]; ok {
- n += len(addresses)
- }
- }
- list := make([]string, 0, n)
-
- for _, field := range []string{"To", "Cc", "Bcc"} {
- if addresses, ok := m.header[field]; ok {
- for _, a := range addresses {
- addr, err := parseAddress(a)
- if err != nil {
- return nil, err
- }
- list = addAddress(list, addr)
- }
- }
- }
-
- return list, nil
-}
-
-func addAddress(list []string, addr string) []string {
- for _, a := range list {
- if addr == a {
- return list
- }
- }
-
- return append(list, addr)
-}
-
-func parseAddress(field string) (string, error) {
- addr, err := mail.ParseAddress(field)
- if err != nil {
- return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
- }
- return addr.Address, nil
-}
diff --git a/vendor/github.com/go-gomail/gomail/smtp.go b/vendor/github.com/go-gomail/gomail/smtp.go
deleted file mode 100644
index 2aa49c8..0000000
--- a/vendor/github.com/go-gomail/gomail/smtp.go
+++ /dev/null
@@ -1,202 +0,0 @@
-package gomail
-
-import (
- "crypto/tls"
- "fmt"
- "io"
- "net"
- "net/smtp"
- "strings"
- "time"
-)
-
-// A Dialer is a dialer to an SMTP server.
-type Dialer struct {
- // Host represents the host of the SMTP server.
- Host string
- // Port represents the port of the SMTP server.
- Port int
- // Username is the username to use to authenticate to the SMTP server.
- Username string
- // Password is the password to use to authenticate to the SMTP server.
- Password string
- // Auth represents the authentication mechanism used to authenticate to the
- // SMTP server.
- Auth smtp.Auth
- // SSL defines whether an SSL connection is used. It should be false in
- // most cases since the authentication mechanism should use the STARTTLS
- // extension instead.
- SSL bool
- // TSLConfig represents the TLS configuration used for the TLS (when the
- // STARTTLS extension is used) or SSL connection.
- TLSConfig *tls.Config
- // LocalName is the hostname sent to the SMTP server with the HELO command.
- // By default, "localhost" is sent.
- LocalName string
-}
-
-// NewDialer returns a new SMTP Dialer. The given parameters are used to connect
-// to the SMTP server.
-func NewDialer(host string, port int, username, password string) *Dialer {
- return &Dialer{
- Host: host,
- Port: port,
- Username: username,
- Password: password,
- SSL: port == 465,
- }
-}
-
-// NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
-// connect to the SMTP server.
-//
-// Deprecated: Use NewDialer instead.
-func NewPlainDialer(host string, port int, username, password string) *Dialer {
- return NewDialer(host, port, username, password)
-}
-
-// Dial dials and authenticates to an SMTP server. The returned SendCloser
-// should be closed when done using it.
-func (d *Dialer) Dial() (SendCloser, error) {
- conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
- if err != nil {
- return nil, err
- }
-
- if d.SSL {
- conn = tlsClient(conn, d.tlsConfig())
- }
-
- c, err := smtpNewClient(conn, d.Host)
- if err != nil {
- return nil, err
- }
-
- if d.LocalName != "" {
- if err := c.Hello(d.LocalName); err != nil {
- return nil, err
- }
- }
-
- if !d.SSL {
- if ok, _ := c.Extension("STARTTLS"); ok {
- if err := c.StartTLS(d.tlsConfig()); err != nil {
- c.Close()
- return nil, err
- }
- }
- }
-
- if d.Auth == nil && d.Username != "" {
- if ok, auths := c.Extension("AUTH"); ok {
- if strings.Contains(auths, "CRAM-MD5") {
- d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
- } else if strings.Contains(auths, "LOGIN") &&
- !strings.Contains(auths, "PLAIN") {
- d.Auth = &loginAuth{
- username: d.Username,
- password: d.Password,
- host: d.Host,
- }
- } else {
- d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
- }
- }
- }
-
- if d.Auth != nil {
- if err = c.Auth(d.Auth); err != nil {
- c.Close()
- return nil, err
- }
- }
-
- return &smtpSender{c, d}, nil
-}
-
-func (d *Dialer) tlsConfig() *tls.Config {
- if d.TLSConfig == nil {
- return &tls.Config{ServerName: d.Host}
- }
- return d.TLSConfig
-}
-
-func addr(host string, port int) string {
- return fmt.Sprintf("%s:%d", host, port)
-}
-
-// DialAndSend opens a connection to the SMTP server, sends the given emails and
-// closes the connection.
-func (d *Dialer) DialAndSend(m ...*Message) error {
- s, err := d.Dial()
- if err != nil {
- return err
- }
- defer s.Close()
-
- return Send(s, m...)
-}
-
-type smtpSender struct {
- smtpClient
- d *Dialer
-}
-
-func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
- if err := c.Mail(from); err != nil {
- if err == io.EOF {
- // This is probably due to a timeout, so reconnect and try again.
- sc, derr := c.d.Dial()
- if derr == nil {
- if s, ok := sc.(*smtpSender); ok {
- *c = *s
- return c.Send(from, to, msg)
- }
- }
- }
- return err
- }
-
- for _, addr := range to {
- if err := c.Rcpt(addr); err != nil {
- return err
- }
- }
-
- w, err := c.Data()
- if err != nil {
- return err
- }
-
- if _, err = msg.WriteTo(w); err != nil {
- w.Close()
- return err
- }
-
- return w.Close()
-}
-
-func (c *smtpSender) Close() error {
- return c.Quit()
-}
-
-// Stubbed out for tests.
-var (
- netDialTimeout = net.DialTimeout
- tlsClient = tls.Client
- smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
- return smtp.NewClient(conn, host)
- }
-)
-
-type smtpClient interface {
- Hello(string) error
- Extension(string) (bool, string)
- StartTLS(*tls.Config) error
- Auth(smtp.Auth) error
- Mail(string) error
- Rcpt(string) error
- Data() (io.WriteCloser, error)
- Quit() error
- Close() error
-}
diff --git a/vendor/github.com/go-gomail/gomail/writeto.go b/vendor/github.com/go-gomail/gomail/writeto.go
deleted file mode 100644
index 9fb6b86..0000000
--- a/vendor/github.com/go-gomail/gomail/writeto.go
+++ /dev/null
@@ -1,306 +0,0 @@
-package gomail
-
-import (
- "encoding/base64"
- "errors"
- "io"
- "mime"
- "mime/multipart"
- "path/filepath"
- "strings"
- "time"
-)
-
-// WriteTo implements io.WriterTo. It dumps the whole message into w.
-func (m *Message) WriteTo(w io.Writer) (int64, error) {
- mw := &messageWriter{w: w}
- mw.writeMessage(m)
- return mw.n, mw.err
-}
-
-func (w *messageWriter) writeMessage(m *Message) {
- if _, ok := m.header["Mime-Version"]; !ok {
- w.writeString("Mime-Version: 1.0\r\n")
- }
- if _, ok := m.header["Date"]; !ok {
- w.writeHeader("Date", m.FormatDate(now()))
- }
- w.writeHeaders(m.header)
-
- if m.hasMixedPart() {
- w.openMultipart("mixed")
- }
-
- if m.hasRelatedPart() {
- w.openMultipart("related")
- }
-
- if m.hasAlternativePart() {
- w.openMultipart("alternative")
- }
- for _, part := range m.parts {
- w.writePart(part, m.charset)
- }
- if m.hasAlternativePart() {
- w.closeMultipart()
- }
-
- w.addFiles(m.embedded, false)
- if m.hasRelatedPart() {
- w.closeMultipart()
- }
-
- w.addFiles(m.attachments, true)
- if m.hasMixedPart() {
- w.closeMultipart()
- }
-}
-
-func (m *Message) hasMixedPart() bool {
- return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
-}
-
-func (m *Message) hasRelatedPart() bool {
- return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
-}
-
-func (m *Message) hasAlternativePart() bool {
- return len(m.parts) > 1
-}
-
-type messageWriter struct {
- w io.Writer
- n int64
- writers [3]*multipart.Writer
- partWriter io.Writer
- depth uint8
- err error
-}
-
-func (w *messageWriter) openMultipart(mimeType string) {
- mw := multipart.NewWriter(w)
- contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
- w.writers[w.depth] = mw
-
- if w.depth == 0 {
- w.writeHeader("Content-Type", contentType)
- w.writeString("\r\n")
- } else {
- w.createPart(map[string][]string{
- "Content-Type": {contentType},
- })
- }
- w.depth++
-}
-
-func (w *messageWriter) createPart(h map[string][]string) {
- w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
-}
-
-func (w *messageWriter) closeMultipart() {
- if w.depth > 0 {
- w.writers[w.depth-1].Close()
- w.depth--
- }
-}
-
-func (w *messageWriter) writePart(p *part, charset string) {
- w.writeHeaders(map[string][]string{
- "Content-Type": {p.contentType + "; charset=" + charset},
- "Content-Transfer-Encoding": {string(p.encoding)},
- })
- w.writeBody(p.copier, p.encoding)
-}
-
-func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
- for _, f := range files {
- if _, ok := f.Header["Content-Type"]; !ok {
- mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
- if mediaType == "" {
- mediaType = "application/octet-stream"
- }
- f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
- }
-
- if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
- f.setHeader("Content-Transfer-Encoding", string(Base64))
- }
-
- if _, ok := f.Header["Content-Disposition"]; !ok {
- var disp string
- if isAttachment {
- disp = "attachment"
- } else {
- disp = "inline"
- }
- f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
- }
-
- if !isAttachment {
- if _, ok := f.Header["Content-ID"]; !ok {
- f.setHeader("Content-ID", "<"+f.Name+">")
- }
- }
- w.writeHeaders(f.Header)
- w.writeBody(f.CopyFunc, Base64)
- }
-}
-
-func (w *messageWriter) Write(p []byte) (int, error) {
- if w.err != nil {
- return 0, errors.New("gomail: cannot write as writer is in error")
- }
-
- var n int
- n, w.err = w.w.Write(p)
- w.n += int64(n)
- return n, w.err
-}
-
-func (w *messageWriter) writeString(s string) {
- n, _ := io.WriteString(w.w, s)
- w.n += int64(n)
-}
-
-func (w *messageWriter) writeHeader(k string, v ...string) {
- w.writeString(k)
- if len(v) == 0 {
- w.writeString(":\r\n")
- return
- }
- w.writeString(": ")
-
- // Max header line length is 78 characters in RFC 5322 and 76 characters
- // in RFC 2047. So for the sake of simplicity we use the 76 characters
- // limit.
- charsLeft := 76 - len(k) - len(": ")
-
- for i, s := range v {
- // If the line is already too long, insert a newline right away.
- if charsLeft < 1 {
- if i == 0 {
- w.writeString("\r\n ")
- } else {
- w.writeString(",\r\n ")
- }
- charsLeft = 75
- } else if i != 0 {
- w.writeString(", ")
- charsLeft -= 2
- }
-
- // While the header content is too long, fold it by inserting a newline.
- for len(s) > charsLeft {
- s = w.writeLine(s, charsLeft)
- charsLeft = 75
- }
- w.writeString(s)
- if i := lastIndexByte(s, '\n'); i != -1 {
- charsLeft = 75 - (len(s) - i - 1)
- } else {
- charsLeft -= len(s)
- }
- }
- w.writeString("\r\n")
-}
-
-func (w *messageWriter) writeLine(s string, charsLeft int) string {
- // If there is already a newline before the limit. Write the line.
- if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
- w.writeString(s[:i+1])
- return s[i+1:]
- }
-
- for i := charsLeft - 1; i >= 0; i-- {
- if s[i] == ' ' {
- w.writeString(s[:i])
- w.writeString("\r\n ")
- return s[i+1:]
- }
- }
-
- // We could not insert a newline cleanly so look for a space or a newline
- // even if it is after the limit.
- for i := 75; i < len(s); i++ {
- if s[i] == ' ' {
- w.writeString(s[:i])
- w.writeString("\r\n ")
- return s[i+1:]
- }
- if s[i] == '\n' {
- w.writeString(s[:i+1])
- return s[i+1:]
- }
- }
-
- // Too bad, no space or newline in the whole string. Just write everything.
- w.writeString(s)
- return ""
-}
-
-func (w *messageWriter) writeHeaders(h map[string][]string) {
- if w.depth == 0 {
- for k, v := range h {
- if k != "Bcc" {
- w.writeHeader(k, v...)
- }
- }
- } else {
- w.createPart(h)
- }
-}
-
-func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
- var subWriter io.Writer
- if w.depth == 0 {
- w.writeString("\r\n")
- subWriter = w.w
- } else {
- subWriter = w.partWriter
- }
-
- if enc == Base64 {
- wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
- w.err = f(wc)
- wc.Close()
- } else if enc == Unencoded {
- w.err = f(subWriter)
- } else {
- wc := newQPWriter(subWriter)
- w.err = f(wc)
- wc.Close()
- }
-}
-
-// As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
-// RFC 2045, 6.8. (page 25) for base64.
-const maxLineLen = 76
-
-// base64LineWriter limits text encoded in base64 to 76 characters per line
-type base64LineWriter struct {
- w io.Writer
- lineLen int
-}
-
-func newBase64LineWriter(w io.Writer) *base64LineWriter {
- return &base64LineWriter{w: w}
-}
-
-func (w *base64LineWriter) Write(p []byte) (int, error) {
- n := 0
- for len(p)+w.lineLen > maxLineLen {
- w.w.Write(p[:maxLineLen-w.lineLen])
- w.w.Write([]byte("\r\n"))
- p = p[maxLineLen-w.lineLen:]
- n += maxLineLen - w.lineLen
- w.lineLen = 0
- }
-
- w.w.Write(p)
- w.lineLen += len(p)
-
- return n + len(p), nil
-}
-
-// Stubbed out for testing.
-var now = time.Now
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
deleted file mode 100644
index 5f5c12a..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE
+++ /dev/null
@@ -1,20 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2014 Alexandre Cesaro
-
-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.
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
deleted file mode 100644
index 98ddf82..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# quotedprintable
-
-## Introduction
-
-Package quotedprintable implements quoted-printable and message header encoding
-as specified by RFC 2045 and RFC 2047.
-
-It is a copy of the Go 1.5 package `mime/quotedprintable`. It also includes
-the new functions of package `mime` concerning RFC 2047.
-
-This code has minor changes with the standard library code in order to work
-with Go 1.0 and newer.
-
-## Documentation
-
-https://godoc.org/gopkg.in/alexcesaro/quotedprintable.v3
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
deleted file mode 100644
index cfd0261..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go
+++ /dev/null
@@ -1,279 +0,0 @@
-package quotedprintable
-
-import (
- "bytes"
- "encoding/base64"
- "errors"
- "fmt"
- "io"
- "strings"
- "unicode"
- "unicode/utf8"
-)
-
-// A WordEncoder is a RFC 2047 encoded-word encoder.
-type WordEncoder byte
-
-const (
- // BEncoding represents Base64 encoding scheme as defined by RFC 2045.
- BEncoding = WordEncoder('b')
- // QEncoding represents the Q-encoding scheme as defined by RFC 2047.
- QEncoding = WordEncoder('q')
-)
-
-var (
- errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
-)
-
-// Encode returns the encoded-word form of s. If s is ASCII without special
-// characters, it is returned unchanged. The provided charset is the IANA
-// charset name of s. It is case insensitive.
-func (e WordEncoder) Encode(charset, s string) string {
- if !needsEncoding(s) {
- return s
- }
- return e.encodeWord(charset, s)
-}
-
-func needsEncoding(s string) bool {
- for _, b := range s {
- if (b < ' ' || b > '~') && b != '\t' {
- return true
- }
- }
- return false
-}
-
-// encodeWord encodes a string into an encoded-word.
-func (e WordEncoder) encodeWord(charset, s string) string {
- buf := getBuffer()
- defer putBuffer(buf)
-
- buf.WriteString("=?")
- buf.WriteString(charset)
- buf.WriteByte('?')
- buf.WriteByte(byte(e))
- buf.WriteByte('?')
-
- if e == BEncoding {
- w := base64.NewEncoder(base64.StdEncoding, buf)
- io.WriteString(w, s)
- w.Close()
- } else {
- enc := make([]byte, 3)
- for i := 0; i < len(s); i++ {
- b := s[i]
- switch {
- case b == ' ':
- buf.WriteByte('_')
- case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
- buf.WriteByte(b)
- default:
- enc[0] = '='
- enc[1] = upperhex[b>>4]
- enc[2] = upperhex[b&0x0f]
- buf.Write(enc)
- }
- }
- }
- buf.WriteString("?=")
- return buf.String()
-}
-
-const upperhex = "0123456789ABCDEF"
-
-// A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
-type WordDecoder struct {
- // CharsetReader, if non-nil, defines a function to generate
- // charset-conversion readers, converting from the provided
- // charset into UTF-8.
- // Charsets are always lower-case. utf-8, iso-8859-1 and us-ascii charsets
- // are handled by default.
- // One of the the CharsetReader's result values must be non-nil.
- CharsetReader func(charset string, input io.Reader) (io.Reader, error)
-}
-
-// Decode decodes an encoded-word. If word is not a valid RFC 2047 encoded-word,
-// word is returned unchanged.
-func (d *WordDecoder) Decode(word string) (string, error) {
- fields := strings.Split(word, "?") // TODO: remove allocation?
- if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
- return "", errInvalidWord
- }
-
- content, err := decode(fields[2][0], fields[3])
- if err != nil {
- return "", err
- }
-
- buf := getBuffer()
- defer putBuffer(buf)
-
- if err := d.convert(buf, fields[1], content); err != nil {
- return "", err
- }
-
- return buf.String(), nil
-}
-
-// DecodeHeader decodes all encoded-words of the given string. It returns an
-// error if and only if CharsetReader of d returns an error.
-func (d *WordDecoder) DecodeHeader(header string) (string, error) {
- // If there is no encoded-word, returns before creating a buffer.
- i := strings.Index(header, "=?")
- if i == -1 {
- return header, nil
- }
-
- buf := getBuffer()
- defer putBuffer(buf)
-
- buf.WriteString(header[:i])
- header = header[i:]
-
- betweenWords := false
- for {
- start := strings.Index(header, "=?")
- if start == -1 {
- break
- }
- cur := start + len("=?")
-
- i := strings.Index(header[cur:], "?")
- if i == -1 {
- break
- }
- charset := header[cur : cur+i]
- cur += i + len("?")
-
- if len(header) < cur+len("Q??=") {
- break
- }
- encoding := header[cur]
- cur++
-
- if header[cur] != '?' {
- break
- }
- cur++
-
- j := strings.Index(header[cur:], "?=")
- if j == -1 {
- break
- }
- text := header[cur : cur+j]
- end := cur + j + len("?=")
-
- content, err := decode(encoding, text)
- if err != nil {
- betweenWords = false
- buf.WriteString(header[:start+2])
- header = header[start+2:]
- continue
- }
-
- // Write characters before the encoded-word. White-space and newline
- // characters separating two encoded-words must be deleted.
- if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) {
- buf.WriteString(header[:start])
- }
-
- if err := d.convert(buf, charset, content); err != nil {
- return "", err
- }
-
- header = header[end:]
- betweenWords = true
- }
-
- if len(header) > 0 {
- buf.WriteString(header)
- }
-
- return buf.String(), nil
-}
-
-func decode(encoding byte, text string) ([]byte, error) {
- switch encoding {
- case 'B', 'b':
- return base64.StdEncoding.DecodeString(text)
- case 'Q', 'q':
- return qDecode(text)
- }
- return nil, errInvalidWord
-}
-
-func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error {
- switch {
- case strings.EqualFold("utf-8", charset):
- buf.Write(content)
- case strings.EqualFold("iso-8859-1", charset):
- for _, c := range content {
- buf.WriteRune(rune(c))
- }
- case strings.EqualFold("us-ascii", charset):
- for _, c := range content {
- if c >= utf8.RuneSelf {
- buf.WriteRune(unicode.ReplacementChar)
- } else {
- buf.WriteByte(c)
- }
- }
- default:
- if d.CharsetReader == nil {
- return fmt.Errorf("mime: unhandled charset %q", charset)
- }
- r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content))
- if err != nil {
- return err
- }
- if _, err = buf.ReadFrom(r); err != nil {
- return err
- }
- }
- return nil
-}
-
-// hasNonWhitespace reports whether s (assumed to be ASCII) contains at least
-// one byte of non-whitespace.
-func hasNonWhitespace(s string) bool {
- for _, b := range s {
- switch b {
- // Encoded-words can only be separated by linear white spaces which does
- // not include vertical tabs (\v).
- case ' ', '\t', '\n', '\r':
- default:
- return true
- }
- }
- return false
-}
-
-// qDecode decodes a Q encoded string.
-func qDecode(s string) ([]byte, error) {
- dec := make([]byte, len(s))
- n := 0
- for i := 0; i < len(s); i++ {
- switch c := s[i]; {
- case c == '_':
- dec[n] = ' '
- case c == '=':
- if i+2 >= len(s) {
- return nil, errInvalidWord
- }
- b, err := readHexByte(s[i+1], s[i+2])
- if err != nil {
- return nil, err
- }
- dec[n] = b
- i += 2
- case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t':
- dec[n] = c
- default:
- return nil, errInvalidWord
- }
- n++
- }
-
- return dec[:n], nil
-}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
deleted file mode 100644
index 24283c5..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go
+++ /dev/null
@@ -1,26 +0,0 @@
-// +build go1.3
-
-package quotedprintable
-
-import (
- "bytes"
- "sync"
-)
-
-var bufPool = sync.Pool{
- New: func() interface{} {
- return new(bytes.Buffer)
- },
-}
-
-func getBuffer() *bytes.Buffer {
- return bufPool.Get().(*bytes.Buffer)
-}
-
-func putBuffer(buf *bytes.Buffer) {
- if buf.Len() > 1024 {
- return
- }
- buf.Reset()
- bufPool.Put(buf)
-}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
deleted file mode 100644
index d335b4a..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// +build !go1.3
-
-package quotedprintable
-
-import "bytes"
-
-var ch = make(chan *bytes.Buffer, 32)
-
-func getBuffer() *bytes.Buffer {
- select {
- case buf := <-ch:
- return buf
- default:
- }
- return new(bytes.Buffer)
-}
-
-func putBuffer(buf *bytes.Buffer) {
- buf.Reset()
- select {
- case ch <- buf:
- default:
- }
-}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
deleted file mode 100644
index 955edca..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go
+++ /dev/null
@@ -1,121 +0,0 @@
-// Package quotedprintable implements quoted-printable encoding as specified by
-// RFC 2045.
-package quotedprintable
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
-)
-
-// Reader is a quoted-printable decoder.
-type Reader struct {
- br *bufio.Reader
- rerr error // last read error
- line []byte // to be consumed before more of br
-}
-
-// NewReader returns a quoted-printable reader, decoding from r.
-func NewReader(r io.Reader) *Reader {
- return &Reader{
- br: bufio.NewReader(r),
- }
-}
-
-func fromHex(b byte) (byte, error) {
- switch {
- case b >= '0' && b <= '9':
- return b - '0', nil
- case b >= 'A' && b <= 'F':
- return b - 'A' + 10, nil
- // Accept badly encoded bytes.
- case b >= 'a' && b <= 'f':
- return b - 'a' + 10, nil
- }
- return 0, fmt.Errorf("quotedprintable: invalid hex byte 0x%02x", b)
-}
-
-func readHexByte(a, b byte) (byte, error) {
- var hb, lb byte
- var err error
- if hb, err = fromHex(a); err != nil {
- return 0, err
- }
- if lb, err = fromHex(b); err != nil {
- return 0, err
- }
- return hb<<4 | lb, nil
-}
-
-func isQPDiscardWhitespace(r rune) bool {
- switch r {
- case '\n', '\r', ' ', '\t':
- return true
- }
- return false
-}
-
-var (
- crlf = []byte("\r\n")
- lf = []byte("\n")
- softSuffix = []byte("=")
-)
-
-// Read reads and decodes quoted-printable data from the underlying reader.
-func (r *Reader) Read(p []byte) (n int, err error) {
- // Deviations from RFC 2045:
- // 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
- // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
- // with other broken QP encoders & decoders.
- for len(p) > 0 {
- if len(r.line) == 0 {
- if r.rerr != nil {
- return n, r.rerr
- }
- r.line, r.rerr = r.br.ReadSlice('\n')
-
- // Does the line end in CRLF instead of just LF?
- hasLF := bytes.HasSuffix(r.line, lf)
- hasCR := bytes.HasSuffix(r.line, crlf)
- wholeLine := r.line
- r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace)
- if bytes.HasSuffix(r.line, softSuffix) {
- rightStripped := wholeLine[len(r.line):]
- r.line = r.line[:len(r.line)-1]
- if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) {
- r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped)
- }
- } else if hasLF {
- if hasCR {
- r.line = append(r.line, '\r', '\n')
- } else {
- r.line = append(r.line, '\n')
- }
- }
- continue
- }
- b := r.line[0]
-
- switch {
- case b == '=':
- if len(r.line[1:]) < 2 {
- return n, io.ErrUnexpectedEOF
- }
- b, err = readHexByte(r.line[1], r.line[2])
- if err != nil {
- return n, err
- }
- r.line = r.line[2:] // 2 of the 3; other 1 is done below
- case b == '\t' || b == '\r' || b == '\n':
- break
- case b < ' ' || b > '~':
- return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b)
- }
- p[0] = b
- p = p[1:]
- r.line = r.line[1:]
- n++
- }
- return n, nil
-}
diff --git a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go b/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
deleted file mode 100644
index 43359d5..0000000
--- a/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go
+++ /dev/null
@@ -1,166 +0,0 @@
-package quotedprintable
-
-import "io"
-
-const lineMaxLen = 76
-
-// A Writer is a quoted-printable writer that implements io.WriteCloser.
-type Writer struct {
- // Binary mode treats the writer's input as pure binary and processes end of
- // line bytes as binary data.
- Binary bool
-
- w io.Writer
- i int
- line [78]byte
- cr bool
-}
-
-// NewWriter returns a new Writer that writes to w.
-func NewWriter(w io.Writer) *Writer {
- return &Writer{w: w}
-}
-
-// Write encodes p using quoted-printable encoding and writes it to the
-// underlying io.Writer. It limits line length to 76 characters. The encoded
-// bytes are not necessarily flushed until the Writer is closed.
-func (w *Writer) Write(p []byte) (n int, err error) {
- for i, b := range p {
- switch {
- // Simple writes are done in batch.
- case b >= '!' && b <= '~' && b != '=':
- continue
- case isWhitespace(b) || !w.Binary && (b == '\n' || b == '\r'):
- continue
- }
-
- if i > n {
- if err := w.write(p[n:i]); err != nil {
- return n, err
- }
- n = i
- }
-
- if err := w.encode(b); err != nil {
- return n, err
- }
- n++
- }
-
- if n == len(p) {
- return n, nil
- }
-
- if err := w.write(p[n:]); err != nil {
- return n, err
- }
-
- return len(p), nil
-}
-
-// Close closes the Writer, flushing any unwritten data to the underlying
-// io.Writer, but does not close the underlying io.Writer.
-func (w *Writer) Close() error {
- if err := w.checkLastByte(); err != nil {
- return err
- }
-
- return w.flush()
-}
-
-// write limits text encoded in quoted-printable to 76 characters per line.
-func (w *Writer) write(p []byte) error {
- for _, b := range p {
- if b == '\n' || b == '\r' {
- // If the previous byte was \r, the CRLF has already been inserted.
- if w.cr && b == '\n' {
- w.cr = false
- continue
- }
-
- if b == '\r' {
- w.cr = true
- }
-
- if err := w.checkLastByte(); err != nil {
- return err
- }
- if err := w.insertCRLF(); err != nil {
- return err
- }
- continue
- }
-
- if w.i == lineMaxLen-1 {
- if err := w.insertSoftLineBreak(); err != nil {
- return err
- }
- }
-
- w.line[w.i] = b
- w.i++
- w.cr = false
- }
-
- return nil
-}
-
-func (w *Writer) encode(b byte) error {
- if lineMaxLen-1-w.i < 3 {
- if err := w.insertSoftLineBreak(); err != nil {
- return err
- }
- }
-
- w.line[w.i] = '='
- w.line[w.i+1] = upperhex[b>>4]
- w.line[w.i+2] = upperhex[b&0x0f]
- w.i += 3
-
- return nil
-}
-
-// checkLastByte encodes the last buffered byte if it is a space or a tab.
-func (w *Writer) checkLastByte() error {
- if w.i == 0 {
- return nil
- }
-
- b := w.line[w.i-1]
- if isWhitespace(b) {
- w.i--
- if err := w.encode(b); err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (w *Writer) insertSoftLineBreak() error {
- w.line[w.i] = '='
- w.i++
-
- return w.insertCRLF()
-}
-
-func (w *Writer) insertCRLF() error {
- w.line[w.i] = '\r'
- w.line[w.i+1] = '\n'
- w.i += 2
-
- return w.flush()
-}
-
-func (w *Writer) flush() error {
- if _, err := w.w.Write(w.line[:w.i]); err != nil {
- return err
- }
-
- w.i = 0
- return nil
-}
-
-func isWhitespace(b byte) bool {
- return b == ' ' || b == '\t'
-}
diff --git a/vendor/gopkg.in/ini.v1/LICENSE b/vendor/gopkg.in/ini.v1/LICENSE
deleted file mode 100644
index 37ec93a..0000000
--- a/vendor/gopkg.in/ini.v1/LICENSE
+++ /dev/null
@@ -1,191 +0,0 @@
-Apache License
-Version 2.0, January 2004
-http://www.apache.org/licenses/
-
-TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-1. Definitions.
-
-"License" shall mean the terms and conditions for use, reproduction, and
-distribution as defined by Sections 1 through 9 of this document.
-
-"Licensor" shall mean the copyright owner or entity authorized by the copyright
-owner that is granting the License.
-
-"Legal Entity" shall mean the union of the acting entity and all other entities
-that control, are controlled by, or are under common control with that entity.
-For the purposes of this definition, "control" means (i) the power, direct or
-indirect, to cause the direction or management of such entity, whether by
-contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
-outstanding shares, or (iii) beneficial ownership of such entity.
-
-"You" (or "Your") shall mean an individual or Legal Entity exercising
-permissions granted by this License.
-
-"Source" form shall mean the preferred form for making modifications, including
-but not limited to software source code, documentation source, and configuration
-files.
-
-"Object" form shall mean any form resulting from mechanical transformation or
-translation of a Source form, including but not limited to compiled object code,
-generated documentation, and conversions to other media types.
-
-"Work" shall mean the work of authorship, whether in Source or Object form, made
-available under the License, as indicated by a copyright notice that is included
-in or attached to the work (an example is provided in the Appendix below).
-
-"Derivative Works" shall mean any work, whether in Source or Object form, that
-is based on (or derived from) the Work and for which the editorial revisions,
-annotations, elaborations, or other modifications represent, as a whole, an
-original work of authorship. For the purposes of this License, Derivative Works
-shall not include works that remain separable from, or merely link (or bind by
-name) to the interfaces of, the Work and Derivative Works thereof.
-
-"Contribution" shall mean any work of authorship, including the original version
-of the Work and any modifications or additions to that Work or Derivative Works
-thereof, that is intentionally submitted to Licensor for inclusion in the Work
-by the copyright owner or by an individual or Legal Entity authorized to submit
-on behalf of the copyright owner. For the purposes of this definition,
-"submitted" means any form of electronic, verbal, or written communication sent
-to the Licensor or its representatives, including but not limited to
-communication on electronic mailing lists, source code control systems, and
-issue tracking systems that are managed by, or on behalf of, the Licensor for
-the purpose of discussing and improving the Work, but excluding communication
-that is conspicuously marked or otherwise designated in writing by the copyright
-owner as "Not a Contribution."
-
-"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
-of whom a Contribution has been received by Licensor and subsequently
-incorporated within the Work.
-
-2. Grant of Copyright License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable copyright license to reproduce, prepare Derivative Works of,
-publicly display, publicly perform, sublicense, and distribute the Work and such
-Derivative Works in Source or Object form.
-
-3. Grant of Patent License.
-
-Subject to the terms and conditions of this License, each Contributor hereby
-grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
-irrevocable (except as stated in this section) patent license to make, have
-made, use, offer to sell, sell, import, and otherwise transfer the Work, where
-such license applies only to those patent claims licensable by such Contributor
-that are necessarily infringed by their Contribution(s) alone or by combination
-of their Contribution(s) with the Work to which such Contribution(s) was
-submitted. If You institute patent litigation against any entity (including a
-cross-claim or counterclaim in a lawsuit) alleging that the Work or a
-Contribution incorporated within the Work constitutes direct or contributory
-patent infringement, then any patent licenses granted to You under this License
-for that Work shall terminate as of the date such litigation is filed.
-
-4. Redistribution.
-
-You may reproduce and distribute copies of the Work or Derivative Works thereof
-in any medium, with or without modifications, and in Source or Object form,
-provided that You meet the following conditions:
-
-You must give any other recipients of the Work or Derivative Works a copy of
-this License; and
-You must cause any modified files to carry prominent notices stating that You
-changed the files; and
-You must retain, in the Source form of any Derivative Works that You distribute,
-all copyright, patent, trademark, and attribution notices from the Source form
-of the Work, excluding those notices that do not pertain to any part of the
-Derivative Works; and
-If the Work includes a "NOTICE" text file as part of its distribution, then any
-Derivative Works that You distribute must include a readable copy of the
-attribution notices contained within such NOTICE file, excluding those notices
-that do not pertain to any part of the Derivative Works, in at least one of the
-following places: within a NOTICE text file distributed as part of the
-Derivative Works; within the Source form or documentation, if provided along
-with the Derivative Works; or, within a display generated by the Derivative
-Works, if and wherever such third-party notices normally appear. The contents of
-the NOTICE file are for informational purposes only and do not modify the
-License. You may add Your own attribution notices within Derivative Works that
-You distribute, alongside or as an addendum to the NOTICE text from the Work,
-provided that such additional attribution notices cannot be construed as
-modifying the License.
-You may add Your own copyright statement to Your modifications and may provide
-additional or different license terms and conditions for use, reproduction, or
-distribution of Your modifications, or for any such Derivative Works as a whole,
-provided Your use, reproduction, and distribution of the Work otherwise complies
-with the conditions stated in this License.
-
-5. Submission of Contributions.
-
-Unless You explicitly state otherwise, any Contribution intentionally submitted
-for inclusion in the Work by You to the Licensor shall be under the terms and
-conditions of this License, without any additional terms or conditions.
-Notwithstanding the above, nothing herein shall supersede or modify the terms of
-any separate license agreement you may have executed with Licensor regarding
-such Contributions.
-
-6. Trademarks.
-
-This License does not grant permission to use the trade names, trademarks,
-service marks, or product names of the Licensor, except as required for
-reasonable and customary use in describing the origin of the Work and
-reproducing the content of the NOTICE file.
-
-7. Disclaimer of Warranty.
-
-Unless required by applicable law or agreed to in writing, Licensor provides the
-Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
-including, without limitation, any warranties or conditions of TITLE,
-NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
-solely responsible for determining the appropriateness of using or
-redistributing the Work and assume any risks associated with Your exercise of
-permissions under this License.
-
-8. Limitation of Liability.
-
-In no event and under no legal theory, whether in tort (including negligence),
-contract, or otherwise, unless required by applicable law (such as deliberate
-and grossly negligent acts) or agreed to in writing, shall any Contributor be
-liable to You for damages, including any direct, indirect, special, incidental,
-or consequential damages of any character arising as a result of this License or
-out of the use or inability to use the Work (including but not limited to
-damages for loss of goodwill, work stoppage, computer failure or malfunction, or
-any and all other commercial damages or losses), even if such Contributor has
-been advised of the possibility of such damages.
-
-9. Accepting Warranty or Additional Liability.
-
-While redistributing the Work or Derivative Works thereof, You may choose to
-offer, and charge a fee for, acceptance of support, warranty, indemnity, or
-other liability obligations and/or rights consistent with this License. However,
-in accepting such obligations, You may act only on Your own behalf and on Your
-sole responsibility, not on behalf of any other Contributor, and only if You
-agree to indemnify, defend, and hold each Contributor harmless for any liability
-incurred by, or claims asserted against, such Contributor by reason of your
-accepting any such warranty or additional liability.
-
-END OF TERMS AND CONDITIONS
-
-APPENDIX: How to apply the Apache License to your work
-
-To apply the Apache License to your work, attach the following boilerplate
-notice, with the fields enclosed by brackets "[]" replaced with your own
-identifying information. (Don't include the brackets!) The text should be
-enclosed in the appropriate comment syntax for the file format. We also
-recommend that a file or class name and description of purpose be included on
-the same "printed page" as the copyright notice for easier identification within
-third-party archives.
-
- Copyright [yyyy] [name of copyright owner]
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/vendor/gopkg.in/ini.v1/Makefile b/vendor/gopkg.in/ini.v1/Makefile
deleted file mode 100644
index ac034e5..0000000
--- a/vendor/gopkg.in/ini.v1/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-.PHONY: build test bench vet
-
-build: vet bench
-
-test:
- go test -v -cover -race
-
-bench:
- go test -v -cover -race -test.bench=. -test.benchmem
-
-vet:
- go vet
diff --git a/vendor/gopkg.in/ini.v1/README.md b/vendor/gopkg.in/ini.v1/README.md
deleted file mode 100644
index e67d51f..0000000
--- a/vendor/gopkg.in/ini.v1/README.md
+++ /dev/null
@@ -1,746 +0,0 @@
-INI [](https://travis-ci.org/go-ini/ini) [](https://sourcegraph.com/github.com/go-ini/ini?badge)
-===
-
-
-
-Package ini provides INI file read and write functionality in Go.
-
-[简体中文](README_ZH.md)
-
-## Feature
-
-- Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
-- Read with recursion values.
-- Read with parent-child sections.
-- Read with auto-increment key names.
-- Read with multiple-line values.
-- Read with tons of helper methods.
-- Read and convert values to Go types.
-- Read and **WRITE** comments of sections and keys.
-- Manipulate sections, keys and comments with ease.
-- Keep sections and keys in order as you parse and save.
-
-## Installation
-
-To use a tagged revision:
-
- go get gopkg.in/ini.v1
-
-To use with latest changes:
-
- go get github.com/go-ini/ini
-
-Please add `-u` flag to update in the future.
-
-### Testing
-
-If you want to test on your machine, please apply `-t` flag:
-
- go get -t gopkg.in/ini.v1
-
-Please add `-u` flag to update in the future.
-
-## Getting Started
-
-### Loading from data sources
-
-A **Data Source** is either raw data in type `[]byte`, a file name with type `string` or `io.ReadCloser`. You can load **as many data sources as you want**. Passing other types will simply return an error.
-
-```go
-cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
-```
-
-Or start with an empty object:
-
-```go
-cfg := ini.Empty()
-```
-
-When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
-
-```go
-err := cfg.Append("other file", []byte("other raw data"))
-```
-
-If you have a list of files with possibilities that some of them may not available at the time, and you don't know exactly which ones, you can use `LooseLoad` to ignore nonexistent files without returning error.
-
-```go
-cfg, err := ini.LooseLoad("filename", "filename_404")
-```
-
-The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
-
-#### Ignore cases of key name
-
-When you do not care about cases of section and key names, you can use `InsensitiveLoad` to force all names to be lowercased while parsing.
-
-```go
-cfg, err := ini.InsensitiveLoad("filename")
-//...
-
-// sec1 and sec2 are the exactly same section object
-sec1, err := cfg.GetSection("Section")
-sec2, err := cfg.GetSection("SecTIOn")
-
-// key1 and key2 are the exactly same key object
-key1, err := sec1.GetKey("Key")
-key2, err := sec2.GetKey("KeY")
-```
-
-#### MySQL-like boolean key
-
-MySQL's configuration allows a key without value as follows:
-
-```ini
-[mysqld]
-...
-skip-host-cache
-skip-name-resolve
-```
-
-By default, this is considered as missing value. But if you know you're going to deal with those cases, you can assign advanced load options:
-
-```go
-cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
-```
-
-The value of those keys are always `true`, and when you save to a file, it will keep in the same foramt as you read.
-
-To generate such keys in your program, you could use `NewBooleanKey`:
-
-```go
-key, err := sec.NewBooleanKey("skip-host-cache")
-```
-
-#### Comment
-
-Take care that following format will be treated as comment:
-
-1. Line begins with `#` or `;`
-2. Words after `#` or `;`
-3. Words after section name (i.e words after `[some section name]`)
-
-If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
-
-Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
-
-```go
-cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
-```
-
-### Working with sections
-
-To get a section, you would need to:
-
-```go
-section, err := cfg.GetSection("section name")
-```
-
-For a shortcut for default section, just give an empty string as name:
-
-```go
-section, err := cfg.GetSection("")
-```
-
-When you're pretty sure the section exists, following code could make your life easier:
-
-```go
-section := cfg.Section("section name")
-```
-
-What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
-
-To create a new section:
-
-```go
-err := cfg.NewSection("new section")
-```
-
-To get a list of sections or section names:
-
-```go
-sections := cfg.Sections()
-names := cfg.SectionStrings()
-```
-
-### Working with keys
-
-To get a key under a section:
-
-```go
-key, err := cfg.Section("").GetKey("key name")
-```
-
-Same rule applies to key operations:
-
-```go
-key := cfg.Section("").Key("key name")
-```
-
-To check if a key exists:
-
-```go
-yes := cfg.Section("").HasKey("key name")
-```
-
-To create a new key:
-
-```go
-err := cfg.Section("").NewKey("name", "value")
-```
-
-To get a list of keys or key names:
-
-```go
-keys := cfg.Section("").Keys()
-names := cfg.Section("").KeyStrings()
-```
-
-To get a clone hash of keys and corresponding values:
-
-```go
-hash := cfg.Section("").KeysHash()
-```
-
-### Working with values
-
-To get a string value:
-
-```go
-val := cfg.Section("").Key("key name").String()
-```
-
-To validate key value on the fly:
-
-```go
-val := cfg.Section("").Key("key name").Validate(func(in string) string {
- if len(in) == 0 {
- return "default"
- }
- return in
-})
-```
-
-If you do not want any auto-transformation (such as recursive read) for the values, you can get raw value directly (this way you get much better performance):
-
-```go
-val := cfg.Section("").Key("key name").Value()
-```
-
-To check if raw value exists:
-
-```go
-yes := cfg.Section("").HasValue("test value")
-```
-
-To get value with types:
-
-```go
-// For boolean values:
-// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
-// false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
-v, err = cfg.Section("").Key("BOOL").Bool()
-v, err = cfg.Section("").Key("FLOAT64").Float64()
-v, err = cfg.Section("").Key("INT").Int()
-v, err = cfg.Section("").Key("INT64").Int64()
-v, err = cfg.Section("").Key("UINT").Uint()
-v, err = cfg.Section("").Key("UINT64").Uint64()
-v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
-v, err = cfg.Section("").Key("TIME").Time() // RFC3339
-
-v = cfg.Section("").Key("BOOL").MustBool()
-v = cfg.Section("").Key("FLOAT64").MustFloat64()
-v = cfg.Section("").Key("INT").MustInt()
-v = cfg.Section("").Key("INT64").MustInt64()
-v = cfg.Section("").Key("UINT").MustUint()
-v = cfg.Section("").Key("UINT64").MustUint64()
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
-v = cfg.Section("").Key("TIME").MustTime() // RFC3339
-
-// Methods start with Must also accept one argument for default value
-// when key not found or fail to parse value to given type.
-// Except method MustString, which you have to pass a default value.
-
-v = cfg.Section("").Key("String").MustString("default")
-v = cfg.Section("").Key("BOOL").MustBool(true)
-v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
-v = cfg.Section("").Key("INT").MustInt(10)
-v = cfg.Section("").Key("INT64").MustInt64(99)
-v = cfg.Section("").Key("UINT").MustUint(3)
-v = cfg.Section("").Key("UINT64").MustUint64(6)
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
-v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
-```
-
-What if my value is three-line long?
-
-```ini
-[advance]
-ADDRESS = """404 road,
-NotFound, State, 5000
-Earth"""
-```
-
-Not a problem!
-
-```go
-cfg.Section("advance").Key("ADDRESS").String()
-
-/* --- start ---
-404 road,
-NotFound, State, 5000
-Earth
------- end --- */
-```
-
-That's cool, how about continuation lines?
-
-```ini
-[advance]
-two_lines = how about \
- continuation lines?
-lots_of_lines = 1 \
- 2 \
- 3 \
- 4
-```
-
-Piece of cake!
-
-```go
-cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
-cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
-```
-
-Well, I hate continuation lines, how do I disable that?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{
- IgnoreContinuation: true,
-}, "filename")
-```
-
-Holy crap!
-
-Note that single quotes around values will be stripped:
-
-```ini
-foo = "some value" // foo: some value
-bar = 'some value' // bar: some value
-```
-
-That's all? Hmm, no.
-
-#### Helper methods of working with values
-
-To get value with given candidates:
-
-```go
-v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
-v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
-v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
-v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
-v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
-v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
-```
-
-Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
-
-To validate value in a given range:
-
-```go
-vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
-vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
-vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
-vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
-vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
-vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
-vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
-```
-
-##### Auto-split values into a slice
-
-To use zero value of type for invalid inputs:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
-vals = cfg.Section("").Key("STRINGS").Strings(",")
-vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
-vals = cfg.Section("").Key("INTS").Ints(",")
-vals = cfg.Section("").Key("INT64S").Int64s(",")
-vals = cfg.Section("").Key("UINTS").Uints(",")
-vals = cfg.Section("").Key("UINT64S").Uint64s(",")
-vals = cfg.Section("").Key("TIMES").Times(",")
-```
-
-To exclude invalid values out of result slice:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [2.2]
-vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
-vals = cfg.Section("").Key("INTS").ValidInts(",")
-vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
-vals = cfg.Section("").Key("UINTS").ValidUints(",")
-vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
-vals = cfg.Section("").Key("TIMES").ValidTimes(",")
-```
-
-Or to return nothing but error when have invalid inputs:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> error
-vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
-vals = cfg.Section("").Key("INTS").StrictInts(",")
-vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
-vals = cfg.Section("").Key("UINTS").StrictUints(",")
-vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
-vals = cfg.Section("").Key("TIMES").StrictTimes(",")
-```
-
-### Save your configuration
-
-Finally, it's time to save your configuration to somewhere.
-
-A typical way to save configuration is writing it to a file:
-
-```go
-// ...
-err = cfg.SaveTo("my.ini")
-err = cfg.SaveToIndent("my.ini", "\t")
-```
-
-Another way to save is writing to a `io.Writer` interface:
-
-```go
-// ...
-cfg.WriteTo(writer)
-cfg.WriteToIndent(writer, "\t")
-```
-
-By default, spaces are used to align "=" sign between key and values, to disable that:
-
-```go
-ini.PrettyFormat = false
-```
-
-## Advanced Usage
-
-### Recursive Values
-
-For all value of keys, there is a special syntax `%()s`, where `` is the key name in same section or default section, and `%()s` will be replaced by corresponding value(empty string if key not found). You can use this syntax at most 99 level of recursions.
-
-```ini
-NAME = ini
-
-[author]
-NAME = Unknwon
-GITHUB = https://github.com/%(NAME)s
-
-[package]
-FULL_NAME = github.com/go-ini/%(NAME)s
-```
-
-```go
-cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
-cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
-```
-
-### Parent-child Sections
-
-You can use `.` in section name to indicate parent-child relationship between two or more sections. If the key not found in the child section, library will try again on its parent section until there is no parent section.
-
-```ini
-NAME = ini
-VERSION = v1
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-```
-
-```go
-cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
-```
-
-#### Retrieve parent keys available to a child section
-
-```go
-cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
-```
-
-### Unparseable Sections
-
-Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
-
-```go
-cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
-<1> This slide has the fuel listed in the wrong units `))
-
-body := cfg.Section("COMMENTS").Body()
-
-/* --- start ---
-<1> This slide has the fuel listed in the wrong units
------- end --- */
-```
-
-### Auto-increment Key Names
-
-If key name is `-` in data source, then it would be seen as special syntax for auto-increment key name start from 1, and every section is independent on counter.
-
-```ini
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-```
-
-```go
-cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
-```
-
-### Map To Struct
-
-Want more objective way to play with INI? Cool.
-
-```ini
-Name = Unknwon
-age = 21
-Male = true
-Born = 1993-01-01T20:17:05Z
-
-[Note]
-Content = Hi is a good man!
-Cities = HangZhou, Boston
-```
-
-```go
-type Note struct {
- Content string
- Cities []string
-}
-
-type Person struct {
- Name string
- Age int `ini:"age"`
- Male bool
- Born time.Time
- Note
- Created time.Time `ini:"-"`
-}
-
-func main() {
- cfg, err := ini.Load("path/to/ini")
- // ...
- p := new(Person)
- err = cfg.MapTo(p)
- // ...
-
- // Things can be simpler.
- err = ini.MapTo(p, "path/to/ini")
- // ...
-
- // Just map a section? Fine.
- n := new(Note)
- err = cfg.Section("Note").MapTo(n)
- // ...
-}
-```
-
-Can I have default value for field? Absolutely.
-
-Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
-
-```go
-// ...
-p := &Person{
- Name: "Joe",
-}
-// ...
-```
-
-It's really cool, but what's the point if you can't give me my file back from struct?
-
-### Reflect From Struct
-
-Why not?
-
-```go
-type Embeded struct {
- Dates []time.Time `delim:"|"`
- Places []string `ini:"places,omitempty"`
- None []int `ini:",omitempty"`
-}
-
-type Author struct {
- Name string `ini:"NAME"`
- Male bool
- Age int
- GPA float64
- NeverMind string `ini:"-"`
- *Embeded
-}
-
-func main() {
- a := &Author{"Unknwon", true, 21, 2.8, "",
- &Embeded{
- []time.Time{time.Now(), time.Now()},
- []string{"HangZhou", "Boston"},
- []int{},
- }}
- cfg := ini.Empty()
- err = ini.ReflectFrom(cfg, a)
- // ...
-}
-```
-
-So, what do I get?
-
-```ini
-NAME = Unknwon
-Male = true
-Age = 21
-GPA = 2.8
-
-[Embeded]
-Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
-places = HangZhou,Boston
-```
-
-#### Name Mapper
-
-To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual section and key name.
-
-There are 2 built-in name mappers:
-
-- `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
-- `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
-
-To use them:
-
-```go
-type Info struct {
- PackageName string
-}
-
-func main() {
- err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
- // ...
-
- cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
- // ...
- info := new(Info)
- cfg.NameMapper = ini.AllCapsUnderscore
- err = cfg.MapTo(info)
- // ...
-}
-```
-
-Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
-
-#### Value Mapper
-
-To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
-
-```go
-type Env struct {
- Foo string `ini:"foo"`
-}
-
-func main() {
- cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
- cfg.ValueMapper = os.ExpandEnv
- // ...
- env := &Env{}
- err = cfg.Section("env").MapTo(env)
-}
-```
-
-This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
-
-#### Other Notes On Map/Reflect
-
-Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
-
-```go
-type Child struct {
- Age string
-}
-
-type Parent struct {
- Name string
- Child
-}
-
-type Config struct {
- City string
- Parent
-}
-```
-
-Example configuration:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-
-[Child]
-Age = 21
-```
-
-What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
-
-```go
-type Child struct {
- Age string
-}
-
-type Parent struct {
- Name string
- Child `ini:"Parent"`
-}
-
-type Config struct {
- City string
- Parent
-}
-```
-
-Example configuration:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-Age = 21
-```
-
-## Getting Help
-
-- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
-- [File An Issue](https://github.com/go-ini/ini/issues/new)
-
-## FAQs
-
-### What does `BlockMode` field do?
-
-By default, library lets you read and write values so we need a locker to make sure your data is safe. But in cases that you are very sure about only reading data through the library, you can set `cfg.BlockMode = false` to speed up read operations about **50-70%** faster.
-
-### Why another INI library?
-
-Many people are using my another INI library [goconfig](https://github.com/Unknwon/goconfig), so the reason for this one is I would like to make more Go style code. Also when you set `cfg.BlockMode = false`, this one is about **10-30%** faster.
-
-To make those changes I have to confirm API broken, so it's safer to keep it in another place and start using `gopkg.in` to version my package at this time.(PS: shorter import path)
-
-## License
-
-This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/vendor/gopkg.in/ini.v1/README_ZH.md b/vendor/gopkg.in/ini.v1/README_ZH.md
deleted file mode 100644
index 0cf4194..0000000
--- a/vendor/gopkg.in/ini.v1/README_ZH.md
+++ /dev/null
@@ -1,733 +0,0 @@
-本包提供了 Go 语言中读写 INI 文件的功能。
-
-## 功能特性
-
-- 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
-- 支持递归读取键值
-- 支持读取父子分区
-- 支持读取自增键名
-- 支持读取多行的键值
-- 支持大量辅助方法
-- 支持在读取时直接转换为 Go 语言类型
-- 支持读取和 **写入** 分区和键的注释
-- 轻松操作分区、键值和注释
-- 在保存文件时分区和键值会保持原有的顺序
-
-## 下载安装
-
-使用一个特定版本:
-
- go get gopkg.in/ini.v1
-
-使用最新版:
-
- go get github.com/go-ini/ini
-
-如需更新请添加 `-u` 选项。
-
-### 测试安装
-
-如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
-
- go get -t gopkg.in/ini.v1
-
-如需更新请添加 `-u` 选项。
-
-## 开始使用
-
-### 从数据源加载
-
-一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
-
-```go
-cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
-```
-
-或者从一个空白的文件开始:
-
-```go
-cfg := ini.Empty()
-```
-
-当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
-
-```go
-err := cfg.Append("other file", []byte("other raw data"))
-```
-
-当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
-
-```go
-cfg, err := ini.LooseLoad("filename", "filename_404")
-```
-
-更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
-
-#### 忽略键名的大小写
-
-有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
-
-```go
-cfg, err := ini.InsensitiveLoad("filename")
-//...
-
-// sec1 和 sec2 指向同一个分区对象
-sec1, err := cfg.GetSection("Section")
-sec2, err := cfg.GetSection("SecTIOn")
-
-// key1 和 key2 指向同一个键对象
-key1, err := sec1.GetKey("Key")
-key2, err := sec2.GetKey("KeY")
-```
-
-#### 类似 MySQL 配置中的布尔值键
-
-MySQL 的配置文件中会出现没有具体值的布尔类型的键:
-
-```ini
-[mysqld]
-...
-skip-host-cache
-skip-name-resolve
-```
-
-默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
-
-```go
-cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
-```
-
-这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
-
-如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
-
-```go
-key, err := sec.NewBooleanKey("skip-host-cache")
-```
-
-#### 关于注释
-
-下述几种情况的内容将被视为注释:
-
-1. 所有以 `#` 或 `;` 开头的行
-2. 所有在 `#` 或 `;` 之后的内容
-3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
-
-如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
-
-除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
-
-```go
-cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
-```
-
-### 操作分区(Section)
-
-获取指定分区:
-
-```go
-section, err := cfg.GetSection("section name")
-```
-
-如果您想要获取默认分区,则可以用空字符串代替分区名:
-
-```go
-section, err := cfg.GetSection("")
-```
-
-当您非常确定某个分区是存在的,可以使用以下简便方法:
-
-```go
-section := cfg.Section("section name")
-```
-
-如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
-
-创建一个分区:
-
-```go
-err := cfg.NewSection("new section")
-```
-
-获取所有分区对象或名称:
-
-```go
-sections := cfg.Sections()
-names := cfg.SectionStrings()
-```
-
-### 操作键(Key)
-
-获取某个分区下的键:
-
-```go
-key, err := cfg.Section("").GetKey("key name")
-```
-
-和分区一样,您也可以直接获取键而忽略错误处理:
-
-```go
-key := cfg.Section("").Key("key name")
-```
-
-判断某个键是否存在:
-
-```go
-yes := cfg.Section("").HasKey("key name")
-```
-
-创建一个新的键:
-
-```go
-err := cfg.Section("").NewKey("name", "value")
-```
-
-获取分区下的所有键或键名:
-
-```go
-keys := cfg.Section("").Keys()
-names := cfg.Section("").KeyStrings()
-```
-
-获取分区下的所有键值对的克隆:
-
-```go
-hash := cfg.Section("").KeysHash()
-```
-
-### 操作键值(Value)
-
-获取一个类型为字符串(string)的值:
-
-```go
-val := cfg.Section("").Key("key name").String()
-```
-
-获取值的同时通过自定义函数进行处理验证:
-
-```go
-val := cfg.Section("").Key("key name").Validate(func(in string) string {
- if len(in) == 0 {
- return "default"
- }
- return in
-})
-```
-
-如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
-
-```go
-val := cfg.Section("").Key("key name").Value()
-```
-
-判断某个原值是否存在:
-
-```go
-yes := cfg.Section("").HasValue("test value")
-```
-
-获取其它类型的值:
-
-```go
-// 布尔值的规则:
-// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
-// false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
-v, err = cfg.Section("").Key("BOOL").Bool()
-v, err = cfg.Section("").Key("FLOAT64").Float64()
-v, err = cfg.Section("").Key("INT").Int()
-v, err = cfg.Section("").Key("INT64").Int64()
-v, err = cfg.Section("").Key("UINT").Uint()
-v, err = cfg.Section("").Key("UINT64").Uint64()
-v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
-v, err = cfg.Section("").Key("TIME").Time() // RFC3339
-
-v = cfg.Section("").Key("BOOL").MustBool()
-v = cfg.Section("").Key("FLOAT64").MustFloat64()
-v = cfg.Section("").Key("INT").MustInt()
-v = cfg.Section("").Key("INT64").MustInt64()
-v = cfg.Section("").Key("UINT").MustUint()
-v = cfg.Section("").Key("UINT64").MustUint64()
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
-v = cfg.Section("").Key("TIME").MustTime() // RFC3339
-
-// 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
-// 当键不存在或者转换失败时,则会直接返回该默认值。
-// 但是,MustString 方法必须传递一个默认值。
-
-v = cfg.Seciont("").Key("String").MustString("default")
-v = cfg.Section("").Key("BOOL").MustBool(true)
-v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
-v = cfg.Section("").Key("INT").MustInt(10)
-v = cfg.Section("").Key("INT64").MustInt64(99)
-v = cfg.Section("").Key("UINT").MustUint(3)
-v = cfg.Section("").Key("UINT64").MustUint64(6)
-v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
-v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
-```
-
-如果我的值有好多行怎么办?
-
-```ini
-[advance]
-ADDRESS = """404 road,
-NotFound, State, 5000
-Earth"""
-```
-
-嗯哼?小 case!
-
-```go
-cfg.Section("advance").Key("ADDRESS").String()
-
-/* --- start ---
-404 road,
-NotFound, State, 5000
-Earth
------- end --- */
-```
-
-赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
-
-```ini
-[advance]
-two_lines = how about \
- continuation lines?
-lots_of_lines = 1 \
- 2 \
- 3 \
- 4
-```
-
-简直是小菜一碟!
-
-```go
-cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
-cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
-```
-
-可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
-
-```go
-cfg, err := ini.LoadSources(ini.LoadOptions{
- IgnoreContinuation: true,
-}, "filename")
-```
-
-哇靠给力啊!
-
-需要注意的是,值两侧的单引号会被自动剔除:
-
-```ini
-foo = "some value" // foo: some value
-bar = 'some value' // bar: some value
-```
-
-这就是全部了?哈哈,当然不是。
-
-#### 操作键值的辅助方法
-
-获取键值时设定候选值:
-
-```go
-v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
-v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
-v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
-v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
-v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
-v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
-```
-
-如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
-
-验证获取的值是否在指定范围内:
-
-```go
-vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
-vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
-vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
-vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
-vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
-vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
-vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
-```
-
-##### 自动分割键值到切片(slice)
-
-当存在无效输入时,使用零值代替:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
-vals = cfg.Section("").Key("STRINGS").Strings(",")
-vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
-vals = cfg.Section("").Key("INTS").Ints(",")
-vals = cfg.Section("").Key("INT64S").Int64s(",")
-vals = cfg.Section("").Key("UINTS").Uints(",")
-vals = cfg.Section("").Key("UINT64S").Uint64s(",")
-vals = cfg.Section("").Key("TIMES").Times(",")
-```
-
-从结果切片中剔除无效输入:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> [2.2]
-vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
-vals = cfg.Section("").Key("INTS").ValidInts(",")
-vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
-vals = cfg.Section("").Key("UINTS").ValidUints(",")
-vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
-vals = cfg.Section("").Key("TIMES").ValidTimes(",")
-```
-
-当存在无效输入时,直接返回错误:
-
-```go
-// Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
-// Input: how, 2.2, are, you -> error
-vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
-vals = cfg.Section("").Key("INTS").StrictInts(",")
-vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
-vals = cfg.Section("").Key("UINTS").StrictUints(",")
-vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
-vals = cfg.Section("").Key("TIMES").StrictTimes(",")
-```
-
-### 保存配置
-
-终于到了这个时刻,是时候保存一下配置了。
-
-比较原始的做法是输出配置到某个文件:
-
-```go
-// ...
-err = cfg.SaveTo("my.ini")
-err = cfg.SaveToIndent("my.ini", "\t")
-```
-
-另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
-
-```go
-// ...
-cfg.WriteTo(writer)
-cfg.WriteToIndent(writer, "\t")
-```
-
-默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
-
-```go
-ini.PrettyFormat = false
-```
-
-## 高级用法
-
-### 递归读取键值
-
-在获取所有键值的过程中,特殊语法 `%()s` 会被应用,其中 `` 可以是相同分区或者默认分区下的键名。字符串 `%()s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
-
-```ini
-NAME = ini
-
-[author]
-NAME = Unknwon
-GITHUB = https://github.com/%(NAME)s
-
-[package]
-FULL_NAME = github.com/go-ini/%(NAME)s
-```
-
-```go
-cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
-cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
-```
-
-### 读取父子分区
-
-您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
-
-```ini
-NAME = ini
-VERSION = v1
-IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
-
-[package]
-CLONE_URL = https://%(IMPORT_PATH)s
-
-[package.sub]
-```
-
-```go
-cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
-```
-
-#### 获取上级父分区下的所有键名
-
-```go
-cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
-```
-
-### 无法解析的分区
-
-如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
-
-```go
-cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
-<1> This slide has the fuel listed in the wrong units `))
-
-body := cfg.Section("COMMENTS").Body()
-
-/* --- start ---
-<1> This slide has the fuel listed in the wrong units
------- end --- */
-```
-
-### 读取自增键名
-
-如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
-
-```ini
-[features]
--: Support read/write comments of keys and sections
--: Support auto-increment of key names
--: Support load multiple files to overwrite key values
-```
-
-```go
-cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
-```
-
-### 映射到结构
-
-想要使用更加面向对象的方式玩转 INI 吗?好主意。
-
-```ini
-Name = Unknwon
-age = 21
-Male = true
-Born = 1993-01-01T20:17:05Z
-
-[Note]
-Content = Hi is a good man!
-Cities = HangZhou, Boston
-```
-
-```go
-type Note struct {
- Content string
- Cities []string
-}
-
-type Person struct {
- Name string
- Age int `ini:"age"`
- Male bool
- Born time.Time
- Note
- Created time.Time `ini:"-"`
-}
-
-func main() {
- cfg, err := ini.Load("path/to/ini")
- // ...
- p := new(Person)
- err = cfg.MapTo(p)
- // ...
-
- // 一切竟可以如此的简单。
- err = ini.MapTo(p, "path/to/ini")
- // ...
-
- // 嗯哼?只需要映射一个分区吗?
- n := new(Note)
- err = cfg.Section("Note").MapTo(n)
- // ...
-}
-```
-
-结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
-
-```go
-// ...
-p := &Person{
- Name: "Joe",
-}
-// ...
-```
-
-这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
-
-### 从结构反射
-
-可是,我有说不能吗?
-
-```go
-type Embeded struct {
- Dates []time.Time `delim:"|"`
- Places []string `ini:"places,omitempty"`
- None []int `ini:",omitempty"`
-}
-
-type Author struct {
- Name string `ini:"NAME"`
- Male bool
- Age int
- GPA float64
- NeverMind string `ini:"-"`
- *Embeded
-}
-
-func main() {
- a := &Author{"Unknwon", true, 21, 2.8, "",
- &Embeded{
- []time.Time{time.Now(), time.Now()},
- []string{"HangZhou", "Boston"},
- []int{},
- }}
- cfg := ini.Empty()
- err = ini.ReflectFrom(cfg, a)
- // ...
-}
-```
-
-瞧瞧,奇迹发生了。
-
-```ini
-NAME = Unknwon
-Male = true
-Age = 21
-GPA = 2.8
-
-[Embeded]
-Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
-places = HangZhou,Boston
-```
-
-#### 名称映射器(Name Mapper)
-
-为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
-
-目前有 2 款内置的映射器:
-
-- `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
-- `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
-
-使用方法:
-
-```go
-type Info struct{
- PackageName string
-}
-
-func main() {
- err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
- // ...
-
- cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
- // ...
- info := new(Info)
- cfg.NameMapper = ini.AllCapsUnderscore
- err = cfg.MapTo(info)
- // ...
-}
-```
-
-使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
-
-#### 值映射器(Value Mapper)
-
-值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
-
-```go
-type Env struct {
- Foo string `ini:"foo"`
-}
-
-func main() {
- cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
- cfg.ValueMapper = os.ExpandEnv
- // ...
- env := &Env{}
- err = cfg.Section("env").MapTo(env)
-}
-```
-
-本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
-
-#### 映射/反射的其它说明
-
-任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
-
-```go
-type Child struct {
- Age string
-}
-
-type Parent struct {
- Name string
- Child
-}
-
-type Config struct {
- City string
- Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-
-[Child]
-Age = 21
-```
-
-很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
-
-```go
-type Child struct {
- Age string
-}
-
-type Parent struct {
- Name string
- Child `ini:"Parent"`
-}
-
-type Config struct {
- City string
- Parent
-}
-```
-
-示例配置文件:
-
-```ini
-City = Boston
-
-[Parent]
-Name = Unknwon
-Age = 21
-```
-
-## 获取帮助
-
-- [API 文档](https://gowalker.org/gopkg.in/ini.v1)
-- [创建工单](https://github.com/go-ini/ini/issues/new)
-
-## 常见问题
-
-### 字段 `BlockMode` 是什么?
-
-默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
-
-### 为什么要写另一个 INI 解析库?
-
-许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
-
-为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
diff --git a/vendor/gopkg.in/ini.v1/error.go b/vendor/gopkg.in/ini.v1/error.go
deleted file mode 100644
index 80afe74..0000000
--- a/vendor/gopkg.in/ini.v1/error.go
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 2016 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "fmt"
-)
-
-type ErrDelimiterNotFound struct {
- Line string
-}
-
-func IsErrDelimiterNotFound(err error) bool {
- _, ok := err.(ErrDelimiterNotFound)
- return ok
-}
-
-func (err ErrDelimiterNotFound) Error() string {
- return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
-}
diff --git a/vendor/gopkg.in/ini.v1/ini.go b/vendor/gopkg.in/ini.v1/ini.go
deleted file mode 100644
index 0bd6c50..0000000
--- a/vendor/gopkg.in/ini.v1/ini.go
+++ /dev/null
@@ -1,561 +0,0 @@
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-// Package ini provides INI file read and write functionality in Go.
-package ini
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "regexp"
- "runtime"
- "strconv"
- "strings"
- "sync"
- "time"
-)
-
-const (
- // Name for default section. You can use this constant or the string literal.
- // In most of cases, an empty string is all you need to access the section.
- DEFAULT_SECTION = "DEFAULT"
-
- // Maximum allowed depth when recursively substituing variable names.
- _DEPTH_VALUES = 99
- _VERSION = "1.28.0"
-)
-
-// Version returns current package version literal.
-func Version() string {
- return _VERSION
-}
-
-var (
- // Delimiter to determine or compose a new line.
- // This variable will be changed to "\r\n" automatically on Windows
- // at package init time.
- LineBreak = "\n"
-
- // Variable regexp pattern: %(variable)s
- varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
-
- // Indicate whether to align "=" sign with spaces to produce pretty output
- // or reduce all possible spaces for compact format.
- PrettyFormat = true
-
- // Explicitly write DEFAULT section header
- DefaultHeader = false
-
- // Indicate whether to put a line between sections
- PrettySection = true
-)
-
-func init() {
- if runtime.GOOS == "windows" {
- LineBreak = "\r\n"
- }
-}
-
-func inSlice(str string, s []string) bool {
- for _, v := range s {
- if str == v {
- return true
- }
- }
- return false
-}
-
-// dataSource is an interface that returns object which can be read and closed.
-type dataSource interface {
- ReadCloser() (io.ReadCloser, error)
-}
-
-// sourceFile represents an object that contains content on the local file system.
-type sourceFile struct {
- name string
-}
-
-func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
- return os.Open(s.name)
-}
-
-type bytesReadCloser struct {
- reader io.Reader
-}
-
-func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
- return rc.reader.Read(p)
-}
-
-func (rc *bytesReadCloser) Close() error {
- return nil
-}
-
-// sourceData represents an object that contains content in memory.
-type sourceData struct {
- data []byte
-}
-
-func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
- return ioutil.NopCloser(bytes.NewReader(s.data)), nil
-}
-
-// sourceReadCloser represents an input stream with Close method.
-type sourceReadCloser struct {
- reader io.ReadCloser
-}
-
-func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
- return s.reader, nil
-}
-
-// File represents a combination of a or more INI file(s) in memory.
-type File struct {
- // Should make things safe, but sometimes doesn't matter.
- BlockMode bool
- // Make sure data is safe in multiple goroutines.
- lock sync.RWMutex
-
- // Allow combination of multiple data sources.
- dataSources []dataSource
- // Actual data is stored here.
- sections map[string]*Section
-
- // To keep data in order.
- sectionList []string
-
- options LoadOptions
-
- NameMapper
- ValueMapper
-}
-
-// newFile initializes File object with given data sources.
-func newFile(dataSources []dataSource, opts LoadOptions) *File {
- return &File{
- BlockMode: true,
- dataSources: dataSources,
- sections: make(map[string]*Section),
- sectionList: make([]string, 0, 10),
- options: opts,
- }
-}
-
-func parseDataSource(source interface{}) (dataSource, error) {
- switch s := source.(type) {
- case string:
- return sourceFile{s}, nil
- case []byte:
- return &sourceData{s}, nil
- case io.ReadCloser:
- return &sourceReadCloser{s}, nil
- default:
- return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
- }
-}
-
-type LoadOptions struct {
- // Loose indicates whether the parser should ignore nonexistent files or return error.
- Loose bool
- // Insensitive indicates whether the parser forces all section and key names to lowercase.
- Insensitive bool
- // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
- IgnoreContinuation bool
- // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
- IgnoreInlineComment bool
- // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
- // This type of keys are mostly used in my.cnf.
- AllowBooleanKeys bool
- // AllowShadows indicates whether to keep track of keys with same name under same section.
- AllowShadows bool
- // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
- // conform to key/value pairs. Specify the names of those blocks here.
- UnparseableSections []string
-}
-
-func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
- sources := make([]dataSource, len(others)+1)
- sources[0], err = parseDataSource(source)
- if err != nil {
- return nil, err
- }
- for i := range others {
- sources[i+1], err = parseDataSource(others[i])
- if err != nil {
- return nil, err
- }
- }
- f := newFile(sources, opts)
- if err = f.Reload(); err != nil {
- return nil, err
- }
- return f, nil
-}
-
-// Load loads and parses from INI data sources.
-// Arguments can be mixed of file name with string type, or raw data in []byte.
-// It will return error if list contains nonexistent files.
-func Load(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{}, source, others...)
-}
-
-// LooseLoad has exactly same functionality as Load function
-// except it ignores nonexistent files instead of returning error.
-func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{Loose: true}, source, others...)
-}
-
-// InsensitiveLoad has exactly same functionality as Load function
-// except it forces all section and key names to be lowercased.
-func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{Insensitive: true}, source, others...)
-}
-
-// InsensitiveLoad has exactly same functionality as Load function
-// except it allows have shadow keys.
-func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
- return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
-}
-
-// Empty returns an empty file object.
-func Empty() *File {
- // Ignore error here, we sure our data is good.
- f, _ := Load([]byte(""))
- return f
-}
-
-// NewSection creates a new section.
-func (f *File) NewSection(name string) (*Section, error) {
- if len(name) == 0 {
- return nil, errors.New("error creating new section: empty section name")
- } else if f.options.Insensitive && name != DEFAULT_SECTION {
- name = strings.ToLower(name)
- }
-
- if f.BlockMode {
- f.lock.Lock()
- defer f.lock.Unlock()
- }
-
- if inSlice(name, f.sectionList) {
- return f.sections[name], nil
- }
-
- f.sectionList = append(f.sectionList, name)
- f.sections[name] = newSection(f, name)
- return f.sections[name], nil
-}
-
-// NewRawSection creates a new section with an unparseable body.
-func (f *File) NewRawSection(name, body string) (*Section, error) {
- section, err := f.NewSection(name)
- if err != nil {
- return nil, err
- }
-
- section.isRawSection = true
- section.rawBody = body
- return section, nil
-}
-
-// NewSections creates a list of sections.
-func (f *File) NewSections(names ...string) (err error) {
- for _, name := range names {
- if _, err = f.NewSection(name); err != nil {
- return err
- }
- }
- return nil
-}
-
-// GetSection returns section by given name.
-func (f *File) GetSection(name string) (*Section, error) {
- if len(name) == 0 {
- name = DEFAULT_SECTION
- } else if f.options.Insensitive {
- name = strings.ToLower(name)
- }
-
- if f.BlockMode {
- f.lock.RLock()
- defer f.lock.RUnlock()
- }
-
- sec := f.sections[name]
- if sec == nil {
- return nil, fmt.Errorf("section '%s' does not exist", name)
- }
- return sec, nil
-}
-
-// Section assumes named section exists and returns a zero-value when not.
-func (f *File) Section(name string) *Section {
- sec, err := f.GetSection(name)
- if err != nil {
- // Note: It's OK here because the only possible error is empty section name,
- // but if it's empty, this piece of code won't be executed.
- sec, _ = f.NewSection(name)
- return sec
- }
- return sec
-}
-
-// Section returns list of Section.
-func (f *File) Sections() []*Section {
- sections := make([]*Section, len(f.sectionList))
- for i := range f.sectionList {
- sections[i] = f.Section(f.sectionList[i])
- }
- return sections
-}
-
-// ChildSections returns a list of child sections of given section name.
-func (f *File) ChildSections(name string) []*Section {
- return f.Section(name).ChildSections()
-}
-
-// SectionStrings returns list of section names.
-func (f *File) SectionStrings() []string {
- list := make([]string, len(f.sectionList))
- copy(list, f.sectionList)
- return list
-}
-
-// DeleteSection deletes a section.
-func (f *File) DeleteSection(name string) {
- if f.BlockMode {
- f.lock.Lock()
- defer f.lock.Unlock()
- }
-
- if len(name) == 0 {
- name = DEFAULT_SECTION
- }
-
- for i, s := range f.sectionList {
- if s == name {
- f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
- delete(f.sections, name)
- return
- }
- }
-}
-
-func (f *File) reload(s dataSource) error {
- r, err := s.ReadCloser()
- if err != nil {
- return err
- }
- defer r.Close()
-
- return f.parse(r)
-}
-
-// Reload reloads and parses all data sources.
-func (f *File) Reload() (err error) {
- for _, s := range f.dataSources {
- if err = f.reload(s); err != nil {
- // In loose mode, we create an empty default section for nonexistent files.
- if os.IsNotExist(err) && f.options.Loose {
- f.parse(bytes.NewBuffer(nil))
- continue
- }
- return err
- }
- }
- return nil
-}
-
-// Append appends one or more data sources and reloads automatically.
-func (f *File) Append(source interface{}, others ...interface{}) error {
- ds, err := parseDataSource(source)
- if err != nil {
- return err
- }
- f.dataSources = append(f.dataSources, ds)
- for _, s := range others {
- ds, err = parseDataSource(s)
- if err != nil {
- return err
- }
- f.dataSources = append(f.dataSources, ds)
- }
- return f.Reload()
-}
-
-// WriteToIndent writes content into io.Writer with given indention.
-// If PrettyFormat has been set to be true,
-// it will align "=" sign with spaces under each section.
-func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
- equalSign := "="
- if PrettyFormat {
- equalSign = " = "
- }
-
- // Use buffer to make sure target is safe until finish encoding.
- buf := bytes.NewBuffer(nil)
- for i, sname := range f.sectionList {
- sec := f.Section(sname)
- if len(sec.Comment) > 0 {
- if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
- sec.Comment = "; " + sec.Comment
- }
- if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
- return 0, err
- }
- }
-
- if i > 0 || DefaultHeader {
- if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
- return 0, err
- }
- } else {
- // Write nothing if default section is empty
- if len(sec.keyList) == 0 {
- continue
- }
- }
-
- if sec.isRawSection {
- if _, err = buf.WriteString(sec.rawBody); err != nil {
- return 0, err
- }
- continue
- }
-
- // Count and generate alignment length and buffer spaces using the
- // longest key. Keys may be modifed if they contain certain characters so
- // we need to take that into account in our calculation.
- alignLength := 0
- if PrettyFormat {
- for _, kname := range sec.keyList {
- keyLength := len(kname)
- // First case will surround key by ` and second by """
- if strings.ContainsAny(kname, "\"=:") {
- keyLength += 2
- } else if strings.Contains(kname, "`") {
- keyLength += 6
- }
-
- if keyLength > alignLength {
- alignLength = keyLength
- }
- }
- }
- alignSpaces := bytes.Repeat([]byte(" "), alignLength)
-
- KEY_LIST:
- for _, kname := range sec.keyList {
- key := sec.Key(kname)
- if len(key.Comment) > 0 {
- if len(indent) > 0 && sname != DEFAULT_SECTION {
- buf.WriteString(indent)
- }
- if key.Comment[0] != '#' && key.Comment[0] != ';' {
- key.Comment = "; " + key.Comment
- }
- if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
- return 0, err
- }
- }
-
- if len(indent) > 0 && sname != DEFAULT_SECTION {
- buf.WriteString(indent)
- }
-
- switch {
- case key.isAutoIncrement:
- kname = "-"
- case strings.ContainsAny(kname, "\"=:"):
- kname = "`" + kname + "`"
- case strings.Contains(kname, "`"):
- kname = `"""` + kname + `"""`
- }
-
- for _, val := range key.ValueWithShadows() {
- if _, err = buf.WriteString(kname); err != nil {
- return 0, err
- }
-
- if key.isBooleanType {
- if kname != sec.keyList[len(sec.keyList)-1] {
- buf.WriteString(LineBreak)
- }
- continue KEY_LIST
- }
-
- // Write out alignment spaces before "=" sign
- if PrettyFormat {
- buf.Write(alignSpaces[:alignLength-len(kname)])
- }
-
- // In case key value contains "\n", "`", "\"", "#" or ";"
- if strings.ContainsAny(val, "\n`") {
- val = `"""` + val + `"""`
- } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
- val = "`" + val + "`"
- }
- if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
- return 0, err
- }
- }
- }
-
- if PrettySection {
- // Put a line between sections
- if _, err = buf.WriteString(LineBreak); err != nil {
- return 0, err
- }
- }
- }
-
- return buf.WriteTo(w)
-}
-
-// WriteTo writes file content into io.Writer.
-func (f *File) WriteTo(w io.Writer) (int64, error) {
- return f.WriteToIndent(w, "")
-}
-
-// SaveToIndent writes content to file system with given value indention.
-func (f *File) SaveToIndent(filename, indent string) error {
- // Note: Because we are truncating with os.Create,
- // so it's safer to save to a temporary file location and rename afte done.
- tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
- defer os.Remove(tmpPath)
-
- fw, err := os.Create(tmpPath)
- if err != nil {
- return err
- }
-
- if _, err = f.WriteToIndent(fw, indent); err != nil {
- fw.Close()
- return err
- }
- fw.Close()
-
- // Remove old file and rename the new one.
- os.Remove(filename)
- return os.Rename(tmpPath, filename)
-}
-
-// SaveTo writes content to file system.
-func (f *File) SaveTo(filename string) error {
- return f.SaveToIndent(filename, "")
-}
diff --git a/vendor/gopkg.in/ini.v1/key.go b/vendor/gopkg.in/ini.v1/key.go
deleted file mode 100644
index 838356a..0000000
--- a/vendor/gopkg.in/ini.v1/key.go
+++ /dev/null
@@ -1,699 +0,0 @@
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "errors"
- "fmt"
- "strconv"
- "strings"
- "time"
-)
-
-// Key represents a key under a section.
-type Key struct {
- s *Section
- name string
- value string
- isAutoIncrement bool
- isBooleanType bool
-
- isShadow bool
- shadows []*Key
-
- Comment string
-}
-
-// newKey simply return a key object with given values.
-func newKey(s *Section, name, val string) *Key {
- return &Key{
- s: s,
- name: name,
- value: val,
- }
-}
-
-func (k *Key) addShadow(val string) error {
- if k.isShadow {
- return errors.New("cannot add shadow to another shadow key")
- } else if k.isAutoIncrement || k.isBooleanType {
- return errors.New("cannot add shadow to auto-increment or boolean key")
- }
-
- shadow := newKey(k.s, k.name, val)
- shadow.isShadow = true
- k.shadows = append(k.shadows, shadow)
- return nil
-}
-
-// AddShadow adds a new shadow key to itself.
-func (k *Key) AddShadow(val string) error {
- if !k.s.f.options.AllowShadows {
- return errors.New("shadow key is not allowed")
- }
- return k.addShadow(val)
-}
-
-// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
-type ValueMapper func(string) string
-
-// Name returns name of key.
-func (k *Key) Name() string {
- return k.name
-}
-
-// Value returns raw value of key for performance purpose.
-func (k *Key) Value() string {
- return k.value
-}
-
-// ValueWithShadows returns raw values of key and its shadows if any.
-func (k *Key) ValueWithShadows() []string {
- if len(k.shadows) == 0 {
- return []string{k.value}
- }
- vals := make([]string, len(k.shadows)+1)
- vals[0] = k.value
- for i := range k.shadows {
- vals[i+1] = k.shadows[i].value
- }
- return vals
-}
-
-// transformValue takes a raw value and transforms to its final string.
-func (k *Key) transformValue(val string) string {
- if k.s.f.ValueMapper != nil {
- val = k.s.f.ValueMapper(val)
- }
-
- // Fail-fast if no indicate char found for recursive value
- if !strings.Contains(val, "%") {
- return val
- }
- for i := 0; i < _DEPTH_VALUES; i++ {
- vr := varPattern.FindString(val)
- if len(vr) == 0 {
- break
- }
-
- // Take off leading '%(' and trailing ')s'.
- noption := strings.TrimLeft(vr, "%(")
- noption = strings.TrimRight(noption, ")s")
-
- // Search in the same section.
- nk, err := k.s.GetKey(noption)
- if err != nil {
- // Search again in default section.
- nk, _ = k.s.f.Section("").GetKey(noption)
- }
-
- // Substitute by new value and take off leading '%(' and trailing ')s'.
- val = strings.Replace(val, vr, nk.value, -1)
- }
- return val
-}
-
-// String returns string representation of value.
-func (k *Key) String() string {
- return k.transformValue(k.value)
-}
-
-// Validate accepts a validate function which can
-// return modifed result as key value.
-func (k *Key) Validate(fn func(string) string) string {
- return fn(k.String())
-}
-
-// parseBool returns the boolean value represented by the string.
-//
-// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
-// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
-// Any other value returns an error.
-func parseBool(str string) (value bool, err error) {
- switch str {
- case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
- return true, nil
- case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
- return false, nil
- }
- return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
-}
-
-// Bool returns bool type value.
-func (k *Key) Bool() (bool, error) {
- return parseBool(k.String())
-}
-
-// Float64 returns float64 type value.
-func (k *Key) Float64() (float64, error) {
- return strconv.ParseFloat(k.String(), 64)
-}
-
-// Int returns int type value.
-func (k *Key) Int() (int, error) {
- return strconv.Atoi(k.String())
-}
-
-// Int64 returns int64 type value.
-func (k *Key) Int64() (int64, error) {
- return strconv.ParseInt(k.String(), 10, 64)
-}
-
-// Uint returns uint type valued.
-func (k *Key) Uint() (uint, error) {
- u, e := strconv.ParseUint(k.String(), 10, 64)
- return uint(u), e
-}
-
-// Uint64 returns uint64 type value.
-func (k *Key) Uint64() (uint64, error) {
- return strconv.ParseUint(k.String(), 10, 64)
-}
-
-// Duration returns time.Duration type value.
-func (k *Key) Duration() (time.Duration, error) {
- return time.ParseDuration(k.String())
-}
-
-// TimeFormat parses with given format and returns time.Time type value.
-func (k *Key) TimeFormat(format string) (time.Time, error) {
- return time.Parse(format, k.String())
-}
-
-// Time parses with RFC3339 format and returns time.Time type value.
-func (k *Key) Time() (time.Time, error) {
- return k.TimeFormat(time.RFC3339)
-}
-
-// MustString returns default value if key value is empty.
-func (k *Key) MustString(defaultVal string) string {
- val := k.String()
- if len(val) == 0 {
- k.value = defaultVal
- return defaultVal
- }
- return val
-}
-
-// MustBool always returns value without error,
-// it returns false if error occurs.
-func (k *Key) MustBool(defaultVal ...bool) bool {
- val, err := k.Bool()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatBool(defaultVal[0])
- return defaultVal[0]
- }
- return val
-}
-
-// MustFloat64 always returns value without error,
-// it returns 0.0 if error occurs.
-func (k *Key) MustFloat64(defaultVal ...float64) float64 {
- val, err := k.Float64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
- return defaultVal[0]
- }
- return val
-}
-
-// MustInt always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt(defaultVal ...int) int {
- val, err := k.Int()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustInt64 always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustInt64(defaultVal ...int64) int64 {
- val, err := k.Int64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatInt(defaultVal[0], 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustUint always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustUint(defaultVal ...uint) uint {
- val, err := k.Uint()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustUint64 always returns value without error,
-// it returns 0 if error occurs.
-func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
- val, err := k.Uint64()
- if len(defaultVal) > 0 && err != nil {
- k.value = strconv.FormatUint(defaultVal[0], 10)
- return defaultVal[0]
- }
- return val
-}
-
-// MustDuration always returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
- val, err := k.Duration()
- if len(defaultVal) > 0 && err != nil {
- k.value = defaultVal[0].String()
- return defaultVal[0]
- }
- return val
-}
-
-// MustTimeFormat always parses with given format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
- val, err := k.TimeFormat(format)
- if len(defaultVal) > 0 && err != nil {
- k.value = defaultVal[0].Format(format)
- return defaultVal[0]
- }
- return val
-}
-
-// MustTime always parses with RFC3339 format and returns value without error,
-// it returns zero value if error occurs.
-func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
- return k.MustTimeFormat(time.RFC3339, defaultVal...)
-}
-
-// In always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) In(defaultVal string, candidates []string) string {
- val := k.String()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InFloat64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
- val := k.MustFloat64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InInt always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt(defaultVal int, candidates []int) int {
- val := k.MustInt()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InInt64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
- val := k.MustInt64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InUint always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
- val := k.MustUint()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InUint64 always returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
- val := k.MustUint64()
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InTimeFormat always parses with given format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
- val := k.MustTimeFormat(format)
- for _, cand := range candidates {
- if val == cand {
- return val
- }
- }
- return defaultVal
-}
-
-// InTime always parses with RFC3339 format and returns value without error,
-// it returns default value if error occurs or doesn't fit into candidates.
-func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
- return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
-}
-
-// RangeFloat64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
- val := k.MustFloat64()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeInt checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt(defaultVal, min, max int) int {
- val := k.MustInt()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeInt64 checks if value is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
- val := k.MustInt64()
- if val < min || val > max {
- return defaultVal
- }
- return val
-}
-
-// RangeTimeFormat checks if value with given format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
- val := k.MustTimeFormat(format)
- if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
- return defaultVal
- }
- return val
-}
-
-// RangeTime checks if value with RFC3339 format is in given range inclusively,
-// and returns default value if it's not.
-func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
- return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
-}
-
-// Strings returns list of string divided by given delimiter.
-func (k *Key) Strings(delim string) []string {
- str := k.String()
- if len(str) == 0 {
- return []string{}
- }
-
- vals := strings.Split(str, delim)
- for i := range vals {
- // vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
- vals[i] = strings.TrimSpace(vals[i])
- }
- return vals
-}
-
-// StringsWithShadows returns list of string divided by given delimiter.
-// Shadows will also be appended if any.
-func (k *Key) StringsWithShadows(delim string) []string {
- vals := k.ValueWithShadows()
- results := make([]string, 0, len(vals)*2)
- for i := range vals {
- if len(vals) == 0 {
- continue
- }
-
- results = append(results, strings.Split(vals[i], delim)...)
- }
-
- for i := range results {
- results[i] = k.transformValue(strings.TrimSpace(results[i]))
- }
- return results
-}
-
-// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Float64s(delim string) []float64 {
- vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
- return vals
-}
-
-// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Ints(delim string) []int {
- vals, _ := k.parseInts(k.Strings(delim), true, false)
- return vals
-}
-
-// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Int64s(delim string) []int64 {
- vals, _ := k.parseInt64s(k.Strings(delim), true, false)
- return vals
-}
-
-// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Uints(delim string) []uint {
- vals, _ := k.parseUints(k.Strings(delim), true, false)
- return vals
-}
-
-// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
-func (k *Key) Uint64s(delim string) []uint64 {
- vals, _ := k.parseUint64s(k.Strings(delim), true, false)
- return vals
-}
-
-// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
-// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
-func (k *Key) TimesFormat(format, delim string) []time.Time {
- vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
- return vals
-}
-
-// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
-// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
-func (k *Key) Times(delim string) []time.Time {
- return k.TimesFormat(time.RFC3339, delim)
-}
-
-// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
-// it will not be included to result list.
-func (k *Key) ValidFloat64s(delim string) []float64 {
- vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
-// not be included to result list.
-func (k *Key) ValidInts(delim string) []int {
- vals, _ := k.parseInts(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
-// then it will not be included to result list.
-func (k *Key) ValidInt64s(delim string) []int64 {
- vals, _ := k.parseInt64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
-// then it will not be included to result list.
-func (k *Key) ValidUints(delim string) []uint {
- vals, _ := k.parseUints(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
-// integer, then it will not be included to result list.
-func (k *Key) ValidUint64s(delim string) []uint64 {
- vals, _ := k.parseUint64s(k.Strings(delim), false, false)
- return vals
-}
-
-// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
-func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
- vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
- return vals
-}
-
-// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
-func (k *Key) ValidTimes(delim string) []time.Time {
- return k.ValidTimesFormat(time.RFC3339, delim)
-}
-
-// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
- return k.parseFloat64s(k.Strings(delim), false, true)
-}
-
-// StrictInts returns list of int divided by given delimiter or error on first invalid input.
-func (k *Key) StrictInts(delim string) ([]int, error) {
- return k.parseInts(k.Strings(delim), false, true)
-}
-
-// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictInt64s(delim string) ([]int64, error) {
- return k.parseInt64s(k.Strings(delim), false, true)
-}
-
-// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
-func (k *Key) StrictUints(delim string) ([]uint, error) {
- return k.parseUints(k.Strings(delim), false, true)
-}
-
-// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
-func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
- return k.parseUint64s(k.Strings(delim), false, true)
-}
-
-// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
-// or error on first invalid input.
-func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
- return k.parseTimesFormat(format, k.Strings(delim), false, true)
-}
-
-// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
-// or error on first invalid input.
-func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
- return k.StrictTimesFormat(time.RFC3339, delim)
-}
-
-// parseFloat64s transforms strings to float64s.
-func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
- vals := make([]float64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseFloat(str, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseInts transforms strings to ints.
-func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
- vals := make([]int, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.Atoi(str)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseInt64s transforms strings to int64s.
-func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
- vals := make([]int64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseInt(str, 10, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseUints transforms strings to uints.
-func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
- vals := make([]uint, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseUint(str, 10, 0)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, uint(val))
- }
- }
- return vals, nil
-}
-
-// parseUint64s transforms strings to uint64s.
-func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
- vals := make([]uint64, 0, len(strs))
- for _, str := range strs {
- val, err := strconv.ParseUint(str, 10, 64)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// parseTimesFormat transforms strings to times in given format.
-func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
- vals := make([]time.Time, 0, len(strs))
- for _, str := range strs {
- val, err := time.Parse(format, str)
- if err != nil && returnOnInvalid {
- return nil, err
- }
- if err == nil || addInvalid {
- vals = append(vals, val)
- }
- }
- return vals, nil
-}
-
-// SetValue changes key value.
-func (k *Key) SetValue(v string) {
- if k.s.f.BlockMode {
- k.s.f.lock.Lock()
- defer k.s.f.lock.Unlock()
- }
-
- k.value = v
- k.s.keysHash[k.name] = v
-}
diff --git a/vendor/gopkg.in/ini.v1/parser.go b/vendor/gopkg.in/ini.v1/parser.go
deleted file mode 100644
index 69d5476..0000000
--- a/vendor/gopkg.in/ini.v1/parser.go
+++ /dev/null
@@ -1,361 +0,0 @@
-// Copyright 2015 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "strconv"
- "strings"
- "unicode"
-)
-
-type tokenType int
-
-const (
- _TOKEN_INVALID tokenType = iota
- _TOKEN_COMMENT
- _TOKEN_SECTION
- _TOKEN_KEY
-)
-
-type parser struct {
- buf *bufio.Reader
- isEOF bool
- count int
- comment *bytes.Buffer
-}
-
-func newParser(r io.Reader) *parser {
- return &parser{
- buf: bufio.NewReader(r),
- count: 1,
- comment: &bytes.Buffer{},
- }
-}
-
-// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
-// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
-func (p *parser) BOM() error {
- mask, err := p.buf.Peek(2)
- if err != nil && err != io.EOF {
- return err
- } else if len(mask) < 2 {
- return nil
- }
-
- switch {
- case mask[0] == 254 && mask[1] == 255:
- fallthrough
- case mask[0] == 255 && mask[1] == 254:
- p.buf.Read(mask)
- case mask[0] == 239 && mask[1] == 187:
- mask, err := p.buf.Peek(3)
- if err != nil && err != io.EOF {
- return err
- } else if len(mask) < 3 {
- return nil
- }
- if mask[2] == 191 {
- p.buf.Read(mask)
- }
- }
- return nil
-}
-
-func (p *parser) readUntil(delim byte) ([]byte, error) {
- data, err := p.buf.ReadBytes(delim)
- if err != nil {
- if err == io.EOF {
- p.isEOF = true
- } else {
- return nil, err
- }
- }
- return data, nil
-}
-
-func cleanComment(in []byte) ([]byte, bool) {
- i := bytes.IndexAny(in, "#;")
- if i == -1 {
- return nil, false
- }
- return in[i:], true
-}
-
-func readKeyName(in []byte) (string, int, error) {
- line := string(in)
-
- // Check if key name surrounded by quotes.
- var keyQuote string
- if line[0] == '"' {
- if len(line) > 6 && string(line[0:3]) == `"""` {
- keyQuote = `"""`
- } else {
- keyQuote = `"`
- }
- } else if line[0] == '`' {
- keyQuote = "`"
- }
-
- // Get out key name
- endIdx := -1
- if len(keyQuote) > 0 {
- startIdx := len(keyQuote)
- // FIXME: fail case -> """"""name"""=value
- pos := strings.Index(line[startIdx:], keyQuote)
- if pos == -1 {
- return "", -1, fmt.Errorf("missing closing key quote: %s", line)
- }
- pos += startIdx
-
- // Find key-value delimiter
- i := strings.IndexAny(line[pos+startIdx:], "=:")
- if i < 0 {
- return "", -1, ErrDelimiterNotFound{line}
- }
- endIdx = pos + i
- return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
- }
-
- endIdx = strings.IndexAny(line, "=:")
- if endIdx < 0 {
- return "", -1, ErrDelimiterNotFound{line}
- }
- return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
-}
-
-func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
- for {
- data, err := p.readUntil('\n')
- if err != nil {
- return "", err
- }
- next := string(data)
-
- pos := strings.LastIndex(next, valQuote)
- if pos > -1 {
- val += next[:pos]
-
- comment, has := cleanComment([]byte(next[pos:]))
- if has {
- p.comment.Write(bytes.TrimSpace(comment))
- }
- break
- }
- val += next
- if p.isEOF {
- return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
- }
- }
- return val, nil
-}
-
-func (p *parser) readContinuationLines(val string) (string, error) {
- for {
- data, err := p.readUntil('\n')
- if err != nil {
- return "", err
- }
- next := strings.TrimSpace(string(data))
-
- if len(next) == 0 {
- break
- }
- val += next
- if val[len(val)-1] != '\\' {
- break
- }
- val = val[:len(val)-1]
- }
- return val, nil
-}
-
-// hasSurroundedQuote check if and only if the first and last characters
-// are quotes \" or \'.
-// It returns false if any other parts also contain same kind of quotes.
-func hasSurroundedQuote(in string, quote byte) bool {
- return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
- strings.IndexByte(in[1:], quote) == len(in)-2
-}
-
-func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
- line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
- if len(line) == 0 {
- return "", nil
- }
-
- var valQuote string
- if len(line) > 3 && string(line[0:3]) == `"""` {
- valQuote = `"""`
- } else if line[0] == '`' {
- valQuote = "`"
- }
-
- if len(valQuote) > 0 {
- startIdx := len(valQuote)
- pos := strings.LastIndex(line[startIdx:], valQuote)
- // Check for multi-line value
- if pos == -1 {
- return p.readMultilines(line, line[startIdx:], valQuote)
- }
-
- return line[startIdx : pos+startIdx], nil
- }
-
- // Won't be able to reach here if value only contains whitespace
- line = strings.TrimSpace(line)
-
- // Check continuation lines when desired
- if !ignoreContinuation && line[len(line)-1] == '\\' {
- return p.readContinuationLines(line[:len(line)-1])
- }
-
- // Check if ignore inline comment
- if !ignoreInlineComment {
- i := strings.IndexAny(line, "#;")
- if i > -1 {
- p.comment.WriteString(line[i:])
- line = strings.TrimSpace(line[:i])
- }
- }
-
- // Trim single quotes
- if hasSurroundedQuote(line, '\'') ||
- hasSurroundedQuote(line, '"') {
- line = line[1 : len(line)-1]
- }
- return line, nil
-}
-
-// parse parses data through an io.Reader.
-func (f *File) parse(reader io.Reader) (err error) {
- p := newParser(reader)
- if err = p.BOM(); err != nil {
- return fmt.Errorf("BOM: %v", err)
- }
-
- // Ignore error because default section name is never empty string.
- section, _ := f.NewSection(DEFAULT_SECTION)
-
- var line []byte
- var inUnparseableSection bool
- for !p.isEOF {
- line, err = p.readUntil('\n')
- if err != nil {
- return err
- }
-
- line = bytes.TrimLeftFunc(line, unicode.IsSpace)
- if len(line) == 0 {
- continue
- }
-
- // Comments
- if line[0] == '#' || line[0] == ';' {
- // Note: we do not care ending line break,
- // it is needed for adding second line,
- // so just clean it once at the end when set to value.
- p.comment.Write(line)
- continue
- }
-
- // Section
- if line[0] == '[' {
- // Read to the next ']' (TODO: support quoted strings)
- // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
- closeIdx := bytes.LastIndex(line, []byte("]"))
- if closeIdx == -1 {
- return fmt.Errorf("unclosed section: %s", line)
- }
-
- name := string(line[1:closeIdx])
- section, err = f.NewSection(name)
- if err != nil {
- return err
- }
-
- comment, has := cleanComment(line[closeIdx+1:])
- if has {
- p.comment.Write(comment)
- }
-
- section.Comment = strings.TrimSpace(p.comment.String())
-
- // Reset aotu-counter and comments
- p.comment.Reset()
- p.count = 1
-
- inUnparseableSection = false
- for i := range f.options.UnparseableSections {
- if f.options.UnparseableSections[i] == name ||
- (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
- inUnparseableSection = true
- continue
- }
- }
- continue
- }
-
- if inUnparseableSection {
- section.isRawSection = true
- section.rawBody += string(line)
- continue
- }
-
- kname, offset, err := readKeyName(line)
- if err != nil {
- // Treat as boolean key when desired, and whole line is key name.
- if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
- kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
- if err != nil {
- return err
- }
- key, err := section.NewBooleanKey(kname)
- if err != nil {
- return err
- }
- key.Comment = strings.TrimSpace(p.comment.String())
- p.comment.Reset()
- continue
- }
- return err
- }
-
- // Auto increment.
- isAutoIncr := false
- if kname == "-" {
- isAutoIncr = true
- kname = "#" + strconv.Itoa(p.count)
- p.count++
- }
-
- value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
- if err != nil {
- return err
- }
-
- key, err := section.NewKey(kname, value)
- if err != nil {
- return err
- }
- key.isAutoIncrement = isAutoIncr
- key.Comment = strings.TrimSpace(p.comment.String())
- p.comment.Reset()
- }
- return nil
-}
diff --git a/vendor/gopkg.in/ini.v1/section.go b/vendor/gopkg.in/ini.v1/section.go
deleted file mode 100644
index 94f7375..0000000
--- a/vendor/gopkg.in/ini.v1/section.go
+++ /dev/null
@@ -1,248 +0,0 @@
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "errors"
- "fmt"
- "strings"
-)
-
-// Section represents a config section.
-type Section struct {
- f *File
- Comment string
- name string
- keys map[string]*Key
- keyList []string
- keysHash map[string]string
-
- isRawSection bool
- rawBody string
-}
-
-func newSection(f *File, name string) *Section {
- return &Section{
- f: f,
- name: name,
- keys: make(map[string]*Key),
- keyList: make([]string, 0, 10),
- keysHash: make(map[string]string),
- }
-}
-
-// Name returns name of Section.
-func (s *Section) Name() string {
- return s.name
-}
-
-// Body returns rawBody of Section if the section was marked as unparseable.
-// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
-func (s *Section) Body() string {
- return strings.TrimSpace(s.rawBody)
-}
-
-// NewKey creates a new key to given section.
-func (s *Section) NewKey(name, val string) (*Key, error) {
- if len(name) == 0 {
- return nil, errors.New("error creating new key: empty key name")
- } else if s.f.options.Insensitive {
- name = strings.ToLower(name)
- }
-
- if s.f.BlockMode {
- s.f.lock.Lock()
- defer s.f.lock.Unlock()
- }
-
- if inSlice(name, s.keyList) {
- if s.f.options.AllowShadows {
- if err := s.keys[name].addShadow(val); err != nil {
- return nil, err
- }
- } else {
- s.keys[name].value = val
- }
- return s.keys[name], nil
- }
-
- s.keyList = append(s.keyList, name)
- s.keys[name] = newKey(s, name, val)
- s.keysHash[name] = val
- return s.keys[name], nil
-}
-
-// NewBooleanKey creates a new boolean type key to given section.
-func (s *Section) NewBooleanKey(name string) (*Key, error) {
- key, err := s.NewKey(name, "true")
- if err != nil {
- return nil, err
- }
-
- key.isBooleanType = true
- return key, nil
-}
-
-// GetKey returns key in section by given name.
-func (s *Section) GetKey(name string) (*Key, error) {
- // FIXME: change to section level lock?
- if s.f.BlockMode {
- s.f.lock.RLock()
- }
- if s.f.options.Insensitive {
- name = strings.ToLower(name)
- }
- key := s.keys[name]
- if s.f.BlockMode {
- s.f.lock.RUnlock()
- }
-
- if key == nil {
- // Check if it is a child-section.
- sname := s.name
- for {
- if i := strings.LastIndex(sname, "."); i > -1 {
- sname = sname[:i]
- sec, err := s.f.GetSection(sname)
- if err != nil {
- continue
- }
- return sec.GetKey(name)
- } else {
- break
- }
- }
- return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
- }
- return key, nil
-}
-
-// HasKey returns true if section contains a key with given name.
-func (s *Section) HasKey(name string) bool {
- key, _ := s.GetKey(name)
- return key != nil
-}
-
-// Haskey is a backwards-compatible name for HasKey.
-func (s *Section) Haskey(name string) bool {
- return s.HasKey(name)
-}
-
-// HasValue returns true if section contains given raw value.
-func (s *Section) HasValue(value string) bool {
- if s.f.BlockMode {
- s.f.lock.RLock()
- defer s.f.lock.RUnlock()
- }
-
- for _, k := range s.keys {
- if value == k.value {
- return true
- }
- }
- return false
-}
-
-// Key assumes named Key exists in section and returns a zero-value when not.
-func (s *Section) Key(name string) *Key {
- key, err := s.GetKey(name)
- if err != nil {
- // It's OK here because the only possible error is empty key name,
- // but if it's empty, this piece of code won't be executed.
- key, _ = s.NewKey(name, "")
- return key
- }
- return key
-}
-
-// Keys returns list of keys of section.
-func (s *Section) Keys() []*Key {
- keys := make([]*Key, len(s.keyList))
- for i := range s.keyList {
- keys[i] = s.Key(s.keyList[i])
- }
- return keys
-}
-
-// ParentKeys returns list of keys of parent section.
-func (s *Section) ParentKeys() []*Key {
- var parentKeys []*Key
- sname := s.name
- for {
- if i := strings.LastIndex(sname, "."); i > -1 {
- sname = sname[:i]
- sec, err := s.f.GetSection(sname)
- if err != nil {
- continue
- }
- parentKeys = append(parentKeys, sec.Keys()...)
- } else {
- break
- }
-
- }
- return parentKeys
-}
-
-// KeyStrings returns list of key names of section.
-func (s *Section) KeyStrings() []string {
- list := make([]string, len(s.keyList))
- copy(list, s.keyList)
- return list
-}
-
-// KeysHash returns keys hash consisting of names and values.
-func (s *Section) KeysHash() map[string]string {
- if s.f.BlockMode {
- s.f.lock.RLock()
- defer s.f.lock.RUnlock()
- }
-
- hash := map[string]string{}
- for key, value := range s.keysHash {
- hash[key] = value
- }
- return hash
-}
-
-// DeleteKey deletes a key from section.
-func (s *Section) DeleteKey(name string) {
- if s.f.BlockMode {
- s.f.lock.Lock()
- defer s.f.lock.Unlock()
- }
-
- for i, k := range s.keyList {
- if k == name {
- s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
- delete(s.keys, name)
- return
- }
- }
-}
-
-// ChildSections returns a list of child sections of current section.
-// For example, "[parent.child1]" and "[parent.child12]" are child sections
-// of section "[parent]".
-func (s *Section) ChildSections() []*Section {
- prefix := s.name + "."
- children := make([]*Section, 0, 3)
- for _, name := range s.f.sectionList {
- if strings.HasPrefix(name, prefix) {
- children = append(children, s.f.sections[name])
- }
- }
- return children
-}
diff --git a/vendor/gopkg.in/ini.v1/struct.go b/vendor/gopkg.in/ini.v1/struct.go
deleted file mode 100644
index d522e00..0000000
--- a/vendor/gopkg.in/ini.v1/struct.go
+++ /dev/null
@@ -1,499 +0,0 @@
-// Copyright 2014 Unknwon
-//
-// Licensed under the Apache License, Version 2.0 (the "License"): you may
-// not use this file except in compliance with the License. You may obtain
-// a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations
-// under the License.
-
-package ini
-
-import (
- "bytes"
- "errors"
- "fmt"
- "reflect"
- "strings"
- "time"
- "unicode"
-)
-
-// NameMapper represents a ini tag name mapper.
-type NameMapper func(string) string
-
-// Built-in name getters.
-var (
- // AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
- AllCapsUnderscore NameMapper = func(raw string) string {
- newstr := make([]rune, 0, len(raw))
- for i, chr := range raw {
- if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
- if i > 0 {
- newstr = append(newstr, '_')
- }
- }
- newstr = append(newstr, unicode.ToUpper(chr))
- }
- return string(newstr)
- }
- // TitleUnderscore converts to format title_underscore.
- TitleUnderscore NameMapper = func(raw string) string {
- newstr := make([]rune, 0, len(raw))
- for i, chr := range raw {
- if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
- if i > 0 {
- newstr = append(newstr, '_')
- }
- chr -= ('A' - 'a')
- }
- newstr = append(newstr, chr)
- }
- return string(newstr)
- }
-)
-
-func (s *Section) parseFieldName(raw, actual string) string {
- if len(actual) > 0 {
- return actual
- }
- if s.f.NameMapper != nil {
- return s.f.NameMapper(raw)
- }
- return raw
-}
-
-func parseDelim(actual string) string {
- if len(actual) > 0 {
- return actual
- }
- return ","
-}
-
-var reflectTime = reflect.TypeOf(time.Now()).Kind()
-
-// setSliceWithProperType sets proper values to slice based on its type.
-func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
- var strs []string
- if allowShadow {
- strs = key.StringsWithShadows(delim)
- } else {
- strs = key.Strings(delim)
- }
-
- numVals := len(strs)
- if numVals == 0 {
- return nil
- }
-
- var vals interface{}
- var err error
-
- sliceOf := field.Type().Elem().Kind()
- switch sliceOf {
- case reflect.String:
- vals = strs
- case reflect.Int:
- vals, err = key.parseInts(strs, true, false)
- case reflect.Int64:
- vals, err = key.parseInt64s(strs, true, false)
- case reflect.Uint:
- vals, err = key.parseUints(strs, true, false)
- case reflect.Uint64:
- vals, err = key.parseUint64s(strs, true, false)
- case reflect.Float64:
- vals, err = key.parseFloat64s(strs, true, false)
- case reflectTime:
- vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
- default:
- return fmt.Errorf("unsupported type '[]%s'", sliceOf)
- }
- if isStrict {
- return err
- }
-
- slice := reflect.MakeSlice(field.Type(), numVals, numVals)
- for i := 0; i < numVals; i++ {
- switch sliceOf {
- case reflect.String:
- slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
- case reflect.Int:
- slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
- case reflect.Int64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
- case reflect.Uint:
- slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
- case reflect.Uint64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
- case reflect.Float64:
- slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
- case reflectTime:
- slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
- }
- }
- field.Set(slice)
- return nil
-}
-
-func wrapStrictError(err error, isStrict bool) error {
- if isStrict {
- return err
- }
- return nil
-}
-
-// setWithProperType sets proper value to field based on its type,
-// but it does not return error for failing parsing,
-// because we want to use default value that is already assigned to strcut.
-func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
- switch t.Kind() {
- case reflect.String:
- if len(key.String()) == 0 {
- return nil
- }
- field.SetString(key.String())
- case reflect.Bool:
- boolVal, err := key.Bool()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetBool(boolVal)
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- durationVal, err := key.Duration()
- // Skip zero value
- if err == nil && int(durationVal) > 0 {
- field.Set(reflect.ValueOf(durationVal))
- return nil
- }
-
- intVal, err := key.Int64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetInt(intVal)
- // byte is an alias for uint8, so supporting uint8 breaks support for byte
- case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- durationVal, err := key.Duration()
- // Skip zero value
- if err == nil && int(durationVal) > 0 {
- field.Set(reflect.ValueOf(durationVal))
- return nil
- }
-
- uintVal, err := key.Uint64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetUint(uintVal)
-
- case reflect.Float32, reflect.Float64:
- floatVal, err := key.Float64()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.SetFloat(floatVal)
- case reflectTime:
- timeVal, err := key.Time()
- if err != nil {
- return wrapStrictError(err, isStrict)
- }
- field.Set(reflect.ValueOf(timeVal))
- case reflect.Slice:
- return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
- default:
- return fmt.Errorf("unsupported type '%s'", t)
- }
- return nil
-}
-
-func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
- opts := strings.SplitN(tag, ",", 3)
- rawName = opts[0]
- if len(opts) > 1 {
- omitEmpty = opts[1] == "omitempty"
- }
- if len(opts) > 2 {
- allowShadow = opts[2] == "allowshadow"
- }
- return rawName, omitEmpty, allowShadow
-}
-
-func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
- if val.Kind() == reflect.Ptr {
- val = val.Elem()
- }
- typ := val.Type()
-
- for i := 0; i < typ.NumField(); i++ {
- field := val.Field(i)
- tpField := typ.Field(i)
-
- tag := tpField.Tag.Get("ini")
- if tag == "-" {
- continue
- }
-
- rawName, _, allowShadow := parseTagOptions(tag)
- fieldName := s.parseFieldName(tpField.Name, rawName)
- if len(fieldName) == 0 || !field.CanSet() {
- continue
- }
-
- isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
- isStruct := tpField.Type.Kind() == reflect.Struct
- if isAnonymous {
- field.Set(reflect.New(tpField.Type.Elem()))
- }
-
- if isAnonymous || isStruct {
- if sec, err := s.f.GetSection(fieldName); err == nil {
- if err = sec.mapTo(field, isStrict); err != nil {
- return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
- }
- continue
- }
- }
-
- if key, err := s.GetKey(fieldName); err == nil {
- delim := parseDelim(tpField.Tag.Get("delim"))
- if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
- return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
- }
- }
- }
- return nil
-}
-
-// MapTo maps section to given struct.
-func (s *Section) MapTo(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot map to non-pointer struct")
- }
-
- return s.mapTo(val, false)
-}
-
-// MapTo maps section to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func (s *Section) StrictMapTo(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot map to non-pointer struct")
- }
-
- return s.mapTo(val, true)
-}
-
-// MapTo maps file to given struct.
-func (f *File) MapTo(v interface{}) error {
- return f.Section("").MapTo(v)
-}
-
-// MapTo maps file to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func (f *File) StrictMapTo(v interface{}) error {
- return f.Section("").StrictMapTo(v)
-}
-
-// MapTo maps data sources to given struct with name mapper.
-func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
- cfg, err := Load(source, others...)
- if err != nil {
- return err
- }
- cfg.NameMapper = mapper
- return cfg.MapTo(v)
-}
-
-// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
-// which returns all possible error including value parsing error.
-func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
- cfg, err := Load(source, others...)
- if err != nil {
- return err
- }
- cfg.NameMapper = mapper
- return cfg.StrictMapTo(v)
-}
-
-// MapTo maps data sources to given struct.
-func MapTo(v, source interface{}, others ...interface{}) error {
- return MapToWithMapper(v, nil, source, others...)
-}
-
-// StrictMapTo maps data sources to given struct in strict mode,
-// which returns all possible error including value parsing error.
-func StrictMapTo(v, source interface{}, others ...interface{}) error {
- return StrictMapToWithMapper(v, nil, source, others...)
-}
-
-// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
-func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
- slice := field.Slice(0, field.Len())
- if field.Len() == 0 {
- return nil
- }
-
- var buf bytes.Buffer
- sliceOf := field.Type().Elem().Kind()
- for i := 0; i < field.Len(); i++ {
- switch sliceOf {
- case reflect.String:
- buf.WriteString(slice.Index(i).String())
- case reflect.Int, reflect.Int64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
- case reflect.Uint, reflect.Uint64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
- case reflect.Float64:
- buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
- case reflectTime:
- buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
- default:
- return fmt.Errorf("unsupported type '[]%s'", sliceOf)
- }
- buf.WriteString(delim)
- }
- key.SetValue(buf.String()[:buf.Len()-1])
- return nil
-}
-
-// reflectWithProperType does the opposite thing as setWithProperType.
-func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
- switch t.Kind() {
- case reflect.String:
- key.SetValue(field.String())
- case reflect.Bool:
- key.SetValue(fmt.Sprint(field.Bool()))
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- key.SetValue(fmt.Sprint(field.Int()))
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- key.SetValue(fmt.Sprint(field.Uint()))
- case reflect.Float32, reflect.Float64:
- key.SetValue(fmt.Sprint(field.Float()))
- case reflectTime:
- key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
- case reflect.Slice:
- return reflectSliceWithProperType(key, field, delim)
- default:
- return fmt.Errorf("unsupported type '%s'", t)
- }
- return nil
-}
-
-// CR: copied from encoding/json/encode.go with modifications of time.Time support.
-// TODO: add more test coverage.
-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 reflectTime:
- return v.Interface().(time.Time).IsZero()
- case reflect.Interface, reflect.Ptr:
- return v.IsNil()
- }
- return false
-}
-
-func (s *Section) reflectFrom(val reflect.Value) error {
- if val.Kind() == reflect.Ptr {
- val = val.Elem()
- }
- typ := val.Type()
-
- for i := 0; i < typ.NumField(); i++ {
- field := val.Field(i)
- tpField := typ.Field(i)
-
- tag := tpField.Tag.Get("ini")
- if tag == "-" {
- continue
- }
-
- opts := strings.SplitN(tag, ",", 2)
- if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
- continue
- }
-
- fieldName := s.parseFieldName(tpField.Name, opts[0])
- if len(fieldName) == 0 || !field.CanSet() {
- continue
- }
-
- if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
- (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
- // Note: The only error here is section doesn't exist.
- sec, err := s.f.GetSection(fieldName)
- if err != nil {
- // Note: fieldName can never be empty here, ignore error.
- sec, _ = s.f.NewSection(fieldName)
- }
- if err = sec.reflectFrom(field); err != nil {
- return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
- }
- continue
- }
-
- // Note: Same reason as secion.
- key, err := s.GetKey(fieldName)
- if err != nil {
- key, _ = s.NewKey(fieldName, "")
- }
- if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
- return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
- }
-
- }
- return nil
-}
-
-// ReflectFrom reflects secion from given struct.
-func (s *Section) ReflectFrom(v interface{}) error {
- typ := reflect.TypeOf(v)
- val := reflect.ValueOf(v)
- if typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- val = val.Elem()
- } else {
- return errors.New("cannot reflect from non-pointer struct")
- }
-
- return s.reflectFrom(val)
-}
-
-// ReflectFrom reflects file from given struct.
-func (f *File) ReflectFrom(v interface{}) error {
- return f.Section("").ReflectFrom(v)
-}
-
-// ReflectFrom reflects data sources from given struct with name mapper.
-func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
- cfg.NameMapper = mapper
- return cfg.ReflectFrom(v)
-}
-
-// ReflectFrom reflects data sources from given struct.
-func ReflectFrom(cfg *File, v interface{}) error {
- return ReflectFromWithMapper(cfg, v, nil)
-}
diff --git a/vendor/vendor.json b/vendor/vendor.json
deleted file mode 100644
index ada01b2..0000000
--- a/vendor/vendor.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "comment": "",
- "ignore": "test",
- "package": [
- {
- "checksumSHA1": "DB+/DpKJUO7dR+MyQVchJ+yyHoI=",
- "path": "github.com/go-gomail/gomail",
- "revision": "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1",
- "revisionTime": "2016-04-11T21:29:32Z"
- },
- {
- "checksumSHA1": "6IzzHO9p32aHJhMYMwijccDUIVA=",
- "path": "gopkg.in/alexcesaro/quotedprintable.v3",
- "revision": "2caba252f4dc53eaf6b553000885530023f54623",
- "revisionTime": "2015-07-16T17:19:45Z"
- },
- {
- "checksumSHA1": "WNjUTH01NMhPWjpwnsaL3SW52Gw=",
- "path": "gopkg.in/ini.v1",
- "revision": "d3de07a94d22b4a0972deb4b96d790c2c0ce8333",
- "revisionTime": "2017-06-02T20:46:24Z"
- }
- ],
- "rootPath": "github.com/ouqiang/supervisor-event-listener"
-}