├── supervisor-event-listener.go
├── vendor
├── github.com
│ └── go-gomail
│ │ └── gomail
│ │ ├── doc.go
│ │ ├── mime.go
│ │ ├── mime_go14.go
│ │ ├── CONTRIBUTING.md
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── auth.go
│ │ ├── README.md
│ │ ├── send.go
│ │ ├── smtp.go
│ │ ├── writeto.go
│ │ └── message.go
├── gopkg.in
│ ├── ini.v1
│ │ ├── Makefile
│ │ ├── error.go
│ │ ├── section.go
│ │ ├── parser.go
│ │ ├── LICENSE
│ │ ├── struct.go
│ │ ├── ini.go
│ │ ├── README_ZH.md
│ │ ├── README.md
│ │ └── key.go
│ └── alexcesaro
│ │ └── quotedprintable.v3
│ │ ├── pool_go12.go
│ │ ├── pool.go
│ │ ├── README.md
│ │ ├── LICENSE
│ │ ├── reader.go
│ │ ├── writer.go
│ │ └── encodedword.go
└── vendor.json
├── .gitignore
├── supervisor-event-listener.ini
├── listener
├── notify
│ ├── webhook.go
│ ├── mail.go
│ ├── slack.go
│ └── notify.go
└── listener.go
├── upload_package_to_qiniu.sh
├── LICENSE
├── utils
├── utils.go
└── httpclient
│ └── http_client.go
├── README.md
├── event
└── event.go
└── config
└── config.go
/supervisor-event-listener.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/ouqiang/supervisor-event-listener/listener"
5 | )
6 |
7 | func main() {
8 | for {
9 | listener.Start()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/doc.go:
--------------------------------------------------------------------------------
1 | // Package gomail provides a simple interface to compose emails and to mail them
2 | // efficiently.
3 | //
4 | // More info on Github: https://github.com/go-gomail/gomail
5 | package gomail
6 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: build test bench vet
2 |
3 | build: vet bench
4 |
5 | test:
6 | go test -v -cover -race
7 |
8 | bench:
9 | go test -v -cover -race -test.bench=. -test.benchmem
10 |
11 | vet:
12 | go vet
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.dll
4 | *.so
5 | *.dylib
6 |
7 | # Test binary, build with `go test -c`
8 | *.test
9 |
10 | # Output of the go coverage tool, specifically when used with LiteIDE
11 | *.out
12 |
13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
14 | .glide/
15 | .idear
16 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/mime.go:
--------------------------------------------------------------------------------
1 | // +build go1.5
2 |
3 | package gomail
4 |
5 | import (
6 | "mime"
7 | "mime/quotedprintable"
8 | "strings"
9 | )
10 |
11 | var newQPWriter = quotedprintable.NewWriter
12 |
13 | type mimeEncoder struct {
14 | mime.WordEncoder
15 | }
16 |
17 | var (
18 | bEncoding = mimeEncoder{mime.BEncoding}
19 | qEncoding = mimeEncoder{mime.QEncoding}
20 | lastIndexByte = strings.LastIndexByte
21 | )
22 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool_go12.go:
--------------------------------------------------------------------------------
1 | // +build !go1.3
2 |
3 | package quotedprintable
4 |
5 | import "bytes"
6 |
7 | var ch = make(chan *bytes.Buffer, 32)
8 |
9 | func getBuffer() *bytes.Buffer {
10 | select {
11 | case buf := <-ch:
12 | return buf
13 | default:
14 | }
15 | return new(bytes.Buffer)
16 | }
17 |
18 | func putBuffer(buf *bytes.Buffer) {
19 | buf.Reset()
20 | select {
21 | case ch <- buf:
22 | default:
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/supervisor-event-listener.ini:
--------------------------------------------------------------------------------
1 | [default]
2 | # 通知类型 mail,slack,webhook 只能选择一种
3 | notify_type = mail
4 |
5 | # 邮件服务器配置
6 | mail.server.user = test@163.com
7 | mail.server.password = 123456
8 | mail.server.host = smtp.163.com
9 | mail.server.port = 25
10 |
11 | # 邮件收件人配置, 多个收件人, 逗号分隔
12 | mail.user = hello@163.com
13 |
14 | # Slack配置
15 | slack.webhook_url = https://hooks.slack.com/services/xxxx/xxx/xxxx
16 | slack.channel = exception
17 |
18 | # WebHook通知URL配置
19 | webhook_url = http://my.webhook.com
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/pool.go:
--------------------------------------------------------------------------------
1 | // +build go1.3
2 |
3 | package quotedprintable
4 |
5 | import (
6 | "bytes"
7 | "sync"
8 | )
9 |
10 | var bufPool = sync.Pool{
11 | New: func() interface{} {
12 | return new(bytes.Buffer)
13 | },
14 | }
15 |
16 | func getBuffer() *bytes.Buffer {
17 | return bufPool.Get().(*bytes.Buffer)
18 | }
19 |
20 | func putBuffer(buf *bytes.Buffer) {
21 | if buf.Len() > 1024 {
22 | return
23 | }
24 | buf.Reset()
25 | bufPool.Put(buf)
26 | }
27 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/README.md:
--------------------------------------------------------------------------------
1 | # quotedprintable
2 |
3 | ## Introduction
4 |
5 | Package quotedprintable implements quoted-printable and message header encoding
6 | as specified by RFC 2045 and RFC 2047.
7 |
8 | It is a copy of the Go 1.5 package `mime/quotedprintable`. It also includes
9 | the new functions of package `mime` concerning RFC 2047.
10 |
11 | This code has minor changes with the standard library code in order to work
12 | with Go 1.0 and newer.
13 |
14 | ## Documentation
15 |
16 | https://godoc.org/gopkg.in/alexcesaro/quotedprintable.v3
17 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/mime_go14.go:
--------------------------------------------------------------------------------
1 | // +build !go1.5
2 |
3 | package gomail
4 |
5 | import "gopkg.in/alexcesaro/quotedprintable.v3"
6 |
7 | var newQPWriter = quotedprintable.NewWriter
8 |
9 | type mimeEncoder struct {
10 | quotedprintable.WordEncoder
11 | }
12 |
13 | var (
14 | bEncoding = mimeEncoder{quotedprintable.BEncoding}
15 | qEncoding = mimeEncoder{quotedprintable.QEncoding}
16 | lastIndexByte = func(s string, c byte) int {
17 | for i := len(s) - 1; i >= 0; i-- {
18 |
19 | if s[i] == c {
20 | return i
21 | }
22 | }
23 | return -1
24 | }
25 | )
26 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Thank you for contributing to Gomail! Here are a few guidelines:
2 |
3 | ## Bugs
4 |
5 | If you think you found a bug, create an issue and supply the minimum amount
6 | of code triggering the bug so it can be reproduced.
7 |
8 |
9 | ## Fixing a bug
10 |
11 | If you want to fix a bug, you can send a pull request. It should contains a
12 | new test or update an existing one to cover that bug.
13 |
14 |
15 | ## New feature proposal
16 |
17 | If you think Gomail lacks a feature, you can open an issue or send a pull
18 | request. I want to keep Gomail code and API as simple as possible so please
19 | describe your needs so we can discuss whether this feature should be added to
20 | Gomail or not.
21 |
--------------------------------------------------------------------------------
/listener/notify/webhook.go:
--------------------------------------------------------------------------------
1 | package notify
2 |
3 | import (
4 | "encoding/json"
5 | "errors"
6 | "fmt"
7 | "github.com/ouqiang/supervisor-event-listener/event"
8 | "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
9 | )
10 |
11 | type WebHook struct{}
12 |
13 | func (hook *WebHook) Send(message event.Message) error {
14 | encodeMessage, err := json.Marshal(message)
15 | if err != nil {
16 | return err
17 | }
18 | timeout := 60
19 | response := httpclient.PostJson(Conf.WebHook.Url, string(encodeMessage), timeout)
20 |
21 | if response.StatusCode == 200 {
22 | return nil
23 | }
24 | errorMessage := fmt.Sprintf("webhook执行失败#HTTP状态码-%d#HTTP-BODY-%s", response.StatusCode, response.Body)
25 | return errors.New(errorMessage)
26 | }
27 |
--------------------------------------------------------------------------------
/upload_package_to_qiniu.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | # set -x -u
3 | # 上传二进制包到七牛
4 |
5 | if [[ -z $QINIU_ACCESS_KEY || -z $QINIU_SECRET_KEY || -z $QINIU_URL ]];then
6 | echo 'QINIU_ACCESS_KEY | QINIU_SECRET_KEY | QINIU_URL is need'
7 | exit 1
8 | fi
9 |
10 | # 打包
11 | for i in linux darwin
12 | do
13 | ./build.sh -p $i
14 | if [[ $? != 0 ]];then
15 | break
16 | fi
17 | done
18 |
19 | # 身份认证
20 | qrsctl login $QINIU_ACCESS_KEY $QINIU_SECRET_KEY
21 |
22 | # 上传
23 | for i in `ls supervisor*.gz`
24 | do
25 | # 上传文件 qrsctl put bucket key srcFile
26 | KEY=supervisor/$i
27 | qrsctl put github $KEY $i
28 | if [[ $? != 0 ]];then
29 | break
30 | fi
31 | echo "刷新七牛CDN-" $QINIU_URL/$KEY
32 | qrsctl cdn/refresh $QINIU_URL/$KEY
33 | rm $i
34 | done
35 |
36 | echo '打包并上传成功'
--------------------------------------------------------------------------------
/vendor/vendor.json:
--------------------------------------------------------------------------------
1 | {
2 | "comment": "",
3 | "ignore": "test",
4 | "package": [
5 | {
6 | "checksumSHA1": "DB+/DpKJUO7dR+MyQVchJ+yyHoI=",
7 | "path": "github.com/go-gomail/gomail",
8 | "revision": "81ebce5c23dfd25c6c67194b37d3dd3f338c98b1",
9 | "revisionTime": "2016-04-11T21:29:32Z"
10 | },
11 | {
12 | "checksumSHA1": "6IzzHO9p32aHJhMYMwijccDUIVA=",
13 | "path": "gopkg.in/alexcesaro/quotedprintable.v3",
14 | "revision": "2caba252f4dc53eaf6b553000885530023f54623",
15 | "revisionTime": "2015-07-16T17:19:45Z"
16 | },
17 | {
18 | "checksumSHA1": "WNjUTH01NMhPWjpwnsaL3SW52Gw=",
19 | "path": "gopkg.in/ini.v1",
20 | "revision": "d3de07a94d22b4a0972deb4b96d790c2c0ce8333",
21 | "revisionTime": "2017-06-02T20:46:24Z"
22 | }
23 | ],
24 | "rootPath": "github.com/ouqiang/supervisor-event-listener"
25 | }
26 |
--------------------------------------------------------------------------------
/listener/notify/mail.go:
--------------------------------------------------------------------------------
1 | package notify
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/go-gomail/gomail"
7 | "github.com/ouqiang/supervisor-event-listener/event"
8 | "strings"
9 | )
10 |
11 | type Mail struct{}
12 |
13 | func (mail *Mail) Send(message event.Message) error {
14 | body := message.String()
15 | body = strings.Replace(body, "\n", "
", -1)
16 | gomailMessage := gomail.NewMessage()
17 | gomailMessage.SetHeader("From", Conf.MailServer.User)
18 | gomailMessage.SetHeader("To", Conf.MailUser.Email...)
19 | gomailMessage.SetHeader("Subject", "Supervisor事件通知")
20 | gomailMessage.SetBody("text/html", body)
21 | mailer := gomail.NewPlainDialer(
22 | Conf.MailServer.Host,
23 | Conf.MailServer.Port,
24 | Conf.MailServer.User,
25 | Conf.MailServer.Password,
26 | )
27 | err := mailer.DialAndSend(gomailMessage)
28 | if err == nil {
29 | return nil
30 | }
31 | errorMessage := fmt.Sprintf("邮件发送失败#%s", err.Error())
32 |
33 | return errors.New(errorMessage)
34 | }
35 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/error.go:
--------------------------------------------------------------------------------
1 | // Copyright 2016 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package ini
16 |
17 | import (
18 | "fmt"
19 | )
20 |
21 | type ErrDelimiterNotFound struct {
22 | Line string
23 | }
24 |
25 | func IsErrDelimiterNotFound(err error) bool {
26 | _, ok := err.(ErrDelimiterNotFound)
27 | return ok
28 | }
29 |
30 | func (err ErrDelimiterNotFound) Error() string {
31 | return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
32 | }
33 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 | All notable changes to this project will be documented in this file.
3 | This project adheres to [Semantic Versioning](http://semver.org/).
4 |
5 | ## [2.0.0] - 2015-09-02
6 |
7 | - Mailer has been removed. It has been replaced by Dialer and Sender.
8 | - `File` type and the `CreateFile` and `OpenFile` functions have been removed.
9 | - `Message.Attach` and `Message.Embed` have a new signature.
10 | - `Message.GetBodyWriter` has been removed. Use `Message.AddAlternativeWriter`
11 | instead.
12 | - `Message.Export` has been removed. `Message.WriteTo` can be used instead.
13 | - `Message.DelHeader` has been removed.
14 | - The `Bcc` header field is no longer sent. It is far more simpler and
15 | efficient: the same message is sent to all recipients instead of sending a
16 | different email to each Bcc address.
17 | - LoginAuth has been removed. `NewPlainDialer` now implements the LOGIN
18 | authentication mechanism when needed.
19 | - Go 1.2 is now required instead of Go 1.3. No external dependency are used when
20 | using Go 1.5.
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 qiang.ou
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/listener/notify/slack.go:
--------------------------------------------------------------------------------
1 | package notify
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/ouqiang/supervisor-event-listener/utils/httpclient"
7 | "github.com/ouqiang/supervisor-event-listener/event"
8 | "github.com/ouqiang/supervisor-event-listener/utils"
9 | )
10 |
11 | type Slack struct{}
12 |
13 | func (slack *Slack) Send(message event.Message) error {
14 | body := slack.format(message.String(), Conf.Slack.Channel)
15 | timeout := 120
16 | response := httpclient.PostJson(Conf.Slack.WebHookUrl, body, timeout)
17 | if response.StatusCode == 200 {
18 | return nil
19 | }
20 |
21 | errorMessage := fmt.Sprintf("发送Slack消息失败#HTTP状态码-%d#HTTP-Body-%s",
22 | response.StatusCode, response.Body)
23 |
24 | return errors.New(errorMessage)
25 | }
26 |
27 | // 格式化消息内容
28 | func (slack *Slack) format(content string, channel string) string {
29 | content = utils.EscapeJson(content)
30 | specialChars := []string{"&", "<", ">"}
31 | replaceChars := []string{"&", "<", ">"}
32 | content = utils.ReplaceStrings(content, specialChars, replaceChars)
33 |
34 | return fmt.Sprintf(`{"text":"%s","username":"Supervisor事件通知", "channel":"%s"}`, content, channel)
35 | }
36 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Alexandre Cesaro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Alexandre Cesaro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/utils/utils.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import (
4 | "net"
5 | "strings"
6 | )
7 |
8 | // 批量替换字符串
9 | func ReplaceStrings(s string, old []string, replace []string) string {
10 | if s == "" {
11 | return s
12 | }
13 | if len(old) != len(replace) {
14 | return s
15 | }
16 |
17 | for i, v := range old {
18 | s = strings.Replace(s, v, replace[i], 1000)
19 | }
20 |
21 | return s
22 | }
23 |
24 | func InStringSlice(slice []string, element string) bool {
25 | element = strings.TrimSpace(element)
26 | for _, v := range slice {
27 | if strings.TrimSpace(v) == element {
28 | return true
29 | }
30 | }
31 |
32 | return false
33 | }
34 |
35 | // 转义json特殊字符
36 | func EscapeJson(s string) string {
37 | specialChars := []string{"\\", "\b", "\f", "\n", "\r", "\t", "\""}
38 | replaceChars := []string{"\\\\", "\\b", "\\f", "\\n", "\\r", "\\t", "\\\""}
39 |
40 | return ReplaceStrings(s, specialChars, replaceChars)
41 | }
42 |
43 | func GetLocalIp() string {
44 | addrs, err := net.InterfaceAddrs()
45 | if err != nil {
46 | return ""
47 | }
48 | for _, addr := range addrs {
49 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
50 | if ipnet.IP.To4() != nil {
51 | return ipnet.IP.String()
52 | }
53 |
54 | }
55 | }
56 |
57 | return ""
58 | }
59 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/auth.go:
--------------------------------------------------------------------------------
1 | package gomail
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "net/smtp"
8 | )
9 |
10 | // loginAuth is an smtp.Auth that implements the LOGIN authentication mechanism.
11 | type loginAuth struct {
12 | username string
13 | password string
14 | host string
15 | }
16 |
17 | func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
18 | if !server.TLS {
19 | advertised := false
20 | for _, mechanism := range server.Auth {
21 | if mechanism == "LOGIN" {
22 | advertised = true
23 | break
24 | }
25 | }
26 | if !advertised {
27 | return "", nil, errors.New("gomail: unencrypted connection")
28 | }
29 | }
30 | if server.Name != a.host {
31 | return "", nil, errors.New("gomail: wrong host name")
32 | }
33 | return "LOGIN", nil, nil
34 | }
35 |
36 | func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
37 | if !more {
38 | return nil, nil
39 | }
40 |
41 | switch {
42 | case bytes.Equal(fromServer, []byte("Username:")):
43 | return []byte(a.username), nil
44 | case bytes.Equal(fromServer, []byte("Password:")):
45 | return []byte(a.password), nil
46 | default:
47 | return nil, fmt.Errorf("gomail: unexpected server challenge: %s", fromServer)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/listener/notify/notify.go:
--------------------------------------------------------------------------------
1 | package notify
2 |
3 | import (
4 | "github.com/ouqiang/supervisor-event-listener/config"
5 | "github.com/ouqiang/supervisor-event-listener/event"
6 |
7 | "fmt"
8 | "os"
9 | "time"
10 | )
11 |
12 | var (
13 | Conf *config.Config
14 | queue chan event.Message
15 | )
16 |
17 | func init() {
18 | Conf = config.ParseConfig()
19 | queue = make(chan event.Message, 10)
20 | go start()
21 | }
22 |
23 | type Notifiable interface {
24 | Send(event.Message) error
25 | }
26 |
27 | func Push(header *event.Header, payload *event.Payload) {
28 | queue <- event.Message{header, payload}
29 | }
30 |
31 | func start() {
32 | var message event.Message
33 | var notifyHandler Notifiable
34 | for {
35 | message = <-queue
36 | switch Conf.NotifyType {
37 | case "mail":
38 | notifyHandler = &Mail{}
39 | case "slack":
40 | notifyHandler = &Slack{}
41 | case "webhook":
42 | notifyHandler = &WebHook{}
43 | }
44 | if notifyHandler == nil {
45 | continue
46 | }
47 | go send(notifyHandler, message)
48 | time.Sleep(1 * time.Second)
49 | }
50 | }
51 |
52 | func send(notifyHandler Notifiable, message event.Message) {
53 | // 最多重试3次
54 | tryTimes := 3
55 | i := 0
56 | for i < tryTimes {
57 | err := notifyHandler.Send(message)
58 | if err == nil {
59 | break
60 | }
61 | fmt.Fprintln(os.Stderr, err)
62 | time.Sleep(30 * time.Second)
63 | i++
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # supervisor-event-listener
2 | Supervisor事件通知, 支持邮件, Slack, WebHook
3 |
4 | ## 简介
5 | Supervisor是*nix环境下的进程管理工具, 可以把前台进程转换为守护进程, 当进程异常退出时自动重启.
6 | supervisor-event-listener监听进程异常退出事件, 并发送通知.
7 |
8 | ## 下载
9 | [v1.0](https://github.com/ouqiang/supervisor-event-listener/releases)
10 |
11 | ### 源码安装
12 | * `go get -u github.com/ouqiang/supervisor-event-listener`
13 |
14 | ## Supervisor配置
15 | ```ini
16 | [eventlistener:supervisor-event-listener]
17 | ; 默认读取配置文件/etc/supervisor-event-listener.ini
18 | command=/path/to/supervisor-event-listener
19 | ; 指定配置文件路径
20 | ;command=/path/to/supervisor-event-listener -c /path/to/supervisor-event-listener.ini
21 | events=PROCESS_STATE_EXITED
22 | ......
23 | ```
24 |
25 | ## 配置文件, 默认读取`/etc/supervisor-event-listener.ini`
26 |
27 | ```ini
28 | [default]
29 | # 通知类型 mail,slack,webhook 只能选择一种
30 | notify_type = mail
31 |
32 | # 邮件服务器配置
33 | mail.server.user = test@163.com
34 | mail.server.password = 123456
35 | mail.server.host = smtp.163.com
36 | mail.server.port = 25
37 |
38 | # 邮件收件人配置, 多个收件人, 逗号分隔
39 | mail.user = hello@163.com
40 |
41 | # Slack配置
42 | slack.webhook_url = https://hooks.slack.com/services/xxxx/xxx/xxxx
43 | slack.channel = exception
44 |
45 | # WebHook通知URL配置
46 | webhook_url = http://my.webhook.com
47 |
48 | ```
49 |
50 | ## 通知内容
51 | 邮件、Slack
52 | ```shell
53 | Host: ip(hostname)
54 | Process: process-name
55 | PID: 6152
56 | EXITED FROM state: RUNNING
57 | ```
58 | WebHook, Post请求, 字段含义查看Supervisor文档
59 | ```json
60 | {
61 | "Header": {
62 | "Ver": "3.0",
63 | "Server": "supervisor",
64 | "Serial": 11,
65 | "Pool": "supervisor-listener",
66 | "PoolSerial": 11,
67 | "EventName": "PROCESS_STATE_EXITED",
68 | "Len": 84
69 | },
70 | "Payload": {
71 | "Ip": "ip(hostname)",
72 | "ProcessName": "process-name",
73 | "GroupName": "group-name",
74 | "FromState": "RUNNING",
75 | "Expected": 0,
76 | "Pid": 6371
77 | }
78 | }
79 | ```
80 |
--------------------------------------------------------------------------------
/listener/listener.go:
--------------------------------------------------------------------------------
1 | package listener
2 |
3 | import (
4 | "bufio"
5 | "errors"
6 | "fmt"
7 | "github.com/ouqiang/supervisor-event-listener/event"
8 | "github.com/ouqiang/supervisor-event-listener/listener/notify"
9 | "log"
10 | "os"
11 | )
12 |
13 | var (
14 | ErrPayloadLength = errors.New("Header中len长度与实际读取长度不一致")
15 | )
16 |
17 | func Start() {
18 | defer func() {
19 | if err := recover(); err != nil {
20 | log.Print("panic", err)
21 | }
22 | }()
23 | listen()
24 | }
25 |
26 | // 监听事件, 从标准输入获取事件内容
27 | func listen() {
28 | reader := bufio.NewReader(os.Stdin)
29 | for {
30 | ready()
31 | header, err := readHeader(reader)
32 | if err != nil {
33 | failure(err)
34 | continue
35 | }
36 | payload, err := readPayload(reader, header.Len)
37 | if err != nil {
38 | failure(err)
39 | continue
40 | }
41 | // 只处理进程异常退出事件
42 | if header.EventName == "PROCESS_STATE_EXITED" {
43 | notify.Push(header, payload)
44 | }
45 | success()
46 | }
47 | }
48 |
49 | // 读取header
50 | func readHeader(reader *bufio.Reader) (*event.Header, error) {
51 | // 读取Header
52 | data, err := reader.ReadString('\n')
53 | if err != nil {
54 | return nil, err
55 | }
56 | // 解析Header
57 | header, err := event.ParseHeader(data)
58 | if err != nil {
59 | return nil, err
60 | }
61 |
62 | return header, nil
63 | }
64 |
65 | // 读取payload
66 | func readPayload(reader *bufio.Reader, payloadLen int) (*event.Payload, error) {
67 | // 读取payload
68 | buf := make([]byte, payloadLen)
69 | length, err := reader.Read(buf)
70 | if err != nil {
71 | return nil, err
72 | }
73 | if payloadLen != length {
74 | return nil, ErrPayloadLength
75 | }
76 | // 解析payload
77 | payload, err := event.ParsePayload(string(buf))
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | return payload, nil
83 | }
84 |
85 | func ready() {
86 | fmt.Fprint(os.Stdout, "READY\n")
87 | }
88 |
89 | func success() {
90 | fmt.Fprint(os.Stdout, "RESULT 2\nOK")
91 | }
92 |
93 | func failure(err error) {
94 | fmt.Fprintln(os.Stderr, err)
95 | fmt.Fprint(os.Stdout, "Result 2\nFAIL")
96 | }
97 |
--------------------------------------------------------------------------------
/utils/httpclient/http_client.go:
--------------------------------------------------------------------------------
1 | package httpclient
2 |
3 | // http-client
4 |
5 | import (
6 | "bytes"
7 | "fmt"
8 | "io/ioutil"
9 | "net/http"
10 | "time"
11 | )
12 |
13 | type ResponseWrapper struct {
14 | StatusCode int
15 | Body string
16 | Header http.Header
17 | }
18 |
19 | func Get(url string, timeout int) ResponseWrapper {
20 | req, err := http.NewRequest("GET", url, nil)
21 | if err != nil {
22 | return createRequestError(err)
23 | }
24 |
25 | return request(req, timeout)
26 | }
27 |
28 | func PostParams(url string, params string, timeout int) ResponseWrapper {
29 | buf := bytes.NewBufferString(params)
30 | req, err := http.NewRequest("POST", url, buf)
31 | if err != nil {
32 | return createRequestError(err)
33 | }
34 | req.Header.Set("Content-type", "application/x-www-form-urlencoded")
35 |
36 | return request(req, timeout)
37 | }
38 |
39 | func PostJson(url string, body string, timeout int) ResponseWrapper {
40 | buf := bytes.NewBufferString(body)
41 | req, err := http.NewRequest("POST", url, buf)
42 | if err != nil {
43 | return createRequestError(err)
44 | }
45 | req.Header.Set("Content-type", "application/json")
46 |
47 | return request(req, timeout)
48 | }
49 |
50 | func request(req *http.Request, timeout int) ResponseWrapper {
51 | wrapper := ResponseWrapper{StatusCode: 0, Body: "", Header: make(http.Header)}
52 | client := &http.Client{}
53 | if timeout > 0 {
54 | client.Timeout = time.Duration(timeout) * time.Second
55 | }
56 | setRequestHeader(req)
57 | resp, err := client.Do(req)
58 | if err != nil {
59 | wrapper.Body = fmt.Sprintf("执行HTTP请求错误-%s", err.Error())
60 | return wrapper
61 | }
62 | defer resp.Body.Close()
63 | body, err := ioutil.ReadAll(resp.Body)
64 | if err != nil {
65 | wrapper.Body = fmt.Sprintf("读取HTTP请求返回值失败-%s", err.Error())
66 | return wrapper
67 | }
68 | wrapper.StatusCode = resp.StatusCode
69 | wrapper.Body = string(body)
70 | wrapper.Header = resp.Header
71 |
72 | return wrapper
73 | }
74 |
75 | func setRequestHeader(req *http.Request) {
76 | req.Header.Set("Accept-Language", "zh-CN,zh;q=0.8,en;q=0.6")
77 | 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")
78 | }
79 |
80 | func createRequestError(err error) ResponseWrapper {
81 | errorMessage := fmt.Sprintf("创建HTTP请求错误-%s", err.Error())
82 | return ResponseWrapper{0, errorMessage, make(http.Header)}
83 | }
84 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/README.md:
--------------------------------------------------------------------------------
1 | # Gomail
2 | [](https://travis-ci.org/go-gomail/gomail) [](http://gocover.io/gopkg.in/gomail.v2) [](https://godoc.org/gopkg.in/gomail.v2)
3 |
4 | ## Introduction
5 |
6 | Gomail is a simple and efficient package to send emails. It is well tested and
7 | documented.
8 |
9 | Gomail can only send emails using an SMTP server. But the API is flexible and it
10 | is easy to implement other methods for sending emails using a local Postfix, an
11 | API, etc.
12 |
13 | It is versioned using [gopkg.in](https://gopkg.in) so I promise
14 | there will never be backward incompatible changes within each version.
15 |
16 | It requires Go 1.2 or newer. With Go 1.5, no external dependencies are used.
17 |
18 |
19 | ## Features
20 |
21 | Gomail supports:
22 | - Attachments
23 | - Embedded images
24 | - HTML and text templates
25 | - Automatic encoding of special characters
26 | - SSL and TLS
27 | - Sending multiple emails with the same SMTP connection
28 |
29 |
30 | ## Documentation
31 |
32 | https://godoc.org/gopkg.in/gomail.v2
33 |
34 |
35 | ## Download
36 |
37 | go get gopkg.in/gomail.v2
38 |
39 |
40 | ## Examples
41 |
42 | See the [examples in the documentation](https://godoc.org/gopkg.in/gomail.v2#example-package).
43 |
44 |
45 | ## FAQ
46 |
47 | ### x509: certificate signed by unknown authority
48 |
49 | If you get this error it means the certificate used by the SMTP server is not
50 | considered valid by the client running Gomail. As a quick workaround you can
51 | bypass the verification of the server's certificate chain and host name by using
52 | `SetTLSConfig`:
53 |
54 | package main
55 |
56 | import (
57 | "crypto/tls"
58 |
59 | "gopkg.in/gomail.v2"
60 | )
61 |
62 | func main() {
63 | d := gomail.NewDialer("smtp.example.com", 587, "user", "123456")
64 | d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
65 |
66 | // Send emails using d.
67 | }
68 |
69 | Note, however, that this is insecure and should not be used in production.
70 |
71 |
72 | ## Contribute
73 |
74 | Contributions are more than welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for
75 | more info.
76 |
77 |
78 | ## Change log
79 |
80 | See [CHANGELOG.md](CHANGELOG.md).
81 |
82 |
83 | ## License
84 |
85 | [MIT](LICENSE)
86 |
87 |
88 | ## Contact
89 |
90 | You can ask questions on the [Gomail
91 | thread](https://groups.google.com/d/topic/golang-nuts/jMxZHzvvEVg/discussion)
92 | in the Go mailing-list.
93 |
--------------------------------------------------------------------------------
/event/event.go:
--------------------------------------------------------------------------------
1 | package event
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/ouqiang/supervisor-event-listener/utils"
7 | "os"
8 | "strconv"
9 | "strings"
10 | )
11 |
12 | // Message 消息格式
13 | type Message struct {
14 | Header *Header
15 | Payload *Payload
16 | }
17 |
18 | func (msg *Message) String() string {
19 | 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)
20 |
21 | }
22 |
23 | // Header Supervisord触发事件时会先发送Header,根据Header中len字段去读取Payload
24 | type Header struct {
25 | Ver string
26 | Server string
27 | Serial int
28 | Pool string
29 | PoolSerial int
30 | EventName string // 事件名称
31 | Len int // Payload长度
32 | }
33 |
34 | // Payload
35 | type Payload struct {
36 | Ip string
37 | ProcessName string // 进程名称
38 | GroupName string // 进程组名称
39 | FromState string
40 | Expected int
41 | Pid int
42 | }
43 |
44 | // Fields
45 | type Fields map[string]string
46 |
47 | var (
48 | ErrParseHeader = errors.New("解析Header失败")
49 | ErrParsePayload = errors.New("解析Payload失败")
50 | )
51 |
52 | func ParseHeader(header string) (*Header, error) {
53 | h := &Header{}
54 | fields := parseFields(header)
55 | if len(fields) == 0 {
56 | return h, ErrParseHeader
57 | }
58 |
59 | h.Ver = fields["ver"]
60 | h.Server = fields["server"]
61 | h.Serial, _ = strconv.Atoi(fields["serial"])
62 | h.Pool = fields["pool"]
63 | h.PoolSerial, _ = strconv.Atoi(fields["poolserial"])
64 | h.EventName = fields["eventname"]
65 | h.Len, _ = strconv.Atoi(fields["len"])
66 |
67 | return h, nil
68 | }
69 |
70 | func ParsePayload(payload string) (*Payload, error) {
71 | p := &Payload{}
72 | fields := parseFields(payload)
73 | if len(fields) == 0 {
74 | return p, ErrParsePayload
75 | }
76 | hostname, _ := os.Hostname()
77 | p.Ip = fmt.Sprintf("%s(%s)", utils.GetLocalIp(), hostname)
78 | p.ProcessName = fields["processname"]
79 | p.GroupName = fields["groupname"]
80 | p.FromState = fields["from_state"]
81 | p.Expected, _ = strconv.Atoi(fields["expected"])
82 | p.Pid, _ = strconv.Atoi(fields["pid"])
83 |
84 | return p, nil
85 | }
86 |
87 | func parseFields(data string) Fields {
88 | fields := make(Fields)
89 | data = strings.TrimSpace(data)
90 | if data == "" {
91 | return fields
92 | }
93 | // 格式如下
94 | // ver:3.0 server:supervisor serial:5
95 | slice := strings.Split(data, " ")
96 | if len(slice) == 0 {
97 | return fields
98 | }
99 | for _, item := range slice {
100 | group := strings.Split(item, ":")
101 | if len(group) < 2 {
102 | continue
103 | }
104 | key := strings.TrimSpace(group[0])
105 | value := strings.TrimSpace(group[1])
106 | fields[key] = value
107 | }
108 |
109 | return fields
110 | }
111 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/send.go:
--------------------------------------------------------------------------------
1 | package gomail
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io"
7 | "net/mail"
8 | )
9 |
10 | // Sender is the interface that wraps the Send method.
11 | //
12 | // Send sends an email to the given addresses.
13 | type Sender interface {
14 | Send(from string, to []string, msg io.WriterTo) error
15 | }
16 |
17 | // SendCloser is the interface that groups the Send and Close methods.
18 | type SendCloser interface {
19 | Sender
20 | Close() error
21 | }
22 |
23 | // A SendFunc is a function that sends emails to the given addresses.
24 | //
25 | // The SendFunc type is an adapter to allow the use of ordinary functions as
26 | // email senders. If f is a function with the appropriate signature, SendFunc(f)
27 | // is a Sender object that calls f.
28 | type SendFunc func(from string, to []string, msg io.WriterTo) error
29 |
30 | // Send calls f(from, to, msg).
31 | func (f SendFunc) Send(from string, to []string, msg io.WriterTo) error {
32 | return f(from, to, msg)
33 | }
34 |
35 | // Send sends emails using the given Sender.
36 | func Send(s Sender, msg ...*Message) error {
37 | for i, m := range msg {
38 | if err := send(s, m); err != nil {
39 | return fmt.Errorf("gomail: could not send email %d: %v", i+1, err)
40 | }
41 | }
42 |
43 | return nil
44 | }
45 |
46 | func send(s Sender, m *Message) error {
47 | from, err := m.getFrom()
48 | if err != nil {
49 | return err
50 | }
51 |
52 | to, err := m.getRecipients()
53 | if err != nil {
54 | return err
55 | }
56 |
57 | if err := s.Send(from, to, m); err != nil {
58 | return err
59 | }
60 |
61 | return nil
62 | }
63 |
64 | func (m *Message) getFrom() (string, error) {
65 | from := m.header["Sender"]
66 | if len(from) == 0 {
67 | from = m.header["From"]
68 | if len(from) == 0 {
69 | return "", errors.New(`gomail: invalid message, "From" field is absent`)
70 | }
71 | }
72 |
73 | return parseAddress(from[0])
74 | }
75 |
76 | func (m *Message) getRecipients() ([]string, error) {
77 | n := 0
78 | for _, field := range []string{"To", "Cc", "Bcc"} {
79 | if addresses, ok := m.header[field]; ok {
80 | n += len(addresses)
81 | }
82 | }
83 | list := make([]string, 0, n)
84 |
85 | for _, field := range []string{"To", "Cc", "Bcc"} {
86 | if addresses, ok := m.header[field]; ok {
87 | for _, a := range addresses {
88 | addr, err := parseAddress(a)
89 | if err != nil {
90 | return nil, err
91 | }
92 | list = addAddress(list, addr)
93 | }
94 | }
95 | }
96 |
97 | return list, nil
98 | }
99 |
100 | func addAddress(list []string, addr string) []string {
101 | for _, a := range list {
102 | if addr == a {
103 | return list
104 | }
105 | }
106 |
107 | return append(list, addr)
108 | }
109 |
110 | func parseAddress(field string) (string, error) {
111 | addr, err := mail.ParseAddress(field)
112 | if err != nil {
113 | return "", fmt.Errorf("gomail: invalid address %q: %v", field, err)
114 | }
115 | return addr.Address, nil
116 | }
117 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/reader.go:
--------------------------------------------------------------------------------
1 | // Package quotedprintable implements quoted-printable encoding as specified by
2 | // RFC 2045.
3 | package quotedprintable
4 |
5 | import (
6 | "bufio"
7 | "bytes"
8 | "fmt"
9 | "io"
10 | )
11 |
12 | // Reader is a quoted-printable decoder.
13 | type Reader struct {
14 | br *bufio.Reader
15 | rerr error // last read error
16 | line []byte // to be consumed before more of br
17 | }
18 |
19 | // NewReader returns a quoted-printable reader, decoding from r.
20 | func NewReader(r io.Reader) *Reader {
21 | return &Reader{
22 | br: bufio.NewReader(r),
23 | }
24 | }
25 |
26 | func fromHex(b byte) (byte, error) {
27 | switch {
28 | case b >= '0' && b <= '9':
29 | return b - '0', nil
30 | case b >= 'A' && b <= 'F':
31 | return b - 'A' + 10, nil
32 | // Accept badly encoded bytes.
33 | case b >= 'a' && b <= 'f':
34 | return b - 'a' + 10, nil
35 | }
36 | return 0, fmt.Errorf("quotedprintable: invalid hex byte 0x%02x", b)
37 | }
38 |
39 | func readHexByte(a, b byte) (byte, error) {
40 | var hb, lb byte
41 | var err error
42 | if hb, err = fromHex(a); err != nil {
43 | return 0, err
44 | }
45 | if lb, err = fromHex(b); err != nil {
46 | return 0, err
47 | }
48 | return hb<<4 | lb, nil
49 | }
50 |
51 | func isQPDiscardWhitespace(r rune) bool {
52 | switch r {
53 | case '\n', '\r', ' ', '\t':
54 | return true
55 | }
56 | return false
57 | }
58 |
59 | var (
60 | crlf = []byte("\r\n")
61 | lf = []byte("\n")
62 | softSuffix = []byte("=")
63 | )
64 |
65 | // Read reads and decodes quoted-printable data from the underlying reader.
66 | func (r *Reader) Read(p []byte) (n int, err error) {
67 | // Deviations from RFC 2045:
68 | // 1. in addition to "=\r\n", "=\n" is also treated as soft line break.
69 | // 2. it will pass through a '\r' or '\n' not preceded by '=', consistent
70 | // with other broken QP encoders & decoders.
71 | for len(p) > 0 {
72 | if len(r.line) == 0 {
73 | if r.rerr != nil {
74 | return n, r.rerr
75 | }
76 | r.line, r.rerr = r.br.ReadSlice('\n')
77 |
78 | // Does the line end in CRLF instead of just LF?
79 | hasLF := bytes.HasSuffix(r.line, lf)
80 | hasCR := bytes.HasSuffix(r.line, crlf)
81 | wholeLine := r.line
82 | r.line = bytes.TrimRightFunc(wholeLine, isQPDiscardWhitespace)
83 | if bytes.HasSuffix(r.line, softSuffix) {
84 | rightStripped := wholeLine[len(r.line):]
85 | r.line = r.line[:len(r.line)-1]
86 | if !bytes.HasPrefix(rightStripped, lf) && !bytes.HasPrefix(rightStripped, crlf) {
87 | r.rerr = fmt.Errorf("quotedprintable: invalid bytes after =: %q", rightStripped)
88 | }
89 | } else if hasLF {
90 | if hasCR {
91 | r.line = append(r.line, '\r', '\n')
92 | } else {
93 | r.line = append(r.line, '\n')
94 | }
95 | }
96 | continue
97 | }
98 | b := r.line[0]
99 |
100 | switch {
101 | case b == '=':
102 | if len(r.line[1:]) < 2 {
103 | return n, io.ErrUnexpectedEOF
104 | }
105 | b, err = readHexByte(r.line[1], r.line[2])
106 | if err != nil {
107 | return n, err
108 | }
109 | r.line = r.line[2:] // 2 of the 3; other 1 is done below
110 | case b == '\t' || b == '\r' || b == '\n':
111 | break
112 | case b < ' ' || b > '~':
113 | return n, fmt.Errorf("quotedprintable: invalid unescaped byte 0x%02x in body", b)
114 | }
115 | p[0] = b
116 | p = p[1:]
117 | r.line = r.line[1:]
118 | n++
119 | }
120 | return n, nil
121 | }
122 |
--------------------------------------------------------------------------------
/config/config.go:
--------------------------------------------------------------------------------
1 | package config
2 |
3 | import (
4 | "flag"
5 | "fmt"
6 | "github.com/ouqiang/supervisor-event-listener/utils"
7 | "gopkg.in/ini.v1"
8 | "os"
9 | "strings"
10 | )
11 |
12 | type Config struct {
13 | NotifyType string
14 | WebHook WebHook
15 | MailServer MailServer
16 | MailUser MailUser
17 | Slack Slack
18 | }
19 |
20 | type WebHook struct {
21 | Url string
22 | }
23 |
24 | type Slack struct {
25 | WebHookUrl string
26 | Channel string
27 | }
28 |
29 | // 邮件服务器
30 | type MailServer struct {
31 | User string
32 | Password string
33 | Host string
34 | Port int
35 | }
36 |
37 | // 接收邮件的用户
38 | type MailUser struct {
39 | Email []string
40 | }
41 |
42 | func ParseConfig() *Config {
43 | var configFile string
44 | flag.StringVar(&configFile, "c", "/etc/supervisor-event-listener.ini", "config file")
45 | flag.Parse()
46 | configFile = strings.TrimSpace(configFile)
47 | if configFile == "" {
48 | Exit("请指定配置文件路径")
49 | }
50 | file, err := ini.Load(configFile)
51 | if err != nil {
52 | Exit("读取配置文件失败#" + err.Error())
53 | }
54 | section := file.Section("default")
55 | notifyType := section.Key("notify_type").String()
56 | notifyType = strings.TrimSpace(notifyType)
57 | if !utils.InStringSlice([]string{"mail", "slack", "webhook"}, notifyType) {
58 | Exit("不支持的通知类型-" + notifyType)
59 | }
60 |
61 | config := &Config{}
62 | config.NotifyType = notifyType
63 | switch notifyType {
64 | case "mail":
65 | config.MailServer = parseMailServer(section)
66 | config.MailUser = parseMailUser(section)
67 | case "slack":
68 | config.Slack = parseSlack(section)
69 | case "webhook":
70 | config.WebHook = parseWebHook(section)
71 | }
72 |
73 | return config
74 | }
75 |
76 | func parseMailServer(section *ini.Section) MailServer {
77 | user := section.Key("mail.server.user").String()
78 | user = strings.TrimSpace(user)
79 | password := section.Key("mail.server.password").String()
80 | password = strings.TrimSpace(password)
81 | host := section.Key("mail.server.host").String()
82 | host = strings.TrimSpace(host)
83 | port, portErr := section.Key("mail.server.port").Int()
84 | if user == "" || password == "" || host == "" || portErr != nil {
85 | Exit("邮件服务器配置错误")
86 | }
87 |
88 | mailServer := MailServer{}
89 | mailServer.User = user
90 | mailServer.Password = password
91 | mailServer.Host = host
92 | mailServer.Port = port
93 |
94 | return mailServer
95 | }
96 |
97 | func parseMailUser(section *ini.Section) MailUser {
98 | user := section.Key("mail.user").String()
99 | user = strings.TrimSpace(user)
100 | if user == "" {
101 | Exit("邮件收件人配置错误")
102 | }
103 | mailUser := MailUser{}
104 | mailUser.Email = strings.Split(user, ",")
105 |
106 | return mailUser
107 | }
108 |
109 | func parseSlack(section *ini.Section) Slack {
110 | webHookUrl := section.Key("slack.webhook_url").String()
111 | webHookUrl = strings.TrimSpace(webHookUrl)
112 | channel := section.Key("slack.channel").String()
113 | channel = strings.TrimSpace(channel)
114 | if webHookUrl == "" || channel == "" {
115 | Exit("Slack配置错误")
116 | }
117 |
118 | slack := Slack{}
119 | slack.WebHookUrl = webHookUrl
120 | slack.Channel = channel
121 |
122 | return slack
123 | }
124 |
125 | func parseWebHook(section *ini.Section) WebHook {
126 | url := section.Key("webhook_url").String()
127 | url = strings.TrimSpace(url)
128 | if url == "" {
129 | Exit("WebHookUrl配置错误")
130 | }
131 | webHook := WebHook{}
132 | webHook.Url = url
133 |
134 | return webHook
135 | }
136 |
137 | func Exit(msg string) {
138 | fmt.Fprintln(os.Stderr, msg)
139 | os.Exit(1)
140 | }
141 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/writer.go:
--------------------------------------------------------------------------------
1 | package quotedprintable
2 |
3 | import "io"
4 |
5 | const lineMaxLen = 76
6 |
7 | // A Writer is a quoted-printable writer that implements io.WriteCloser.
8 | type Writer struct {
9 | // Binary mode treats the writer's input as pure binary and processes end of
10 | // line bytes as binary data.
11 | Binary bool
12 |
13 | w io.Writer
14 | i int
15 | line [78]byte
16 | cr bool
17 | }
18 |
19 | // NewWriter returns a new Writer that writes to w.
20 | func NewWriter(w io.Writer) *Writer {
21 | return &Writer{w: w}
22 | }
23 |
24 | // Write encodes p using quoted-printable encoding and writes it to the
25 | // underlying io.Writer. It limits line length to 76 characters. The encoded
26 | // bytes are not necessarily flushed until the Writer is closed.
27 | func (w *Writer) Write(p []byte) (n int, err error) {
28 | for i, b := range p {
29 | switch {
30 | // Simple writes are done in batch.
31 | case b >= '!' && b <= '~' && b != '=':
32 | continue
33 | case isWhitespace(b) || !w.Binary && (b == '\n' || b == '\r'):
34 | continue
35 | }
36 |
37 | if i > n {
38 | if err := w.write(p[n:i]); err != nil {
39 | return n, err
40 | }
41 | n = i
42 | }
43 |
44 | if err := w.encode(b); err != nil {
45 | return n, err
46 | }
47 | n++
48 | }
49 |
50 | if n == len(p) {
51 | return n, nil
52 | }
53 |
54 | if err := w.write(p[n:]); err != nil {
55 | return n, err
56 | }
57 |
58 | return len(p), nil
59 | }
60 |
61 | // Close closes the Writer, flushing any unwritten data to the underlying
62 | // io.Writer, but does not close the underlying io.Writer.
63 | func (w *Writer) Close() error {
64 | if err := w.checkLastByte(); err != nil {
65 | return err
66 | }
67 |
68 | return w.flush()
69 | }
70 |
71 | // write limits text encoded in quoted-printable to 76 characters per line.
72 | func (w *Writer) write(p []byte) error {
73 | for _, b := range p {
74 | if b == '\n' || b == '\r' {
75 | // If the previous byte was \r, the CRLF has already been inserted.
76 | if w.cr && b == '\n' {
77 | w.cr = false
78 | continue
79 | }
80 |
81 | if b == '\r' {
82 | w.cr = true
83 | }
84 |
85 | if err := w.checkLastByte(); err != nil {
86 | return err
87 | }
88 | if err := w.insertCRLF(); err != nil {
89 | return err
90 | }
91 | continue
92 | }
93 |
94 | if w.i == lineMaxLen-1 {
95 | if err := w.insertSoftLineBreak(); err != nil {
96 | return err
97 | }
98 | }
99 |
100 | w.line[w.i] = b
101 | w.i++
102 | w.cr = false
103 | }
104 |
105 | return nil
106 | }
107 |
108 | func (w *Writer) encode(b byte) error {
109 | if lineMaxLen-1-w.i < 3 {
110 | if err := w.insertSoftLineBreak(); err != nil {
111 | return err
112 | }
113 | }
114 |
115 | w.line[w.i] = '='
116 | w.line[w.i+1] = upperhex[b>>4]
117 | w.line[w.i+2] = upperhex[b&0x0f]
118 | w.i += 3
119 |
120 | return nil
121 | }
122 |
123 | // checkLastByte encodes the last buffered byte if it is a space or a tab.
124 | func (w *Writer) checkLastByte() error {
125 | if w.i == 0 {
126 | return nil
127 | }
128 |
129 | b := w.line[w.i-1]
130 | if isWhitespace(b) {
131 | w.i--
132 | if err := w.encode(b); err != nil {
133 | return err
134 | }
135 | }
136 |
137 | return nil
138 | }
139 |
140 | func (w *Writer) insertSoftLineBreak() error {
141 | w.line[w.i] = '='
142 | w.i++
143 |
144 | return w.insertCRLF()
145 | }
146 |
147 | func (w *Writer) insertCRLF() error {
148 | w.line[w.i] = '\r'
149 | w.line[w.i+1] = '\n'
150 | w.i += 2
151 |
152 | return w.flush()
153 | }
154 |
155 | func (w *Writer) flush() error {
156 | if _, err := w.w.Write(w.line[:w.i]); err != nil {
157 | return err
158 | }
159 |
160 | w.i = 0
161 | return nil
162 | }
163 |
164 | func isWhitespace(b byte) bool {
165 | return b == ' ' || b == '\t'
166 | }
167 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/smtp.go:
--------------------------------------------------------------------------------
1 | package gomail
2 |
3 | import (
4 | "crypto/tls"
5 | "fmt"
6 | "io"
7 | "net"
8 | "net/smtp"
9 | "strings"
10 | "time"
11 | )
12 |
13 | // A Dialer is a dialer to an SMTP server.
14 | type Dialer struct {
15 | // Host represents the host of the SMTP server.
16 | Host string
17 | // Port represents the port of the SMTP server.
18 | Port int
19 | // Username is the username to use to authenticate to the SMTP server.
20 | Username string
21 | // Password is the password to use to authenticate to the SMTP server.
22 | Password string
23 | // Auth represents the authentication mechanism used to authenticate to the
24 | // SMTP server.
25 | Auth smtp.Auth
26 | // SSL defines whether an SSL connection is used. It should be false in
27 | // most cases since the authentication mechanism should use the STARTTLS
28 | // extension instead.
29 | SSL bool
30 | // TSLConfig represents the TLS configuration used for the TLS (when the
31 | // STARTTLS extension is used) or SSL connection.
32 | TLSConfig *tls.Config
33 | // LocalName is the hostname sent to the SMTP server with the HELO command.
34 | // By default, "localhost" is sent.
35 | LocalName string
36 | }
37 |
38 | // NewDialer returns a new SMTP Dialer. The given parameters are used to connect
39 | // to the SMTP server.
40 | func NewDialer(host string, port int, username, password string) *Dialer {
41 | return &Dialer{
42 | Host: host,
43 | Port: port,
44 | Username: username,
45 | Password: password,
46 | SSL: port == 465,
47 | }
48 | }
49 |
50 | // NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
51 | // connect to the SMTP server.
52 | //
53 | // Deprecated: Use NewDialer instead.
54 | func NewPlainDialer(host string, port int, username, password string) *Dialer {
55 | return NewDialer(host, port, username, password)
56 | }
57 |
58 | // Dial dials and authenticates to an SMTP server. The returned SendCloser
59 | // should be closed when done using it.
60 | func (d *Dialer) Dial() (SendCloser, error) {
61 | conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
62 | if err != nil {
63 | return nil, err
64 | }
65 |
66 | if d.SSL {
67 | conn = tlsClient(conn, d.tlsConfig())
68 | }
69 |
70 | c, err := smtpNewClient(conn, d.Host)
71 | if err != nil {
72 | return nil, err
73 | }
74 |
75 | if d.LocalName != "" {
76 | if err := c.Hello(d.LocalName); err != nil {
77 | return nil, err
78 | }
79 | }
80 |
81 | if !d.SSL {
82 | if ok, _ := c.Extension("STARTTLS"); ok {
83 | if err := c.StartTLS(d.tlsConfig()); err != nil {
84 | c.Close()
85 | return nil, err
86 | }
87 | }
88 | }
89 |
90 | if d.Auth == nil && d.Username != "" {
91 | if ok, auths := c.Extension("AUTH"); ok {
92 | if strings.Contains(auths, "CRAM-MD5") {
93 | d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
94 | } else if strings.Contains(auths, "LOGIN") &&
95 | !strings.Contains(auths, "PLAIN") {
96 | d.Auth = &loginAuth{
97 | username: d.Username,
98 | password: d.Password,
99 | host: d.Host,
100 | }
101 | } else {
102 | d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
103 | }
104 | }
105 | }
106 |
107 | if d.Auth != nil {
108 | if err = c.Auth(d.Auth); err != nil {
109 | c.Close()
110 | return nil, err
111 | }
112 | }
113 |
114 | return &smtpSender{c, d}, nil
115 | }
116 |
117 | func (d *Dialer) tlsConfig() *tls.Config {
118 | if d.TLSConfig == nil {
119 | return &tls.Config{ServerName: d.Host}
120 | }
121 | return d.TLSConfig
122 | }
123 |
124 | func addr(host string, port int) string {
125 | return fmt.Sprintf("%s:%d", host, port)
126 | }
127 |
128 | // DialAndSend opens a connection to the SMTP server, sends the given emails and
129 | // closes the connection.
130 | func (d *Dialer) DialAndSend(m ...*Message) error {
131 | s, err := d.Dial()
132 | if err != nil {
133 | return err
134 | }
135 | defer s.Close()
136 |
137 | return Send(s, m...)
138 | }
139 |
140 | type smtpSender struct {
141 | smtpClient
142 | d *Dialer
143 | }
144 |
145 | func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
146 | if err := c.Mail(from); err != nil {
147 | if err == io.EOF {
148 | // This is probably due to a timeout, so reconnect and try again.
149 | sc, derr := c.d.Dial()
150 | if derr == nil {
151 | if s, ok := sc.(*smtpSender); ok {
152 | *c = *s
153 | return c.Send(from, to, msg)
154 | }
155 | }
156 | }
157 | return err
158 | }
159 |
160 | for _, addr := range to {
161 | if err := c.Rcpt(addr); err != nil {
162 | return err
163 | }
164 | }
165 |
166 | w, err := c.Data()
167 | if err != nil {
168 | return err
169 | }
170 |
171 | if _, err = msg.WriteTo(w); err != nil {
172 | w.Close()
173 | return err
174 | }
175 |
176 | return w.Close()
177 | }
178 |
179 | func (c *smtpSender) Close() error {
180 | return c.Quit()
181 | }
182 |
183 | // Stubbed out for tests.
184 | var (
185 | netDialTimeout = net.DialTimeout
186 | tlsClient = tls.Client
187 | smtpNewClient = func(conn net.Conn, host string) (smtpClient, error) {
188 | return smtp.NewClient(conn, host)
189 | }
190 | )
191 |
192 | type smtpClient interface {
193 | Hello(string) error
194 | Extension(string) (bool, string)
195 | StartTLS(*tls.Config) error
196 | Auth(smtp.Auth) error
197 | Mail(string) error
198 | Rcpt(string) error
199 | Data() (io.WriteCloser, error)
200 | Quit() error
201 | Close() error
202 | }
203 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/section.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package ini
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | "strings"
21 | )
22 |
23 | // Section represents a config section.
24 | type Section struct {
25 | f *File
26 | Comment string
27 | name string
28 | keys map[string]*Key
29 | keyList []string
30 | keysHash map[string]string
31 |
32 | isRawSection bool
33 | rawBody string
34 | }
35 |
36 | func newSection(f *File, name string) *Section {
37 | return &Section{
38 | f: f,
39 | name: name,
40 | keys: make(map[string]*Key),
41 | keyList: make([]string, 0, 10),
42 | keysHash: make(map[string]string),
43 | }
44 | }
45 |
46 | // Name returns name of Section.
47 | func (s *Section) Name() string {
48 | return s.name
49 | }
50 |
51 | // Body returns rawBody of Section if the section was marked as unparseable.
52 | // It still follows the other rules of the INI format surrounding leading/trailing whitespace.
53 | func (s *Section) Body() string {
54 | return strings.TrimSpace(s.rawBody)
55 | }
56 |
57 | // NewKey creates a new key to given section.
58 | func (s *Section) NewKey(name, val string) (*Key, error) {
59 | if len(name) == 0 {
60 | return nil, errors.New("error creating new key: empty key name")
61 | } else if s.f.options.Insensitive {
62 | name = strings.ToLower(name)
63 | }
64 |
65 | if s.f.BlockMode {
66 | s.f.lock.Lock()
67 | defer s.f.lock.Unlock()
68 | }
69 |
70 | if inSlice(name, s.keyList) {
71 | if s.f.options.AllowShadows {
72 | if err := s.keys[name].addShadow(val); err != nil {
73 | return nil, err
74 | }
75 | } else {
76 | s.keys[name].value = val
77 | }
78 | return s.keys[name], nil
79 | }
80 |
81 | s.keyList = append(s.keyList, name)
82 | s.keys[name] = newKey(s, name, val)
83 | s.keysHash[name] = val
84 | return s.keys[name], nil
85 | }
86 |
87 | // NewBooleanKey creates a new boolean type key to given section.
88 | func (s *Section) NewBooleanKey(name string) (*Key, error) {
89 | key, err := s.NewKey(name, "true")
90 | if err != nil {
91 | return nil, err
92 | }
93 |
94 | key.isBooleanType = true
95 | return key, nil
96 | }
97 |
98 | // GetKey returns key in section by given name.
99 | func (s *Section) GetKey(name string) (*Key, error) {
100 | // FIXME: change to section level lock?
101 | if s.f.BlockMode {
102 | s.f.lock.RLock()
103 | }
104 | if s.f.options.Insensitive {
105 | name = strings.ToLower(name)
106 | }
107 | key := s.keys[name]
108 | if s.f.BlockMode {
109 | s.f.lock.RUnlock()
110 | }
111 |
112 | if key == nil {
113 | // Check if it is a child-section.
114 | sname := s.name
115 | for {
116 | if i := strings.LastIndex(sname, "."); i > -1 {
117 | sname = sname[:i]
118 | sec, err := s.f.GetSection(sname)
119 | if err != nil {
120 | continue
121 | }
122 | return sec.GetKey(name)
123 | } else {
124 | break
125 | }
126 | }
127 | return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name)
128 | }
129 | return key, nil
130 | }
131 |
132 | // HasKey returns true if section contains a key with given name.
133 | func (s *Section) HasKey(name string) bool {
134 | key, _ := s.GetKey(name)
135 | return key != nil
136 | }
137 |
138 | // Haskey is a backwards-compatible name for HasKey.
139 | func (s *Section) Haskey(name string) bool {
140 | return s.HasKey(name)
141 | }
142 |
143 | // HasValue returns true if section contains given raw value.
144 | func (s *Section) HasValue(value string) bool {
145 | if s.f.BlockMode {
146 | s.f.lock.RLock()
147 | defer s.f.lock.RUnlock()
148 | }
149 |
150 | for _, k := range s.keys {
151 | if value == k.value {
152 | return true
153 | }
154 | }
155 | return false
156 | }
157 |
158 | // Key assumes named Key exists in section and returns a zero-value when not.
159 | func (s *Section) Key(name string) *Key {
160 | key, err := s.GetKey(name)
161 | if err != nil {
162 | // It's OK here because the only possible error is empty key name,
163 | // but if it's empty, this piece of code won't be executed.
164 | key, _ = s.NewKey(name, "")
165 | return key
166 | }
167 | return key
168 | }
169 |
170 | // Keys returns list of keys of section.
171 | func (s *Section) Keys() []*Key {
172 | keys := make([]*Key, len(s.keyList))
173 | for i := range s.keyList {
174 | keys[i] = s.Key(s.keyList[i])
175 | }
176 | return keys
177 | }
178 |
179 | // ParentKeys returns list of keys of parent section.
180 | func (s *Section) ParentKeys() []*Key {
181 | var parentKeys []*Key
182 | sname := s.name
183 | for {
184 | if i := strings.LastIndex(sname, "."); i > -1 {
185 | sname = sname[:i]
186 | sec, err := s.f.GetSection(sname)
187 | if err != nil {
188 | continue
189 | }
190 | parentKeys = append(parentKeys, sec.Keys()...)
191 | } else {
192 | break
193 | }
194 |
195 | }
196 | return parentKeys
197 | }
198 |
199 | // KeyStrings returns list of key names of section.
200 | func (s *Section) KeyStrings() []string {
201 | list := make([]string, len(s.keyList))
202 | copy(list, s.keyList)
203 | return list
204 | }
205 |
206 | // KeysHash returns keys hash consisting of names and values.
207 | func (s *Section) KeysHash() map[string]string {
208 | if s.f.BlockMode {
209 | s.f.lock.RLock()
210 | defer s.f.lock.RUnlock()
211 | }
212 |
213 | hash := map[string]string{}
214 | for key, value := range s.keysHash {
215 | hash[key] = value
216 | }
217 | return hash
218 | }
219 |
220 | // DeleteKey deletes a key from section.
221 | func (s *Section) DeleteKey(name string) {
222 | if s.f.BlockMode {
223 | s.f.lock.Lock()
224 | defer s.f.lock.Unlock()
225 | }
226 |
227 | for i, k := range s.keyList {
228 | if k == name {
229 | s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
230 | delete(s.keys, name)
231 | return
232 | }
233 | }
234 | }
235 |
236 | // ChildSections returns a list of child sections of current section.
237 | // For example, "[parent.child1]" and "[parent.child12]" are child sections
238 | // of section "[parent]".
239 | func (s *Section) ChildSections() []*Section {
240 | prefix := s.name + "."
241 | children := make([]*Section, 0, 3)
242 | for _, name := range s.f.sectionList {
243 | if strings.HasPrefix(name, prefix) {
244 | children = append(children, s.f.sections[name])
245 | }
246 | }
247 | return children
248 | }
249 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/alexcesaro/quotedprintable.v3/encodedword.go:
--------------------------------------------------------------------------------
1 | package quotedprintable
2 |
3 | import (
4 | "bytes"
5 | "encoding/base64"
6 | "errors"
7 | "fmt"
8 | "io"
9 | "strings"
10 | "unicode"
11 | "unicode/utf8"
12 | )
13 |
14 | // A WordEncoder is a RFC 2047 encoded-word encoder.
15 | type WordEncoder byte
16 |
17 | const (
18 | // BEncoding represents Base64 encoding scheme as defined by RFC 2045.
19 | BEncoding = WordEncoder('b')
20 | // QEncoding represents the Q-encoding scheme as defined by RFC 2047.
21 | QEncoding = WordEncoder('q')
22 | )
23 |
24 | var (
25 | errInvalidWord = errors.New("mime: invalid RFC 2047 encoded-word")
26 | )
27 |
28 | // Encode returns the encoded-word form of s. If s is ASCII without special
29 | // characters, it is returned unchanged. The provided charset is the IANA
30 | // charset name of s. It is case insensitive.
31 | func (e WordEncoder) Encode(charset, s string) string {
32 | if !needsEncoding(s) {
33 | return s
34 | }
35 | return e.encodeWord(charset, s)
36 | }
37 |
38 | func needsEncoding(s string) bool {
39 | for _, b := range s {
40 | if (b < ' ' || b > '~') && b != '\t' {
41 | return true
42 | }
43 | }
44 | return false
45 | }
46 |
47 | // encodeWord encodes a string into an encoded-word.
48 | func (e WordEncoder) encodeWord(charset, s string) string {
49 | buf := getBuffer()
50 | defer putBuffer(buf)
51 |
52 | buf.WriteString("=?")
53 | buf.WriteString(charset)
54 | buf.WriteByte('?')
55 | buf.WriteByte(byte(e))
56 | buf.WriteByte('?')
57 |
58 | if e == BEncoding {
59 | w := base64.NewEncoder(base64.StdEncoding, buf)
60 | io.WriteString(w, s)
61 | w.Close()
62 | } else {
63 | enc := make([]byte, 3)
64 | for i := 0; i < len(s); i++ {
65 | b := s[i]
66 | switch {
67 | case b == ' ':
68 | buf.WriteByte('_')
69 | case b <= '~' && b >= '!' && b != '=' && b != '?' && b != '_':
70 | buf.WriteByte(b)
71 | default:
72 | enc[0] = '='
73 | enc[1] = upperhex[b>>4]
74 | enc[2] = upperhex[b&0x0f]
75 | buf.Write(enc)
76 | }
77 | }
78 | }
79 | buf.WriteString("?=")
80 | return buf.String()
81 | }
82 |
83 | const upperhex = "0123456789ABCDEF"
84 |
85 | // A WordDecoder decodes MIME headers containing RFC 2047 encoded-words.
86 | type WordDecoder struct {
87 | // CharsetReader, if non-nil, defines a function to generate
88 | // charset-conversion readers, converting from the provided
89 | // charset into UTF-8.
90 | // Charsets are always lower-case. utf-8, iso-8859-1 and us-ascii charsets
91 | // are handled by default.
92 | // One of the the CharsetReader's result values must be non-nil.
93 | CharsetReader func(charset string, input io.Reader) (io.Reader, error)
94 | }
95 |
96 | // Decode decodes an encoded-word. If word is not a valid RFC 2047 encoded-word,
97 | // word is returned unchanged.
98 | func (d *WordDecoder) Decode(word string) (string, error) {
99 | fields := strings.Split(word, "?") // TODO: remove allocation?
100 | if len(fields) != 5 || fields[0] != "=" || fields[4] != "=" || len(fields[2]) != 1 {
101 | return "", errInvalidWord
102 | }
103 |
104 | content, err := decode(fields[2][0], fields[3])
105 | if err != nil {
106 | return "", err
107 | }
108 |
109 | buf := getBuffer()
110 | defer putBuffer(buf)
111 |
112 | if err := d.convert(buf, fields[1], content); err != nil {
113 | return "", err
114 | }
115 |
116 | return buf.String(), nil
117 | }
118 |
119 | // DecodeHeader decodes all encoded-words of the given string. It returns an
120 | // error if and only if CharsetReader of d returns an error.
121 | func (d *WordDecoder) DecodeHeader(header string) (string, error) {
122 | // If there is no encoded-word, returns before creating a buffer.
123 | i := strings.Index(header, "=?")
124 | if i == -1 {
125 | return header, nil
126 | }
127 |
128 | buf := getBuffer()
129 | defer putBuffer(buf)
130 |
131 | buf.WriteString(header[:i])
132 | header = header[i:]
133 |
134 | betweenWords := false
135 | for {
136 | start := strings.Index(header, "=?")
137 | if start == -1 {
138 | break
139 | }
140 | cur := start + len("=?")
141 |
142 | i := strings.Index(header[cur:], "?")
143 | if i == -1 {
144 | break
145 | }
146 | charset := header[cur : cur+i]
147 | cur += i + len("?")
148 |
149 | if len(header) < cur+len("Q??=") {
150 | break
151 | }
152 | encoding := header[cur]
153 | cur++
154 |
155 | if header[cur] != '?' {
156 | break
157 | }
158 | cur++
159 |
160 | j := strings.Index(header[cur:], "?=")
161 | if j == -1 {
162 | break
163 | }
164 | text := header[cur : cur+j]
165 | end := cur + j + len("?=")
166 |
167 | content, err := decode(encoding, text)
168 | if err != nil {
169 | betweenWords = false
170 | buf.WriteString(header[:start+2])
171 | header = header[start+2:]
172 | continue
173 | }
174 |
175 | // Write characters before the encoded-word. White-space and newline
176 | // characters separating two encoded-words must be deleted.
177 | if start > 0 && (!betweenWords || hasNonWhitespace(header[:start])) {
178 | buf.WriteString(header[:start])
179 | }
180 |
181 | if err := d.convert(buf, charset, content); err != nil {
182 | return "", err
183 | }
184 |
185 | header = header[end:]
186 | betweenWords = true
187 | }
188 |
189 | if len(header) > 0 {
190 | buf.WriteString(header)
191 | }
192 |
193 | return buf.String(), nil
194 | }
195 |
196 | func decode(encoding byte, text string) ([]byte, error) {
197 | switch encoding {
198 | case 'B', 'b':
199 | return base64.StdEncoding.DecodeString(text)
200 | case 'Q', 'q':
201 | return qDecode(text)
202 | }
203 | return nil, errInvalidWord
204 | }
205 |
206 | func (d *WordDecoder) convert(buf *bytes.Buffer, charset string, content []byte) error {
207 | switch {
208 | case strings.EqualFold("utf-8", charset):
209 | buf.Write(content)
210 | case strings.EqualFold("iso-8859-1", charset):
211 | for _, c := range content {
212 | buf.WriteRune(rune(c))
213 | }
214 | case strings.EqualFold("us-ascii", charset):
215 | for _, c := range content {
216 | if c >= utf8.RuneSelf {
217 | buf.WriteRune(unicode.ReplacementChar)
218 | } else {
219 | buf.WriteByte(c)
220 | }
221 | }
222 | default:
223 | if d.CharsetReader == nil {
224 | return fmt.Errorf("mime: unhandled charset %q", charset)
225 | }
226 | r, err := d.CharsetReader(strings.ToLower(charset), bytes.NewReader(content))
227 | if err != nil {
228 | return err
229 | }
230 | if _, err = buf.ReadFrom(r); err != nil {
231 | return err
232 | }
233 | }
234 | return nil
235 | }
236 |
237 | // hasNonWhitespace reports whether s (assumed to be ASCII) contains at least
238 | // one byte of non-whitespace.
239 | func hasNonWhitespace(s string) bool {
240 | for _, b := range s {
241 | switch b {
242 | // Encoded-words can only be separated by linear white spaces which does
243 | // not include vertical tabs (\v).
244 | case ' ', '\t', '\n', '\r':
245 | default:
246 | return true
247 | }
248 | }
249 | return false
250 | }
251 |
252 | // qDecode decodes a Q encoded string.
253 | func qDecode(s string) ([]byte, error) {
254 | dec := make([]byte, len(s))
255 | n := 0
256 | for i := 0; i < len(s); i++ {
257 | switch c := s[i]; {
258 | case c == '_':
259 | dec[n] = ' '
260 | case c == '=':
261 | if i+2 >= len(s) {
262 | return nil, errInvalidWord
263 | }
264 | b, err := readHexByte(s[i+1], s[i+2])
265 | if err != nil {
266 | return nil, err
267 | }
268 | dec[n] = b
269 | i += 2
270 | case (c <= '~' && c >= ' ') || c == '\n' || c == '\r' || c == '\t':
271 | dec[n] = c
272 | default:
273 | return nil, errInvalidWord
274 | }
275 | n++
276 | }
277 |
278 | return dec[:n], nil
279 | }
280 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/writeto.go:
--------------------------------------------------------------------------------
1 | package gomail
2 |
3 | import (
4 | "encoding/base64"
5 | "errors"
6 | "io"
7 | "mime"
8 | "mime/multipart"
9 | "path/filepath"
10 | "strings"
11 | "time"
12 | )
13 |
14 | // WriteTo implements io.WriterTo. It dumps the whole message into w.
15 | func (m *Message) WriteTo(w io.Writer) (int64, error) {
16 | mw := &messageWriter{w: w}
17 | mw.writeMessage(m)
18 | return mw.n, mw.err
19 | }
20 |
21 | func (w *messageWriter) writeMessage(m *Message) {
22 | if _, ok := m.header["Mime-Version"]; !ok {
23 | w.writeString("Mime-Version: 1.0\r\n")
24 | }
25 | if _, ok := m.header["Date"]; !ok {
26 | w.writeHeader("Date", m.FormatDate(now()))
27 | }
28 | w.writeHeaders(m.header)
29 |
30 | if m.hasMixedPart() {
31 | w.openMultipart("mixed")
32 | }
33 |
34 | if m.hasRelatedPart() {
35 | w.openMultipart("related")
36 | }
37 |
38 | if m.hasAlternativePart() {
39 | w.openMultipart("alternative")
40 | }
41 | for _, part := range m.parts {
42 | w.writePart(part, m.charset)
43 | }
44 | if m.hasAlternativePart() {
45 | w.closeMultipart()
46 | }
47 |
48 | w.addFiles(m.embedded, false)
49 | if m.hasRelatedPart() {
50 | w.closeMultipart()
51 | }
52 |
53 | w.addFiles(m.attachments, true)
54 | if m.hasMixedPart() {
55 | w.closeMultipart()
56 | }
57 | }
58 |
59 | func (m *Message) hasMixedPart() bool {
60 | return (len(m.parts) > 0 && len(m.attachments) > 0) || len(m.attachments) > 1
61 | }
62 |
63 | func (m *Message) hasRelatedPart() bool {
64 | return (len(m.parts) > 0 && len(m.embedded) > 0) || len(m.embedded) > 1
65 | }
66 |
67 | func (m *Message) hasAlternativePart() bool {
68 | return len(m.parts) > 1
69 | }
70 |
71 | type messageWriter struct {
72 | w io.Writer
73 | n int64
74 | writers [3]*multipart.Writer
75 | partWriter io.Writer
76 | depth uint8
77 | err error
78 | }
79 |
80 | func (w *messageWriter) openMultipart(mimeType string) {
81 | mw := multipart.NewWriter(w)
82 | contentType := "multipart/" + mimeType + ";\r\n boundary=" + mw.Boundary()
83 | w.writers[w.depth] = mw
84 |
85 | if w.depth == 0 {
86 | w.writeHeader("Content-Type", contentType)
87 | w.writeString("\r\n")
88 | } else {
89 | w.createPart(map[string][]string{
90 | "Content-Type": {contentType},
91 | })
92 | }
93 | w.depth++
94 | }
95 |
96 | func (w *messageWriter) createPart(h map[string][]string) {
97 | w.partWriter, w.err = w.writers[w.depth-1].CreatePart(h)
98 | }
99 |
100 | func (w *messageWriter) closeMultipart() {
101 | if w.depth > 0 {
102 | w.writers[w.depth-1].Close()
103 | w.depth--
104 | }
105 | }
106 |
107 | func (w *messageWriter) writePart(p *part, charset string) {
108 | w.writeHeaders(map[string][]string{
109 | "Content-Type": {p.contentType + "; charset=" + charset},
110 | "Content-Transfer-Encoding": {string(p.encoding)},
111 | })
112 | w.writeBody(p.copier, p.encoding)
113 | }
114 |
115 | func (w *messageWriter) addFiles(files []*file, isAttachment bool) {
116 | for _, f := range files {
117 | if _, ok := f.Header["Content-Type"]; !ok {
118 | mediaType := mime.TypeByExtension(filepath.Ext(f.Name))
119 | if mediaType == "" {
120 | mediaType = "application/octet-stream"
121 | }
122 | f.setHeader("Content-Type", mediaType+`; name="`+f.Name+`"`)
123 | }
124 |
125 | if _, ok := f.Header["Content-Transfer-Encoding"]; !ok {
126 | f.setHeader("Content-Transfer-Encoding", string(Base64))
127 | }
128 |
129 | if _, ok := f.Header["Content-Disposition"]; !ok {
130 | var disp string
131 | if isAttachment {
132 | disp = "attachment"
133 | } else {
134 | disp = "inline"
135 | }
136 | f.setHeader("Content-Disposition", disp+`; filename="`+f.Name+`"`)
137 | }
138 |
139 | if !isAttachment {
140 | if _, ok := f.Header["Content-ID"]; !ok {
141 | f.setHeader("Content-ID", "<"+f.Name+">")
142 | }
143 | }
144 | w.writeHeaders(f.Header)
145 | w.writeBody(f.CopyFunc, Base64)
146 | }
147 | }
148 |
149 | func (w *messageWriter) Write(p []byte) (int, error) {
150 | if w.err != nil {
151 | return 0, errors.New("gomail: cannot write as writer is in error")
152 | }
153 |
154 | var n int
155 | n, w.err = w.w.Write(p)
156 | w.n += int64(n)
157 | return n, w.err
158 | }
159 |
160 | func (w *messageWriter) writeString(s string) {
161 | n, _ := io.WriteString(w.w, s)
162 | w.n += int64(n)
163 | }
164 |
165 | func (w *messageWriter) writeHeader(k string, v ...string) {
166 | w.writeString(k)
167 | if len(v) == 0 {
168 | w.writeString(":\r\n")
169 | return
170 | }
171 | w.writeString(": ")
172 |
173 | // Max header line length is 78 characters in RFC 5322 and 76 characters
174 | // in RFC 2047. So for the sake of simplicity we use the 76 characters
175 | // limit.
176 | charsLeft := 76 - len(k) - len(": ")
177 |
178 | for i, s := range v {
179 | // If the line is already too long, insert a newline right away.
180 | if charsLeft < 1 {
181 | if i == 0 {
182 | w.writeString("\r\n ")
183 | } else {
184 | w.writeString(",\r\n ")
185 | }
186 | charsLeft = 75
187 | } else if i != 0 {
188 | w.writeString(", ")
189 | charsLeft -= 2
190 | }
191 |
192 | // While the header content is too long, fold it by inserting a newline.
193 | for len(s) > charsLeft {
194 | s = w.writeLine(s, charsLeft)
195 | charsLeft = 75
196 | }
197 | w.writeString(s)
198 | if i := lastIndexByte(s, '\n'); i != -1 {
199 | charsLeft = 75 - (len(s) - i - 1)
200 | } else {
201 | charsLeft -= len(s)
202 | }
203 | }
204 | w.writeString("\r\n")
205 | }
206 |
207 | func (w *messageWriter) writeLine(s string, charsLeft int) string {
208 | // If there is already a newline before the limit. Write the line.
209 | if i := strings.IndexByte(s, '\n'); i != -1 && i < charsLeft {
210 | w.writeString(s[:i+1])
211 | return s[i+1:]
212 | }
213 |
214 | for i := charsLeft - 1; i >= 0; i-- {
215 | if s[i] == ' ' {
216 | w.writeString(s[:i])
217 | w.writeString("\r\n ")
218 | return s[i+1:]
219 | }
220 | }
221 |
222 | // We could not insert a newline cleanly so look for a space or a newline
223 | // even if it is after the limit.
224 | for i := 75; i < len(s); i++ {
225 | if s[i] == ' ' {
226 | w.writeString(s[:i])
227 | w.writeString("\r\n ")
228 | return s[i+1:]
229 | }
230 | if s[i] == '\n' {
231 | w.writeString(s[:i+1])
232 | return s[i+1:]
233 | }
234 | }
235 |
236 | // Too bad, no space or newline in the whole string. Just write everything.
237 | w.writeString(s)
238 | return ""
239 | }
240 |
241 | func (w *messageWriter) writeHeaders(h map[string][]string) {
242 | if w.depth == 0 {
243 | for k, v := range h {
244 | if k != "Bcc" {
245 | w.writeHeader(k, v...)
246 | }
247 | }
248 | } else {
249 | w.createPart(h)
250 | }
251 | }
252 |
253 | func (w *messageWriter) writeBody(f func(io.Writer) error, enc Encoding) {
254 | var subWriter io.Writer
255 | if w.depth == 0 {
256 | w.writeString("\r\n")
257 | subWriter = w.w
258 | } else {
259 | subWriter = w.partWriter
260 | }
261 |
262 | if enc == Base64 {
263 | wc := base64.NewEncoder(base64.StdEncoding, newBase64LineWriter(subWriter))
264 | w.err = f(wc)
265 | wc.Close()
266 | } else if enc == Unencoded {
267 | w.err = f(subWriter)
268 | } else {
269 | wc := newQPWriter(subWriter)
270 | w.err = f(wc)
271 | wc.Close()
272 | }
273 | }
274 |
275 | // As required by RFC 2045, 6.7. (page 21) for quoted-printable, and
276 | // RFC 2045, 6.8. (page 25) for base64.
277 | const maxLineLen = 76
278 |
279 | // base64LineWriter limits text encoded in base64 to 76 characters per line
280 | type base64LineWriter struct {
281 | w io.Writer
282 | lineLen int
283 | }
284 |
285 | func newBase64LineWriter(w io.Writer) *base64LineWriter {
286 | return &base64LineWriter{w: w}
287 | }
288 |
289 | func (w *base64LineWriter) Write(p []byte) (int, error) {
290 | n := 0
291 | for len(p)+w.lineLen > maxLineLen {
292 | w.w.Write(p[:maxLineLen-w.lineLen])
293 | w.w.Write([]byte("\r\n"))
294 | p = p[maxLineLen-w.lineLen:]
295 | n += maxLineLen - w.lineLen
296 | w.lineLen = 0
297 | }
298 |
299 | w.w.Write(p)
300 | w.lineLen += len(p)
301 |
302 | return n + len(p), nil
303 | }
304 |
305 | // Stubbed out for testing.
306 | var now = time.Now
307 |
--------------------------------------------------------------------------------
/vendor/github.com/go-gomail/gomail/message.go:
--------------------------------------------------------------------------------
1 | package gomail
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "os"
7 | "path/filepath"
8 | "time"
9 | )
10 |
11 | // Message represents an email.
12 | type Message struct {
13 | header header
14 | parts []*part
15 | attachments []*file
16 | embedded []*file
17 | charset string
18 | encoding Encoding
19 | hEncoder mimeEncoder
20 | buf bytes.Buffer
21 | }
22 |
23 | type header map[string][]string
24 |
25 | type part struct {
26 | contentType string
27 | copier func(io.Writer) error
28 | encoding Encoding
29 | }
30 |
31 | // NewMessage creates a new message. It uses UTF-8 and quoted-printable encoding
32 | // by default.
33 | func NewMessage(settings ...MessageSetting) *Message {
34 | m := &Message{
35 | header: make(header),
36 | charset: "UTF-8",
37 | encoding: QuotedPrintable,
38 | }
39 |
40 | m.applySettings(settings)
41 |
42 | if m.encoding == Base64 {
43 | m.hEncoder = bEncoding
44 | } else {
45 | m.hEncoder = qEncoding
46 | }
47 |
48 | return m
49 | }
50 |
51 | // Reset resets the message so it can be reused. The message keeps its previous
52 | // settings so it is in the same state that after a call to NewMessage.
53 | func (m *Message) Reset() {
54 | for k := range m.header {
55 | delete(m.header, k)
56 | }
57 | m.parts = nil
58 | m.attachments = nil
59 | m.embedded = nil
60 | }
61 |
62 | func (m *Message) applySettings(settings []MessageSetting) {
63 | for _, s := range settings {
64 | s(m)
65 | }
66 | }
67 |
68 | // A MessageSetting can be used as an argument in NewMessage to configure an
69 | // email.
70 | type MessageSetting func(m *Message)
71 |
72 | // SetCharset is a message setting to set the charset of the email.
73 | func SetCharset(charset string) MessageSetting {
74 | return func(m *Message) {
75 | m.charset = charset
76 | }
77 | }
78 |
79 | // SetEncoding is a message setting to set the encoding of the email.
80 | func SetEncoding(enc Encoding) MessageSetting {
81 | return func(m *Message) {
82 | m.encoding = enc
83 | }
84 | }
85 |
86 | // Encoding represents a MIME encoding scheme like quoted-printable or base64.
87 | type Encoding string
88 |
89 | const (
90 | // QuotedPrintable represents the quoted-printable encoding as defined in
91 | // RFC 2045.
92 | QuotedPrintable Encoding = "quoted-printable"
93 | // Base64 represents the base64 encoding as defined in RFC 2045.
94 | Base64 Encoding = "base64"
95 | // Unencoded can be used to avoid encoding the body of an email. The headers
96 | // will still be encoded using quoted-printable encoding.
97 | Unencoded Encoding = "8bit"
98 | )
99 |
100 | // SetHeader sets a value to the given header field.
101 | func (m *Message) SetHeader(field string, value ...string) {
102 | m.encodeHeader(value)
103 | m.header[field] = value
104 | }
105 |
106 | func (m *Message) encodeHeader(values []string) {
107 | for i := range values {
108 | values[i] = m.encodeString(values[i])
109 | }
110 | }
111 |
112 | func (m *Message) encodeString(value string) string {
113 | return m.hEncoder.Encode(m.charset, value)
114 | }
115 |
116 | // SetHeaders sets the message headers.
117 | func (m *Message) SetHeaders(h map[string][]string) {
118 | for k, v := range h {
119 | m.SetHeader(k, v...)
120 | }
121 | }
122 |
123 | // SetAddressHeader sets an address to the given header field.
124 | func (m *Message) SetAddressHeader(field, address, name string) {
125 | m.header[field] = []string{m.FormatAddress(address, name)}
126 | }
127 |
128 | // FormatAddress formats an address and a name as a valid RFC 5322 address.
129 | func (m *Message) FormatAddress(address, name string) string {
130 | if name == "" {
131 | return address
132 | }
133 |
134 | enc := m.encodeString(name)
135 | if enc == name {
136 | m.buf.WriteByte('"')
137 | for i := 0; i < len(name); i++ {
138 | b := name[i]
139 | if b == '\\' || b == '"' {
140 | m.buf.WriteByte('\\')
141 | }
142 | m.buf.WriteByte(b)
143 | }
144 | m.buf.WriteByte('"')
145 | } else if hasSpecials(name) {
146 | m.buf.WriteString(bEncoding.Encode(m.charset, name))
147 | } else {
148 | m.buf.WriteString(enc)
149 | }
150 | m.buf.WriteString(" <")
151 | m.buf.WriteString(address)
152 | m.buf.WriteByte('>')
153 |
154 | addr := m.buf.String()
155 | m.buf.Reset()
156 | return addr
157 | }
158 |
159 | func hasSpecials(text string) bool {
160 | for i := 0; i < len(text); i++ {
161 | switch c := text[i]; c {
162 | case '(', ')', '<', '>', '[', ']', ':', ';', '@', '\\', ',', '.', '"':
163 | return true
164 | }
165 | }
166 |
167 | return false
168 | }
169 |
170 | // SetDateHeader sets a date to the given header field.
171 | func (m *Message) SetDateHeader(field string, date time.Time) {
172 | m.header[field] = []string{m.FormatDate(date)}
173 | }
174 |
175 | // FormatDate formats a date as a valid RFC 5322 date.
176 | func (m *Message) FormatDate(date time.Time) string {
177 | return date.Format(time.RFC1123Z)
178 | }
179 |
180 | // GetHeader gets a header field.
181 | func (m *Message) GetHeader(field string) []string {
182 | return m.header[field]
183 | }
184 |
185 | // SetBody sets the body of the message. It replaces any content previously set
186 | // by SetBody, AddAlternative or AddAlternativeWriter.
187 | func (m *Message) SetBody(contentType, body string, settings ...PartSetting) {
188 | m.parts = []*part{m.newPart(contentType, newCopier(body), settings)}
189 | }
190 |
191 | // AddAlternative adds an alternative part to the message.
192 | //
193 | // It is commonly used to send HTML emails that default to the plain text
194 | // version for backward compatibility. AddAlternative appends the new part to
195 | // the end of the message. So the plain text part should be added before the
196 | // HTML part. See http://en.wikipedia.org/wiki/MIME#Alternative
197 | func (m *Message) AddAlternative(contentType, body string, settings ...PartSetting) {
198 | m.AddAlternativeWriter(contentType, newCopier(body), settings...)
199 | }
200 |
201 | func newCopier(s string) func(io.Writer) error {
202 | return func(w io.Writer) error {
203 | _, err := io.WriteString(w, s)
204 | return err
205 | }
206 | }
207 |
208 | // AddAlternativeWriter adds an alternative part to the message. It can be
209 | // useful with the text/template or html/template packages.
210 | func (m *Message) AddAlternativeWriter(contentType string, f func(io.Writer) error, settings ...PartSetting) {
211 | m.parts = append(m.parts, m.newPart(contentType, f, settings))
212 | }
213 |
214 | func (m *Message) newPart(contentType string, f func(io.Writer) error, settings []PartSetting) *part {
215 | p := &part{
216 | contentType: contentType,
217 | copier: f,
218 | encoding: m.encoding,
219 | }
220 |
221 | for _, s := range settings {
222 | s(p)
223 | }
224 |
225 | return p
226 | }
227 |
228 | // A PartSetting can be used as an argument in Message.SetBody,
229 | // Message.AddAlternative or Message.AddAlternativeWriter to configure the part
230 | // added to a message.
231 | type PartSetting func(*part)
232 |
233 | // SetPartEncoding sets the encoding of the part added to the message. By
234 | // default, parts use the same encoding than the message.
235 | func SetPartEncoding(e Encoding) PartSetting {
236 | return PartSetting(func(p *part) {
237 | p.encoding = e
238 | })
239 | }
240 |
241 | type file struct {
242 | Name string
243 | Header map[string][]string
244 | CopyFunc func(w io.Writer) error
245 | }
246 |
247 | func (f *file) setHeader(field, value string) {
248 | f.Header[field] = []string{value}
249 | }
250 |
251 | // A FileSetting can be used as an argument in Message.Attach or Message.Embed.
252 | type FileSetting func(*file)
253 |
254 | // SetHeader is a file setting to set the MIME header of the message part that
255 | // contains the file content.
256 | //
257 | // Mandatory headers are automatically added if they are not set when sending
258 | // the email.
259 | func SetHeader(h map[string][]string) FileSetting {
260 | return func(f *file) {
261 | for k, v := range h {
262 | f.Header[k] = v
263 | }
264 | }
265 | }
266 |
267 | // Rename is a file setting to set the name of the attachment if the name is
268 | // different than the filename on disk.
269 | func Rename(name string) FileSetting {
270 | return func(f *file) {
271 | f.Name = name
272 | }
273 | }
274 |
275 | // SetCopyFunc is a file setting to replace the function that runs when the
276 | // message is sent. It should copy the content of the file to the io.Writer.
277 | //
278 | // The default copy function opens the file with the given filename, and copy
279 | // its content to the io.Writer.
280 | func SetCopyFunc(f func(io.Writer) error) FileSetting {
281 | return func(fi *file) {
282 | fi.CopyFunc = f
283 | }
284 | }
285 |
286 | func (m *Message) appendFile(list []*file, name string, settings []FileSetting) []*file {
287 | f := &file{
288 | Name: filepath.Base(name),
289 | Header: make(map[string][]string),
290 | CopyFunc: func(w io.Writer) error {
291 | h, err := os.Open(name)
292 | if err != nil {
293 | return err
294 | }
295 | if _, err := io.Copy(w, h); err != nil {
296 | h.Close()
297 | return err
298 | }
299 | return h.Close()
300 | },
301 | }
302 |
303 | for _, s := range settings {
304 | s(f)
305 | }
306 |
307 | if list == nil {
308 | return []*file{f}
309 | }
310 |
311 | return append(list, f)
312 | }
313 |
314 | // Attach attaches the files to the email.
315 | func (m *Message) Attach(filename string, settings ...FileSetting) {
316 | m.attachments = m.appendFile(m.attachments, filename, settings)
317 | }
318 |
319 | // Embed embeds the images to the email.
320 | func (m *Message) Embed(filename string, settings ...FileSetting) {
321 | m.embedded = m.appendFile(m.embedded, filename, settings)
322 | }
323 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/parser.go:
--------------------------------------------------------------------------------
1 | // Copyright 2015 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package ini
16 |
17 | import (
18 | "bufio"
19 | "bytes"
20 | "fmt"
21 | "io"
22 | "strconv"
23 | "strings"
24 | "unicode"
25 | )
26 |
27 | type tokenType int
28 |
29 | const (
30 | _TOKEN_INVALID tokenType = iota
31 | _TOKEN_COMMENT
32 | _TOKEN_SECTION
33 | _TOKEN_KEY
34 | )
35 |
36 | type parser struct {
37 | buf *bufio.Reader
38 | isEOF bool
39 | count int
40 | comment *bytes.Buffer
41 | }
42 |
43 | func newParser(r io.Reader) *parser {
44 | return &parser{
45 | buf: bufio.NewReader(r),
46 | count: 1,
47 | comment: &bytes.Buffer{},
48 | }
49 | }
50 |
51 | // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
52 | // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
53 | func (p *parser) BOM() error {
54 | mask, err := p.buf.Peek(2)
55 | if err != nil && err != io.EOF {
56 | return err
57 | } else if len(mask) < 2 {
58 | return nil
59 | }
60 |
61 | switch {
62 | case mask[0] == 254 && mask[1] == 255:
63 | fallthrough
64 | case mask[0] == 255 && mask[1] == 254:
65 | p.buf.Read(mask)
66 | case mask[0] == 239 && mask[1] == 187:
67 | mask, err := p.buf.Peek(3)
68 | if err != nil && err != io.EOF {
69 | return err
70 | } else if len(mask) < 3 {
71 | return nil
72 | }
73 | if mask[2] == 191 {
74 | p.buf.Read(mask)
75 | }
76 | }
77 | return nil
78 | }
79 |
80 | func (p *parser) readUntil(delim byte) ([]byte, error) {
81 | data, err := p.buf.ReadBytes(delim)
82 | if err != nil {
83 | if err == io.EOF {
84 | p.isEOF = true
85 | } else {
86 | return nil, err
87 | }
88 | }
89 | return data, nil
90 | }
91 |
92 | func cleanComment(in []byte) ([]byte, bool) {
93 | i := bytes.IndexAny(in, "#;")
94 | if i == -1 {
95 | return nil, false
96 | }
97 | return in[i:], true
98 | }
99 |
100 | func readKeyName(in []byte) (string, int, error) {
101 | line := string(in)
102 |
103 | // Check if key name surrounded by quotes.
104 | var keyQuote string
105 | if line[0] == '"' {
106 | if len(line) > 6 && string(line[0:3]) == `"""` {
107 | keyQuote = `"""`
108 | } else {
109 | keyQuote = `"`
110 | }
111 | } else if line[0] == '`' {
112 | keyQuote = "`"
113 | }
114 |
115 | // Get out key name
116 | endIdx := -1
117 | if len(keyQuote) > 0 {
118 | startIdx := len(keyQuote)
119 | // FIXME: fail case -> """"""name"""=value
120 | pos := strings.Index(line[startIdx:], keyQuote)
121 | if pos == -1 {
122 | return "", -1, fmt.Errorf("missing closing key quote: %s", line)
123 | }
124 | pos += startIdx
125 |
126 | // Find key-value delimiter
127 | i := strings.IndexAny(line[pos+startIdx:], "=:")
128 | if i < 0 {
129 | return "", -1, ErrDelimiterNotFound{line}
130 | }
131 | endIdx = pos + i
132 | return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
133 | }
134 |
135 | endIdx = strings.IndexAny(line, "=:")
136 | if endIdx < 0 {
137 | return "", -1, ErrDelimiterNotFound{line}
138 | }
139 | return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
140 | }
141 |
142 | func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
143 | for {
144 | data, err := p.readUntil('\n')
145 | if err != nil {
146 | return "", err
147 | }
148 | next := string(data)
149 |
150 | pos := strings.LastIndex(next, valQuote)
151 | if pos > -1 {
152 | val += next[:pos]
153 |
154 | comment, has := cleanComment([]byte(next[pos:]))
155 | if has {
156 | p.comment.Write(bytes.TrimSpace(comment))
157 | }
158 | break
159 | }
160 | val += next
161 | if p.isEOF {
162 | return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next)
163 | }
164 | }
165 | return val, nil
166 | }
167 |
168 | func (p *parser) readContinuationLines(val string) (string, error) {
169 | for {
170 | data, err := p.readUntil('\n')
171 | if err != nil {
172 | return "", err
173 | }
174 | next := strings.TrimSpace(string(data))
175 |
176 | if len(next) == 0 {
177 | break
178 | }
179 | val += next
180 | if val[len(val)-1] != '\\' {
181 | break
182 | }
183 | val = val[:len(val)-1]
184 | }
185 | return val, nil
186 | }
187 |
188 | // hasSurroundedQuote check if and only if the first and last characters
189 | // are quotes \" or \'.
190 | // It returns false if any other parts also contain same kind of quotes.
191 | func hasSurroundedQuote(in string, quote byte) bool {
192 | return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
193 | strings.IndexByte(in[1:], quote) == len(in)-2
194 | }
195 |
196 | func (p *parser) readValue(in []byte, ignoreContinuation, ignoreInlineComment bool) (string, error) {
197 | line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
198 | if len(line) == 0 {
199 | return "", nil
200 | }
201 |
202 | var valQuote string
203 | if len(line) > 3 && string(line[0:3]) == `"""` {
204 | valQuote = `"""`
205 | } else if line[0] == '`' {
206 | valQuote = "`"
207 | }
208 |
209 | if len(valQuote) > 0 {
210 | startIdx := len(valQuote)
211 | pos := strings.LastIndex(line[startIdx:], valQuote)
212 | // Check for multi-line value
213 | if pos == -1 {
214 | return p.readMultilines(line, line[startIdx:], valQuote)
215 | }
216 |
217 | return line[startIdx : pos+startIdx], nil
218 | }
219 |
220 | // Won't be able to reach here if value only contains whitespace
221 | line = strings.TrimSpace(line)
222 |
223 | // Check continuation lines when desired
224 | if !ignoreContinuation && line[len(line)-1] == '\\' {
225 | return p.readContinuationLines(line[:len(line)-1])
226 | }
227 |
228 | // Check if ignore inline comment
229 | if !ignoreInlineComment {
230 | i := strings.IndexAny(line, "#;")
231 | if i > -1 {
232 | p.comment.WriteString(line[i:])
233 | line = strings.TrimSpace(line[:i])
234 | }
235 | }
236 |
237 | // Trim single quotes
238 | if hasSurroundedQuote(line, '\'') ||
239 | hasSurroundedQuote(line, '"') {
240 | line = line[1 : len(line)-1]
241 | }
242 | return line, nil
243 | }
244 |
245 | // parse parses data through an io.Reader.
246 | func (f *File) parse(reader io.Reader) (err error) {
247 | p := newParser(reader)
248 | if err = p.BOM(); err != nil {
249 | return fmt.Errorf("BOM: %v", err)
250 | }
251 |
252 | // Ignore error because default section name is never empty string.
253 | section, _ := f.NewSection(DEFAULT_SECTION)
254 |
255 | var line []byte
256 | var inUnparseableSection bool
257 | for !p.isEOF {
258 | line, err = p.readUntil('\n')
259 | if err != nil {
260 | return err
261 | }
262 |
263 | line = bytes.TrimLeftFunc(line, unicode.IsSpace)
264 | if len(line) == 0 {
265 | continue
266 | }
267 |
268 | // Comments
269 | if line[0] == '#' || line[0] == ';' {
270 | // Note: we do not care ending line break,
271 | // it is needed for adding second line,
272 | // so just clean it once at the end when set to value.
273 | p.comment.Write(line)
274 | continue
275 | }
276 |
277 | // Section
278 | if line[0] == '[' {
279 | // Read to the next ']' (TODO: support quoted strings)
280 | // TODO(unknwon): use LastIndexByte when stop supporting Go1.4
281 | closeIdx := bytes.LastIndex(line, []byte("]"))
282 | if closeIdx == -1 {
283 | return fmt.Errorf("unclosed section: %s", line)
284 | }
285 |
286 | name := string(line[1:closeIdx])
287 | section, err = f.NewSection(name)
288 | if err != nil {
289 | return err
290 | }
291 |
292 | comment, has := cleanComment(line[closeIdx+1:])
293 | if has {
294 | p.comment.Write(comment)
295 | }
296 |
297 | section.Comment = strings.TrimSpace(p.comment.String())
298 |
299 | // Reset aotu-counter and comments
300 | p.comment.Reset()
301 | p.count = 1
302 |
303 | inUnparseableSection = false
304 | for i := range f.options.UnparseableSections {
305 | if f.options.UnparseableSections[i] == name ||
306 | (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) {
307 | inUnparseableSection = true
308 | continue
309 | }
310 | }
311 | continue
312 | }
313 |
314 | if inUnparseableSection {
315 | section.isRawSection = true
316 | section.rawBody += string(line)
317 | continue
318 | }
319 |
320 | kname, offset, err := readKeyName(line)
321 | if err != nil {
322 | // Treat as boolean key when desired, and whole line is key name.
323 | if IsErrDelimiterNotFound(err) && f.options.AllowBooleanKeys {
324 | kname, err := p.readValue(line, f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
325 | if err != nil {
326 | return err
327 | }
328 | key, err := section.NewBooleanKey(kname)
329 | if err != nil {
330 | return err
331 | }
332 | key.Comment = strings.TrimSpace(p.comment.String())
333 | p.comment.Reset()
334 | continue
335 | }
336 | return err
337 | }
338 |
339 | // Auto increment.
340 | isAutoIncr := false
341 | if kname == "-" {
342 | isAutoIncr = true
343 | kname = "#" + strconv.Itoa(p.count)
344 | p.count++
345 | }
346 |
347 | value, err := p.readValue(line[offset:], f.options.IgnoreContinuation, f.options.IgnoreInlineComment)
348 | if err != nil {
349 | return err
350 | }
351 |
352 | key, err := section.NewKey(kname, value)
353 | if err != nil {
354 | return err
355 | }
356 | key.isAutoIncrement = isAutoIncr
357 | key.Comment = strings.TrimSpace(p.comment.String())
358 | p.comment.Reset()
359 | }
360 | return nil
361 | }
362 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction, and
10 | distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright
13 | owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all other entities
16 | that control, are controlled by, or are under common control with that entity.
17 | For the purposes of this definition, "control" means (i) the power, direct or
18 | indirect, to cause the direction or management of such entity, whether by
19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
20 | outstanding shares, or (iii) beneficial ownership of such entity.
21 |
22 | "You" (or "Your") shall mean an individual or Legal Entity exercising
23 | permissions granted by this License.
24 |
25 | "Source" form shall mean the preferred form for making modifications, including
26 | but not limited to software source code, documentation source, and configuration
27 | files.
28 |
29 | "Object" form shall mean any form resulting from mechanical transformation or
30 | translation of a Source form, including but not limited to compiled object code,
31 | generated documentation, and conversions to other media types.
32 |
33 | "Work" shall mean the work of authorship, whether in Source or Object form, made
34 | available under the License, as indicated by a copyright notice that is included
35 | in or attached to the work (an example is provided in the Appendix below).
36 |
37 | "Derivative Works" shall mean any work, whether in Source or Object form, that
38 | is based on (or derived from) the Work and for which the editorial revisions,
39 | annotations, elaborations, or other modifications represent, as a whole, an
40 | original work of authorship. For the purposes of this License, Derivative Works
41 | shall not include works that remain separable from, or merely link (or bind by
42 | name) to the interfaces of, the Work and Derivative Works thereof.
43 |
44 | "Contribution" shall mean any work of authorship, including the original version
45 | of the Work and any modifications or additions to that Work or Derivative Works
46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work
47 | by the copyright owner or by an individual or Legal Entity authorized to submit
48 | on behalf of the copyright owner. For the purposes of this definition,
49 | "submitted" means any form of electronic, verbal, or written communication sent
50 | to the Licensor or its representatives, including but not limited to
51 | communication on electronic mailing lists, source code control systems, and
52 | issue tracking systems that are managed by, or on behalf of, the Licensor for
53 | the purpose of discussing and improving the Work, but excluding communication
54 | that is conspicuously marked or otherwise designated in writing by the copyright
55 | owner as "Not a Contribution."
56 |
57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf
58 | of whom a Contribution has been received by Licensor and subsequently
59 | incorporated within the Work.
60 |
61 | 2. Grant of Copyright License.
62 |
63 | Subject to the terms and conditions of this License, each Contributor hereby
64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
65 | irrevocable copyright license to reproduce, prepare Derivative Works of,
66 | publicly display, publicly perform, sublicense, and distribute the Work and such
67 | Derivative Works in Source or Object form.
68 |
69 | 3. Grant of Patent License.
70 |
71 | Subject to the terms and conditions of this License, each Contributor hereby
72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
73 | irrevocable (except as stated in this section) patent license to make, have
74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where
75 | such license applies only to those patent claims licensable by such Contributor
76 | that are necessarily infringed by their Contribution(s) alone or by combination
77 | of their Contribution(s) with the Work to which such Contribution(s) was
78 | submitted. If You institute patent litigation against any entity (including a
79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a
80 | Contribution incorporated within the Work constitutes direct or contributory
81 | patent infringement, then any patent licenses granted to You under this License
82 | for that Work shall terminate as of the date such litigation is filed.
83 |
84 | 4. Redistribution.
85 |
86 | You may reproduce and distribute copies of the Work or Derivative Works thereof
87 | in any medium, with or without modifications, and in Source or Object form,
88 | provided that You meet the following conditions:
89 |
90 | You must give any other recipients of the Work or Derivative Works a copy of
91 | this License; and
92 | You must cause any modified files to carry prominent notices stating that You
93 | changed the files; and
94 | You must retain, in the Source form of any Derivative Works that You distribute,
95 | all copyright, patent, trademark, and attribution notices from the Source form
96 | of the Work, excluding those notices that do not pertain to any part of the
97 | Derivative Works; and
98 | If the Work includes a "NOTICE" text file as part of its distribution, then any
99 | Derivative Works that You distribute must include a readable copy of the
100 | attribution notices contained within such NOTICE file, excluding those notices
101 | that do not pertain to any part of the Derivative Works, in at least one of the
102 | following places: within a NOTICE text file distributed as part of the
103 | Derivative Works; within the Source form or documentation, if provided along
104 | with the Derivative Works; or, within a display generated by the Derivative
105 | Works, if and wherever such third-party notices normally appear. The contents of
106 | the NOTICE file are for informational purposes only and do not modify the
107 | License. You may add Your own attribution notices within Derivative Works that
108 | You distribute, alongside or as an addendum to the NOTICE text from the Work,
109 | provided that such additional attribution notices cannot be construed as
110 | modifying the License.
111 | You may add Your own copyright statement to Your modifications and may provide
112 | additional or different license terms and conditions for use, reproduction, or
113 | distribution of Your modifications, or for any such Derivative Works as a whole,
114 | provided Your use, reproduction, and distribution of the Work otherwise complies
115 | with the conditions stated in this License.
116 |
117 | 5. Submission of Contributions.
118 |
119 | Unless You explicitly state otherwise, any Contribution intentionally submitted
120 | for inclusion in the Work by You to the Licensor shall be under the terms and
121 | conditions of this License, without any additional terms or conditions.
122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of
123 | any separate license agreement you may have executed with Licensor regarding
124 | such Contributions.
125 |
126 | 6. Trademarks.
127 |
128 | This License does not grant permission to use the trade names, trademarks,
129 | service marks, or product names of the Licensor, except as required for
130 | reasonable and customary use in describing the origin of the Work and
131 | reproducing the content of the NOTICE file.
132 |
133 | 7. Disclaimer of Warranty.
134 |
135 | Unless required by applicable law or agreed to in writing, Licensor provides the
136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
138 | including, without limitation, any warranties or conditions of TITLE,
139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
140 | solely responsible for determining the appropriateness of using or
141 | redistributing the Work and assume any risks associated with Your exercise of
142 | permissions under this License.
143 |
144 | 8. Limitation of Liability.
145 |
146 | In no event and under no legal theory, whether in tort (including negligence),
147 | contract, or otherwise, unless required by applicable law (such as deliberate
148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be
149 | liable to You for damages, including any direct, indirect, special, incidental,
150 | or consequential damages of any character arising as a result of this License or
151 | out of the use or inability to use the Work (including but not limited to
152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or
153 | any and all other commercial damages or losses), even if such Contributor has
154 | been advised of the possibility of such damages.
155 |
156 | 9. Accepting Warranty or Additional Liability.
157 |
158 | While redistributing the Work or Derivative Works thereof, You may choose to
159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or
160 | other liability obligations and/or rights consistent with this License. However,
161 | in accepting such obligations, You may act only on Your own behalf and on Your
162 | sole responsibility, not on behalf of any other Contributor, and only if You
163 | agree to indemnify, defend, and hold each Contributor harmless for any liability
164 | incurred by, or claims asserted against, such Contributor by reason of your
165 | accepting any such warranty or additional liability.
166 |
167 | END OF TERMS AND CONDITIONS
168 |
169 | APPENDIX: How to apply the Apache License to your work
170 |
171 | To apply the Apache License to your work, attach the following boilerplate
172 | notice, with the fields enclosed by brackets "[]" replaced with your own
173 | identifying information. (Don't include the brackets!) The text should be
174 | enclosed in the appropriate comment syntax for the file format. We also
175 | recommend that a file or class name and description of purpose be included on
176 | the same "printed page" as the copyright notice for easier identification within
177 | third-party archives.
178 |
179 | Copyright [yyyy] [name of copyright owner]
180 |
181 | Licensed under the Apache License, Version 2.0 (the "License");
182 | you may not use this file except in compliance with the License.
183 | You may obtain a copy of the License at
184 |
185 | http://www.apache.org/licenses/LICENSE-2.0
186 |
187 | Unless required by applicable law or agreed to in writing, software
188 | distributed under the License is distributed on an "AS IS" BASIS,
189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 | See the License for the specific language governing permissions and
191 | limitations under the License.
192 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/struct.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package ini
16 |
17 | import (
18 | "bytes"
19 | "errors"
20 | "fmt"
21 | "reflect"
22 | "strings"
23 | "time"
24 | "unicode"
25 | )
26 |
27 | // NameMapper represents a ini tag name mapper.
28 | type NameMapper func(string) string
29 |
30 | // Built-in name getters.
31 | var (
32 | // AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
33 | AllCapsUnderscore NameMapper = func(raw string) string {
34 | newstr := make([]rune, 0, len(raw))
35 | for i, chr := range raw {
36 | if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
37 | if i > 0 {
38 | newstr = append(newstr, '_')
39 | }
40 | }
41 | newstr = append(newstr, unicode.ToUpper(chr))
42 | }
43 | return string(newstr)
44 | }
45 | // TitleUnderscore converts to format title_underscore.
46 | TitleUnderscore NameMapper = func(raw string) string {
47 | newstr := make([]rune, 0, len(raw))
48 | for i, chr := range raw {
49 | if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
50 | if i > 0 {
51 | newstr = append(newstr, '_')
52 | }
53 | chr -= ('A' - 'a')
54 | }
55 | newstr = append(newstr, chr)
56 | }
57 | return string(newstr)
58 | }
59 | )
60 |
61 | func (s *Section) parseFieldName(raw, actual string) string {
62 | if len(actual) > 0 {
63 | return actual
64 | }
65 | if s.f.NameMapper != nil {
66 | return s.f.NameMapper(raw)
67 | }
68 | return raw
69 | }
70 |
71 | func parseDelim(actual string) string {
72 | if len(actual) > 0 {
73 | return actual
74 | }
75 | return ","
76 | }
77 |
78 | var reflectTime = reflect.TypeOf(time.Now()).Kind()
79 |
80 | // setSliceWithProperType sets proper values to slice based on its type.
81 | func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
82 | var strs []string
83 | if allowShadow {
84 | strs = key.StringsWithShadows(delim)
85 | } else {
86 | strs = key.Strings(delim)
87 | }
88 |
89 | numVals := len(strs)
90 | if numVals == 0 {
91 | return nil
92 | }
93 |
94 | var vals interface{}
95 | var err error
96 |
97 | sliceOf := field.Type().Elem().Kind()
98 | switch sliceOf {
99 | case reflect.String:
100 | vals = strs
101 | case reflect.Int:
102 | vals, err = key.parseInts(strs, true, false)
103 | case reflect.Int64:
104 | vals, err = key.parseInt64s(strs, true, false)
105 | case reflect.Uint:
106 | vals, err = key.parseUints(strs, true, false)
107 | case reflect.Uint64:
108 | vals, err = key.parseUint64s(strs, true, false)
109 | case reflect.Float64:
110 | vals, err = key.parseFloat64s(strs, true, false)
111 | case reflectTime:
112 | vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
113 | default:
114 | return fmt.Errorf("unsupported type '[]%s'", sliceOf)
115 | }
116 | if isStrict {
117 | return err
118 | }
119 |
120 | slice := reflect.MakeSlice(field.Type(), numVals, numVals)
121 | for i := 0; i < numVals; i++ {
122 | switch sliceOf {
123 | case reflect.String:
124 | slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
125 | case reflect.Int:
126 | slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
127 | case reflect.Int64:
128 | slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
129 | case reflect.Uint:
130 | slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
131 | case reflect.Uint64:
132 | slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
133 | case reflect.Float64:
134 | slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
135 | case reflectTime:
136 | slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
137 | }
138 | }
139 | field.Set(slice)
140 | return nil
141 | }
142 |
143 | func wrapStrictError(err error, isStrict bool) error {
144 | if isStrict {
145 | return err
146 | }
147 | return nil
148 | }
149 |
150 | // setWithProperType sets proper value to field based on its type,
151 | // but it does not return error for failing parsing,
152 | // because we want to use default value that is already assigned to strcut.
153 | func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
154 | switch t.Kind() {
155 | case reflect.String:
156 | if len(key.String()) == 0 {
157 | return nil
158 | }
159 | field.SetString(key.String())
160 | case reflect.Bool:
161 | boolVal, err := key.Bool()
162 | if err != nil {
163 | return wrapStrictError(err, isStrict)
164 | }
165 | field.SetBool(boolVal)
166 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
167 | durationVal, err := key.Duration()
168 | // Skip zero value
169 | if err == nil && int(durationVal) > 0 {
170 | field.Set(reflect.ValueOf(durationVal))
171 | return nil
172 | }
173 |
174 | intVal, err := key.Int64()
175 | if err != nil {
176 | return wrapStrictError(err, isStrict)
177 | }
178 | field.SetInt(intVal)
179 | // byte is an alias for uint8, so supporting uint8 breaks support for byte
180 | case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
181 | durationVal, err := key.Duration()
182 | // Skip zero value
183 | if err == nil && int(durationVal) > 0 {
184 | field.Set(reflect.ValueOf(durationVal))
185 | return nil
186 | }
187 |
188 | uintVal, err := key.Uint64()
189 | if err != nil {
190 | return wrapStrictError(err, isStrict)
191 | }
192 | field.SetUint(uintVal)
193 |
194 | case reflect.Float32, reflect.Float64:
195 | floatVal, err := key.Float64()
196 | if err != nil {
197 | return wrapStrictError(err, isStrict)
198 | }
199 | field.SetFloat(floatVal)
200 | case reflectTime:
201 | timeVal, err := key.Time()
202 | if err != nil {
203 | return wrapStrictError(err, isStrict)
204 | }
205 | field.Set(reflect.ValueOf(timeVal))
206 | case reflect.Slice:
207 | return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
208 | default:
209 | return fmt.Errorf("unsupported type '%s'", t)
210 | }
211 | return nil
212 | }
213 |
214 | func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) {
215 | opts := strings.SplitN(tag, ",", 3)
216 | rawName = opts[0]
217 | if len(opts) > 1 {
218 | omitEmpty = opts[1] == "omitempty"
219 | }
220 | if len(opts) > 2 {
221 | allowShadow = opts[2] == "allowshadow"
222 | }
223 | return rawName, omitEmpty, allowShadow
224 | }
225 |
226 | func (s *Section) mapTo(val reflect.Value, isStrict bool) error {
227 | if val.Kind() == reflect.Ptr {
228 | val = val.Elem()
229 | }
230 | typ := val.Type()
231 |
232 | for i := 0; i < typ.NumField(); i++ {
233 | field := val.Field(i)
234 | tpField := typ.Field(i)
235 |
236 | tag := tpField.Tag.Get("ini")
237 | if tag == "-" {
238 | continue
239 | }
240 |
241 | rawName, _, allowShadow := parseTagOptions(tag)
242 | fieldName := s.parseFieldName(tpField.Name, rawName)
243 | if len(fieldName) == 0 || !field.CanSet() {
244 | continue
245 | }
246 |
247 | isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
248 | isStruct := tpField.Type.Kind() == reflect.Struct
249 | if isAnonymous {
250 | field.Set(reflect.New(tpField.Type.Elem()))
251 | }
252 |
253 | if isAnonymous || isStruct {
254 | if sec, err := s.f.GetSection(fieldName); err == nil {
255 | if err = sec.mapTo(field, isStrict); err != nil {
256 | return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
257 | }
258 | continue
259 | }
260 | }
261 |
262 | if key, err := s.GetKey(fieldName); err == nil {
263 | delim := parseDelim(tpField.Tag.Get("delim"))
264 | if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
265 | return fmt.Errorf("error mapping field(%s): %v", fieldName, err)
266 | }
267 | }
268 | }
269 | return nil
270 | }
271 |
272 | // MapTo maps section to given struct.
273 | func (s *Section) MapTo(v interface{}) error {
274 | typ := reflect.TypeOf(v)
275 | val := reflect.ValueOf(v)
276 | if typ.Kind() == reflect.Ptr {
277 | typ = typ.Elem()
278 | val = val.Elem()
279 | } else {
280 | return errors.New("cannot map to non-pointer struct")
281 | }
282 |
283 | return s.mapTo(val, false)
284 | }
285 |
286 | // MapTo maps section to given struct in strict mode,
287 | // which returns all possible error including value parsing error.
288 | func (s *Section) StrictMapTo(v interface{}) error {
289 | typ := reflect.TypeOf(v)
290 | val := reflect.ValueOf(v)
291 | if typ.Kind() == reflect.Ptr {
292 | typ = typ.Elem()
293 | val = val.Elem()
294 | } else {
295 | return errors.New("cannot map to non-pointer struct")
296 | }
297 |
298 | return s.mapTo(val, true)
299 | }
300 |
301 | // MapTo maps file to given struct.
302 | func (f *File) MapTo(v interface{}) error {
303 | return f.Section("").MapTo(v)
304 | }
305 |
306 | // MapTo maps file to given struct in strict mode,
307 | // which returns all possible error including value parsing error.
308 | func (f *File) StrictMapTo(v interface{}) error {
309 | return f.Section("").StrictMapTo(v)
310 | }
311 |
312 | // MapTo maps data sources to given struct with name mapper.
313 | func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
314 | cfg, err := Load(source, others...)
315 | if err != nil {
316 | return err
317 | }
318 | cfg.NameMapper = mapper
319 | return cfg.MapTo(v)
320 | }
321 |
322 | // StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
323 | // which returns all possible error including value parsing error.
324 | func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
325 | cfg, err := Load(source, others...)
326 | if err != nil {
327 | return err
328 | }
329 | cfg.NameMapper = mapper
330 | return cfg.StrictMapTo(v)
331 | }
332 |
333 | // MapTo maps data sources to given struct.
334 | func MapTo(v, source interface{}, others ...interface{}) error {
335 | return MapToWithMapper(v, nil, source, others...)
336 | }
337 |
338 | // StrictMapTo maps data sources to given struct in strict mode,
339 | // which returns all possible error including value parsing error.
340 | func StrictMapTo(v, source interface{}, others ...interface{}) error {
341 | return StrictMapToWithMapper(v, nil, source, others...)
342 | }
343 |
344 | // reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
345 | func reflectSliceWithProperType(key *Key, field reflect.Value, delim string) error {
346 | slice := field.Slice(0, field.Len())
347 | if field.Len() == 0 {
348 | return nil
349 | }
350 |
351 | var buf bytes.Buffer
352 | sliceOf := field.Type().Elem().Kind()
353 | for i := 0; i < field.Len(); i++ {
354 | switch sliceOf {
355 | case reflect.String:
356 | buf.WriteString(slice.Index(i).String())
357 | case reflect.Int, reflect.Int64:
358 | buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
359 | case reflect.Uint, reflect.Uint64:
360 | buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
361 | case reflect.Float64:
362 | buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
363 | case reflectTime:
364 | buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
365 | default:
366 | return fmt.Errorf("unsupported type '[]%s'", sliceOf)
367 | }
368 | buf.WriteString(delim)
369 | }
370 | key.SetValue(buf.String()[:buf.Len()-1])
371 | return nil
372 | }
373 |
374 | // reflectWithProperType does the opposite thing as setWithProperType.
375 | func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string) error {
376 | switch t.Kind() {
377 | case reflect.String:
378 | key.SetValue(field.String())
379 | case reflect.Bool:
380 | key.SetValue(fmt.Sprint(field.Bool()))
381 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
382 | key.SetValue(fmt.Sprint(field.Int()))
383 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
384 | key.SetValue(fmt.Sprint(field.Uint()))
385 | case reflect.Float32, reflect.Float64:
386 | key.SetValue(fmt.Sprint(field.Float()))
387 | case reflectTime:
388 | key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
389 | case reflect.Slice:
390 | return reflectSliceWithProperType(key, field, delim)
391 | default:
392 | return fmt.Errorf("unsupported type '%s'", t)
393 | }
394 | return nil
395 | }
396 |
397 | // CR: copied from encoding/json/encode.go with modifications of time.Time support.
398 | // TODO: add more test coverage.
399 | func isEmptyValue(v reflect.Value) bool {
400 | switch v.Kind() {
401 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
402 | return v.Len() == 0
403 | case reflect.Bool:
404 | return !v.Bool()
405 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
406 | return v.Int() == 0
407 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
408 | return v.Uint() == 0
409 | case reflect.Float32, reflect.Float64:
410 | return v.Float() == 0
411 | case reflectTime:
412 | return v.Interface().(time.Time).IsZero()
413 | case reflect.Interface, reflect.Ptr:
414 | return v.IsNil()
415 | }
416 | return false
417 | }
418 |
419 | func (s *Section) reflectFrom(val reflect.Value) error {
420 | if val.Kind() == reflect.Ptr {
421 | val = val.Elem()
422 | }
423 | typ := val.Type()
424 |
425 | for i := 0; i < typ.NumField(); i++ {
426 | field := val.Field(i)
427 | tpField := typ.Field(i)
428 |
429 | tag := tpField.Tag.Get("ini")
430 | if tag == "-" {
431 | continue
432 | }
433 |
434 | opts := strings.SplitN(tag, ",", 2)
435 | if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) {
436 | continue
437 | }
438 |
439 | fieldName := s.parseFieldName(tpField.Name, opts[0])
440 | if len(fieldName) == 0 || !field.CanSet() {
441 | continue
442 | }
443 |
444 | if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
445 | (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
446 | // Note: The only error here is section doesn't exist.
447 | sec, err := s.f.GetSection(fieldName)
448 | if err != nil {
449 | // Note: fieldName can never be empty here, ignore error.
450 | sec, _ = s.f.NewSection(fieldName)
451 | }
452 | if err = sec.reflectFrom(field); err != nil {
453 | return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
454 | }
455 | continue
456 | }
457 |
458 | // Note: Same reason as secion.
459 | key, err := s.GetKey(fieldName)
460 | if err != nil {
461 | key, _ = s.NewKey(fieldName, "")
462 | }
463 | if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil {
464 | return fmt.Errorf("error reflecting field (%s): %v", fieldName, err)
465 | }
466 |
467 | }
468 | return nil
469 | }
470 |
471 | // ReflectFrom reflects secion from given struct.
472 | func (s *Section) ReflectFrom(v interface{}) error {
473 | typ := reflect.TypeOf(v)
474 | val := reflect.ValueOf(v)
475 | if typ.Kind() == reflect.Ptr {
476 | typ = typ.Elem()
477 | val = val.Elem()
478 | } else {
479 | return errors.New("cannot reflect from non-pointer struct")
480 | }
481 |
482 | return s.reflectFrom(val)
483 | }
484 |
485 | // ReflectFrom reflects file from given struct.
486 | func (f *File) ReflectFrom(v interface{}) error {
487 | return f.Section("").ReflectFrom(v)
488 | }
489 |
490 | // ReflectFrom reflects data sources from given struct with name mapper.
491 | func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
492 | cfg.NameMapper = mapper
493 | return cfg.ReflectFrom(v)
494 | }
495 |
496 | // ReflectFrom reflects data sources from given struct.
497 | func ReflectFrom(cfg *File, v interface{}) error {
498 | return ReflectFromWithMapper(cfg, v, nil)
499 | }
500 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/ini.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | // Package ini provides INI file read and write functionality in Go.
16 | package ini
17 |
18 | import (
19 | "bytes"
20 | "errors"
21 | "fmt"
22 | "io"
23 | "io/ioutil"
24 | "os"
25 | "regexp"
26 | "runtime"
27 | "strconv"
28 | "strings"
29 | "sync"
30 | "time"
31 | )
32 |
33 | const (
34 | // Name for default section. You can use this constant or the string literal.
35 | // In most of cases, an empty string is all you need to access the section.
36 | DEFAULT_SECTION = "DEFAULT"
37 |
38 | // Maximum allowed depth when recursively substituing variable names.
39 | _DEPTH_VALUES = 99
40 | _VERSION = "1.28.0"
41 | )
42 |
43 | // Version returns current package version literal.
44 | func Version() string {
45 | return _VERSION
46 | }
47 |
48 | var (
49 | // Delimiter to determine or compose a new line.
50 | // This variable will be changed to "\r\n" automatically on Windows
51 | // at package init time.
52 | LineBreak = "\n"
53 |
54 | // Variable regexp pattern: %(variable)s
55 | varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`)
56 |
57 | // Indicate whether to align "=" sign with spaces to produce pretty output
58 | // or reduce all possible spaces for compact format.
59 | PrettyFormat = true
60 |
61 | // Explicitly write DEFAULT section header
62 | DefaultHeader = false
63 |
64 | // Indicate whether to put a line between sections
65 | PrettySection = true
66 | )
67 |
68 | func init() {
69 | if runtime.GOOS == "windows" {
70 | LineBreak = "\r\n"
71 | }
72 | }
73 |
74 | func inSlice(str string, s []string) bool {
75 | for _, v := range s {
76 | if str == v {
77 | return true
78 | }
79 | }
80 | return false
81 | }
82 |
83 | // dataSource is an interface that returns object which can be read and closed.
84 | type dataSource interface {
85 | ReadCloser() (io.ReadCloser, error)
86 | }
87 |
88 | // sourceFile represents an object that contains content on the local file system.
89 | type sourceFile struct {
90 | name string
91 | }
92 |
93 | func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
94 | return os.Open(s.name)
95 | }
96 |
97 | type bytesReadCloser struct {
98 | reader io.Reader
99 | }
100 |
101 | func (rc *bytesReadCloser) Read(p []byte) (n int, err error) {
102 | return rc.reader.Read(p)
103 | }
104 |
105 | func (rc *bytesReadCloser) Close() error {
106 | return nil
107 | }
108 |
109 | // sourceData represents an object that contains content in memory.
110 | type sourceData struct {
111 | data []byte
112 | }
113 |
114 | func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
115 | return ioutil.NopCloser(bytes.NewReader(s.data)), nil
116 | }
117 |
118 | // sourceReadCloser represents an input stream with Close method.
119 | type sourceReadCloser struct {
120 | reader io.ReadCloser
121 | }
122 |
123 | func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
124 | return s.reader, nil
125 | }
126 |
127 | // File represents a combination of a or more INI file(s) in memory.
128 | type File struct {
129 | // Should make things safe, but sometimes doesn't matter.
130 | BlockMode bool
131 | // Make sure data is safe in multiple goroutines.
132 | lock sync.RWMutex
133 |
134 | // Allow combination of multiple data sources.
135 | dataSources []dataSource
136 | // Actual data is stored here.
137 | sections map[string]*Section
138 |
139 | // To keep data in order.
140 | sectionList []string
141 |
142 | options LoadOptions
143 |
144 | NameMapper
145 | ValueMapper
146 | }
147 |
148 | // newFile initializes File object with given data sources.
149 | func newFile(dataSources []dataSource, opts LoadOptions) *File {
150 | return &File{
151 | BlockMode: true,
152 | dataSources: dataSources,
153 | sections: make(map[string]*Section),
154 | sectionList: make([]string, 0, 10),
155 | options: opts,
156 | }
157 | }
158 |
159 | func parseDataSource(source interface{}) (dataSource, error) {
160 | switch s := source.(type) {
161 | case string:
162 | return sourceFile{s}, nil
163 | case []byte:
164 | return &sourceData{s}, nil
165 | case io.ReadCloser:
166 | return &sourceReadCloser{s}, nil
167 | default:
168 | return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s)
169 | }
170 | }
171 |
172 | type LoadOptions struct {
173 | // Loose indicates whether the parser should ignore nonexistent files or return error.
174 | Loose bool
175 | // Insensitive indicates whether the parser forces all section and key names to lowercase.
176 | Insensitive bool
177 | // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
178 | IgnoreContinuation bool
179 | // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
180 | IgnoreInlineComment bool
181 | // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
182 | // This type of keys are mostly used in my.cnf.
183 | AllowBooleanKeys bool
184 | // AllowShadows indicates whether to keep track of keys with same name under same section.
185 | AllowShadows bool
186 | // Some INI formats allow group blocks that store a block of raw content that doesn't otherwise
187 | // conform to key/value pairs. Specify the names of those blocks here.
188 | UnparseableSections []string
189 | }
190 |
191 | func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
192 | sources := make([]dataSource, len(others)+1)
193 | sources[0], err = parseDataSource(source)
194 | if err != nil {
195 | return nil, err
196 | }
197 | for i := range others {
198 | sources[i+1], err = parseDataSource(others[i])
199 | if err != nil {
200 | return nil, err
201 | }
202 | }
203 | f := newFile(sources, opts)
204 | if err = f.Reload(); err != nil {
205 | return nil, err
206 | }
207 | return f, nil
208 | }
209 |
210 | // Load loads and parses from INI data sources.
211 | // Arguments can be mixed of file name with string type, or raw data in []byte.
212 | // It will return error if list contains nonexistent files.
213 | func Load(source interface{}, others ...interface{}) (*File, error) {
214 | return LoadSources(LoadOptions{}, source, others...)
215 | }
216 |
217 | // LooseLoad has exactly same functionality as Load function
218 | // except it ignores nonexistent files instead of returning error.
219 | func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
220 | return LoadSources(LoadOptions{Loose: true}, source, others...)
221 | }
222 |
223 | // InsensitiveLoad has exactly same functionality as Load function
224 | // except it forces all section and key names to be lowercased.
225 | func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
226 | return LoadSources(LoadOptions{Insensitive: true}, source, others...)
227 | }
228 |
229 | // InsensitiveLoad has exactly same functionality as Load function
230 | // except it allows have shadow keys.
231 | func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
232 | return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
233 | }
234 |
235 | // Empty returns an empty file object.
236 | func Empty() *File {
237 | // Ignore error here, we sure our data is good.
238 | f, _ := Load([]byte(""))
239 | return f
240 | }
241 |
242 | // NewSection creates a new section.
243 | func (f *File) NewSection(name string) (*Section, error) {
244 | if len(name) == 0 {
245 | return nil, errors.New("error creating new section: empty section name")
246 | } else if f.options.Insensitive && name != DEFAULT_SECTION {
247 | name = strings.ToLower(name)
248 | }
249 |
250 | if f.BlockMode {
251 | f.lock.Lock()
252 | defer f.lock.Unlock()
253 | }
254 |
255 | if inSlice(name, f.sectionList) {
256 | return f.sections[name], nil
257 | }
258 |
259 | f.sectionList = append(f.sectionList, name)
260 | f.sections[name] = newSection(f, name)
261 | return f.sections[name], nil
262 | }
263 |
264 | // NewRawSection creates a new section with an unparseable body.
265 | func (f *File) NewRawSection(name, body string) (*Section, error) {
266 | section, err := f.NewSection(name)
267 | if err != nil {
268 | return nil, err
269 | }
270 |
271 | section.isRawSection = true
272 | section.rawBody = body
273 | return section, nil
274 | }
275 |
276 | // NewSections creates a list of sections.
277 | func (f *File) NewSections(names ...string) (err error) {
278 | for _, name := range names {
279 | if _, err = f.NewSection(name); err != nil {
280 | return err
281 | }
282 | }
283 | return nil
284 | }
285 |
286 | // GetSection returns section by given name.
287 | func (f *File) GetSection(name string) (*Section, error) {
288 | if len(name) == 0 {
289 | name = DEFAULT_SECTION
290 | } else if f.options.Insensitive {
291 | name = strings.ToLower(name)
292 | }
293 |
294 | if f.BlockMode {
295 | f.lock.RLock()
296 | defer f.lock.RUnlock()
297 | }
298 |
299 | sec := f.sections[name]
300 | if sec == nil {
301 | return nil, fmt.Errorf("section '%s' does not exist", name)
302 | }
303 | return sec, nil
304 | }
305 |
306 | // Section assumes named section exists and returns a zero-value when not.
307 | func (f *File) Section(name string) *Section {
308 | sec, err := f.GetSection(name)
309 | if err != nil {
310 | // Note: It's OK here because the only possible error is empty section name,
311 | // but if it's empty, this piece of code won't be executed.
312 | sec, _ = f.NewSection(name)
313 | return sec
314 | }
315 | return sec
316 | }
317 |
318 | // Section returns list of Section.
319 | func (f *File) Sections() []*Section {
320 | sections := make([]*Section, len(f.sectionList))
321 | for i := range f.sectionList {
322 | sections[i] = f.Section(f.sectionList[i])
323 | }
324 | return sections
325 | }
326 |
327 | // ChildSections returns a list of child sections of given section name.
328 | func (f *File) ChildSections(name string) []*Section {
329 | return f.Section(name).ChildSections()
330 | }
331 |
332 | // SectionStrings returns list of section names.
333 | func (f *File) SectionStrings() []string {
334 | list := make([]string, len(f.sectionList))
335 | copy(list, f.sectionList)
336 | return list
337 | }
338 |
339 | // DeleteSection deletes a section.
340 | func (f *File) DeleteSection(name string) {
341 | if f.BlockMode {
342 | f.lock.Lock()
343 | defer f.lock.Unlock()
344 | }
345 |
346 | if len(name) == 0 {
347 | name = DEFAULT_SECTION
348 | }
349 |
350 | for i, s := range f.sectionList {
351 | if s == name {
352 | f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
353 | delete(f.sections, name)
354 | return
355 | }
356 | }
357 | }
358 |
359 | func (f *File) reload(s dataSource) error {
360 | r, err := s.ReadCloser()
361 | if err != nil {
362 | return err
363 | }
364 | defer r.Close()
365 |
366 | return f.parse(r)
367 | }
368 |
369 | // Reload reloads and parses all data sources.
370 | func (f *File) Reload() (err error) {
371 | for _, s := range f.dataSources {
372 | if err = f.reload(s); err != nil {
373 | // In loose mode, we create an empty default section for nonexistent files.
374 | if os.IsNotExist(err) && f.options.Loose {
375 | f.parse(bytes.NewBuffer(nil))
376 | continue
377 | }
378 | return err
379 | }
380 | }
381 | return nil
382 | }
383 |
384 | // Append appends one or more data sources and reloads automatically.
385 | func (f *File) Append(source interface{}, others ...interface{}) error {
386 | ds, err := parseDataSource(source)
387 | if err != nil {
388 | return err
389 | }
390 | f.dataSources = append(f.dataSources, ds)
391 | for _, s := range others {
392 | ds, err = parseDataSource(s)
393 | if err != nil {
394 | return err
395 | }
396 | f.dataSources = append(f.dataSources, ds)
397 | }
398 | return f.Reload()
399 | }
400 |
401 | // WriteToIndent writes content into io.Writer with given indention.
402 | // If PrettyFormat has been set to be true,
403 | // it will align "=" sign with spaces under each section.
404 | func (f *File) WriteToIndent(w io.Writer, indent string) (n int64, err error) {
405 | equalSign := "="
406 | if PrettyFormat {
407 | equalSign = " = "
408 | }
409 |
410 | // Use buffer to make sure target is safe until finish encoding.
411 | buf := bytes.NewBuffer(nil)
412 | for i, sname := range f.sectionList {
413 | sec := f.Section(sname)
414 | if len(sec.Comment) > 0 {
415 | if sec.Comment[0] != '#' && sec.Comment[0] != ';' {
416 | sec.Comment = "; " + sec.Comment
417 | }
418 | if _, err = buf.WriteString(sec.Comment + LineBreak); err != nil {
419 | return 0, err
420 | }
421 | }
422 |
423 | if i > 0 || DefaultHeader {
424 | if _, err = buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
425 | return 0, err
426 | }
427 | } else {
428 | // Write nothing if default section is empty
429 | if len(sec.keyList) == 0 {
430 | continue
431 | }
432 | }
433 |
434 | if sec.isRawSection {
435 | if _, err = buf.WriteString(sec.rawBody); err != nil {
436 | return 0, err
437 | }
438 | continue
439 | }
440 |
441 | // Count and generate alignment length and buffer spaces using the
442 | // longest key. Keys may be modifed if they contain certain characters so
443 | // we need to take that into account in our calculation.
444 | alignLength := 0
445 | if PrettyFormat {
446 | for _, kname := range sec.keyList {
447 | keyLength := len(kname)
448 | // First case will surround key by ` and second by """
449 | if strings.ContainsAny(kname, "\"=:") {
450 | keyLength += 2
451 | } else if strings.Contains(kname, "`") {
452 | keyLength += 6
453 | }
454 |
455 | if keyLength > alignLength {
456 | alignLength = keyLength
457 | }
458 | }
459 | }
460 | alignSpaces := bytes.Repeat([]byte(" "), alignLength)
461 |
462 | KEY_LIST:
463 | for _, kname := range sec.keyList {
464 | key := sec.Key(kname)
465 | if len(key.Comment) > 0 {
466 | if len(indent) > 0 && sname != DEFAULT_SECTION {
467 | buf.WriteString(indent)
468 | }
469 | if key.Comment[0] != '#' && key.Comment[0] != ';' {
470 | key.Comment = "; " + key.Comment
471 | }
472 | if _, err = buf.WriteString(key.Comment + LineBreak); err != nil {
473 | return 0, err
474 | }
475 | }
476 |
477 | if len(indent) > 0 && sname != DEFAULT_SECTION {
478 | buf.WriteString(indent)
479 | }
480 |
481 | switch {
482 | case key.isAutoIncrement:
483 | kname = "-"
484 | case strings.ContainsAny(kname, "\"=:"):
485 | kname = "`" + kname + "`"
486 | case strings.Contains(kname, "`"):
487 | kname = `"""` + kname + `"""`
488 | }
489 |
490 | for _, val := range key.ValueWithShadows() {
491 | if _, err = buf.WriteString(kname); err != nil {
492 | return 0, err
493 | }
494 |
495 | if key.isBooleanType {
496 | if kname != sec.keyList[len(sec.keyList)-1] {
497 | buf.WriteString(LineBreak)
498 | }
499 | continue KEY_LIST
500 | }
501 |
502 | // Write out alignment spaces before "=" sign
503 | if PrettyFormat {
504 | buf.Write(alignSpaces[:alignLength-len(kname)])
505 | }
506 |
507 | // In case key value contains "\n", "`", "\"", "#" or ";"
508 | if strings.ContainsAny(val, "\n`") {
509 | val = `"""` + val + `"""`
510 | } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
511 | val = "`" + val + "`"
512 | }
513 | if _, err = buf.WriteString(equalSign + val + LineBreak); err != nil {
514 | return 0, err
515 | }
516 | }
517 | }
518 |
519 | if PrettySection {
520 | // Put a line between sections
521 | if _, err = buf.WriteString(LineBreak); err != nil {
522 | return 0, err
523 | }
524 | }
525 | }
526 |
527 | return buf.WriteTo(w)
528 | }
529 |
530 | // WriteTo writes file content into io.Writer.
531 | func (f *File) WriteTo(w io.Writer) (int64, error) {
532 | return f.WriteToIndent(w, "")
533 | }
534 |
535 | // SaveToIndent writes content to file system with given value indention.
536 | func (f *File) SaveToIndent(filename, indent string) error {
537 | // Note: Because we are truncating with os.Create,
538 | // so it's safer to save to a temporary file location and rename afte done.
539 | tmpPath := filename + "." + strconv.Itoa(time.Now().Nanosecond()) + ".tmp"
540 | defer os.Remove(tmpPath)
541 |
542 | fw, err := os.Create(tmpPath)
543 | if err != nil {
544 | return err
545 | }
546 |
547 | if _, err = f.WriteToIndent(fw, indent); err != nil {
548 | fw.Close()
549 | return err
550 | }
551 | fw.Close()
552 |
553 | // Remove old file and rename the new one.
554 | os.Remove(filename)
555 | return os.Rename(tmpPath, filename)
556 | }
557 |
558 | // SaveTo writes content to file system.
559 | func (f *File) SaveTo(filename string) error {
560 | return f.SaveToIndent(filename, "")
561 | }
562 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/README_ZH.md:
--------------------------------------------------------------------------------
1 | 本包提供了 Go 语言中读写 INI 文件的功能。
2 |
3 | ## 功能特性
4 |
5 | - 支持覆盖加载多个数据源(`[]byte`、文件和 `io.ReadCloser`)
6 | - 支持递归读取键值
7 | - 支持读取父子分区
8 | - 支持读取自增键名
9 | - 支持读取多行的键值
10 | - 支持大量辅助方法
11 | - 支持在读取时直接转换为 Go 语言类型
12 | - 支持读取和 **写入** 分区和键的注释
13 | - 轻松操作分区、键值和注释
14 | - 在保存文件时分区和键值会保持原有的顺序
15 |
16 | ## 下载安装
17 |
18 | 使用一个特定版本:
19 |
20 | go get gopkg.in/ini.v1
21 |
22 | 使用最新版:
23 |
24 | go get github.com/go-ini/ini
25 |
26 | 如需更新请添加 `-u` 选项。
27 |
28 | ### 测试安装
29 |
30 | 如果您想要在自己的机器上运行测试,请使用 `-t` 标记:
31 |
32 | go get -t gopkg.in/ini.v1
33 |
34 | 如需更新请添加 `-u` 选项。
35 |
36 | ## 开始使用
37 |
38 | ### 从数据源加载
39 |
40 | 一个 **数据源** 可以是 `[]byte` 类型的原始数据,`string` 类型的文件路径或 `io.ReadCloser`。您可以加载 **任意多个** 数据源。如果您传递其它类型的数据源,则会直接返回错误。
41 |
42 | ```go
43 | cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
44 | ```
45 |
46 | 或者从一个空白的文件开始:
47 |
48 | ```go
49 | cfg := ini.Empty()
50 | ```
51 |
52 | 当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
53 |
54 | ```go
55 | err := cfg.Append("other file", []byte("other raw data"))
56 | ```
57 |
58 | 当您想要加载一系列文件,但是不能够确定其中哪些文件是不存在的,可以通过调用函数 `LooseLoad` 来忽略它们(`Load` 会因为文件不存在而返回错误):
59 |
60 | ```go
61 | cfg, err := ini.LooseLoad("filename", "filename_404")
62 | ```
63 |
64 | 更牛逼的是,当那些之前不存在的文件在重新调用 `Reload` 方法的时候突然出现了,那么它们会被正常加载。
65 |
66 | #### 忽略键名的大小写
67 |
68 | 有时候分区和键的名称大小写混合非常烦人,这个时候就可以通过 `InsensitiveLoad` 将所有分区和键名在读取里强制转换为小写:
69 |
70 | ```go
71 | cfg, err := ini.InsensitiveLoad("filename")
72 | //...
73 |
74 | // sec1 和 sec2 指向同一个分区对象
75 | sec1, err := cfg.GetSection("Section")
76 | sec2, err := cfg.GetSection("SecTIOn")
77 |
78 | // key1 和 key2 指向同一个键对象
79 | key1, err := sec1.GetKey("Key")
80 | key2, err := sec2.GetKey("KeY")
81 | ```
82 |
83 | #### 类似 MySQL 配置中的布尔值键
84 |
85 | MySQL 的配置文件中会出现没有具体值的布尔类型的键:
86 |
87 | ```ini
88 | [mysqld]
89 | ...
90 | skip-host-cache
91 | skip-name-resolve
92 | ```
93 |
94 | 默认情况下这被认为是缺失值而无法完成解析,但可以通过高级的加载选项对它们进行处理:
95 |
96 | ```go
97 | cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
98 | ```
99 |
100 | 这些键的值永远为 `true`,且在保存到文件时也只会输出键名。
101 |
102 | 如果您想要通过程序来生成此类键,则可以使用 `NewBooleanKey`:
103 |
104 | ```go
105 | key, err := sec.NewBooleanKey("skip-host-cache")
106 | ```
107 |
108 | #### 关于注释
109 |
110 | 下述几种情况的内容将被视为注释:
111 |
112 | 1. 所有以 `#` 或 `;` 开头的行
113 | 2. 所有在 `#` 或 `;` 之后的内容
114 | 3. 分区标签后的文字 (即 `[分区名]` 之后的内容)
115 |
116 | 如果你希望使用包含 `#` 或 `;` 的值,请使用 ``` ` ``` 或 ``` """ ``` 进行包覆。
117 |
118 | 除此之外,您还可以通过 `LoadOptions` 完全忽略行内注释:
119 |
120 | ```go
121 | cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
122 | ```
123 |
124 | ### 操作分区(Section)
125 |
126 | 获取指定分区:
127 |
128 | ```go
129 | section, err := cfg.GetSection("section name")
130 | ```
131 |
132 | 如果您想要获取默认分区,则可以用空字符串代替分区名:
133 |
134 | ```go
135 | section, err := cfg.GetSection("")
136 | ```
137 |
138 | 当您非常确定某个分区是存在的,可以使用以下简便方法:
139 |
140 | ```go
141 | section := cfg.Section("section name")
142 | ```
143 |
144 | 如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您。
145 |
146 | 创建一个分区:
147 |
148 | ```go
149 | err := cfg.NewSection("new section")
150 | ```
151 |
152 | 获取所有分区对象或名称:
153 |
154 | ```go
155 | sections := cfg.Sections()
156 | names := cfg.SectionStrings()
157 | ```
158 |
159 | ### 操作键(Key)
160 |
161 | 获取某个分区下的键:
162 |
163 | ```go
164 | key, err := cfg.Section("").GetKey("key name")
165 | ```
166 |
167 | 和分区一样,您也可以直接获取键而忽略错误处理:
168 |
169 | ```go
170 | key := cfg.Section("").Key("key name")
171 | ```
172 |
173 | 判断某个键是否存在:
174 |
175 | ```go
176 | yes := cfg.Section("").HasKey("key name")
177 | ```
178 |
179 | 创建一个新的键:
180 |
181 | ```go
182 | err := cfg.Section("").NewKey("name", "value")
183 | ```
184 |
185 | 获取分区下的所有键或键名:
186 |
187 | ```go
188 | keys := cfg.Section("").Keys()
189 | names := cfg.Section("").KeyStrings()
190 | ```
191 |
192 | 获取分区下的所有键值对的克隆:
193 |
194 | ```go
195 | hash := cfg.Section("").KeysHash()
196 | ```
197 |
198 | ### 操作键值(Value)
199 |
200 | 获取一个类型为字符串(string)的值:
201 |
202 | ```go
203 | val := cfg.Section("").Key("key name").String()
204 | ```
205 |
206 | 获取值的同时通过自定义函数进行处理验证:
207 |
208 | ```go
209 | val := cfg.Section("").Key("key name").Validate(func(in string) string {
210 | if len(in) == 0 {
211 | return "default"
212 | }
213 | return in
214 | })
215 | ```
216 |
217 | 如果您不需要任何对值的自动转变功能(例如递归读取),可以直接获取原值(这种方式性能最佳):
218 |
219 | ```go
220 | val := cfg.Section("").Key("key name").Value()
221 | ```
222 |
223 | 判断某个原值是否存在:
224 |
225 | ```go
226 | yes := cfg.Section("").HasValue("test value")
227 | ```
228 |
229 | 获取其它类型的值:
230 |
231 | ```go
232 | // 布尔值的规则:
233 | // true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
234 | // false 当值为:0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
235 | v, err = cfg.Section("").Key("BOOL").Bool()
236 | v, err = cfg.Section("").Key("FLOAT64").Float64()
237 | v, err = cfg.Section("").Key("INT").Int()
238 | v, err = cfg.Section("").Key("INT64").Int64()
239 | v, err = cfg.Section("").Key("UINT").Uint()
240 | v, err = cfg.Section("").Key("UINT64").Uint64()
241 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
242 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339
243 |
244 | v = cfg.Section("").Key("BOOL").MustBool()
245 | v = cfg.Section("").Key("FLOAT64").MustFloat64()
246 | v = cfg.Section("").Key("INT").MustInt()
247 | v = cfg.Section("").Key("INT64").MustInt64()
248 | v = cfg.Section("").Key("UINT").MustUint()
249 | v = cfg.Section("").Key("UINT64").MustUint64()
250 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
251 | v = cfg.Section("").Key("TIME").MustTime() // RFC3339
252 |
253 | // 由 Must 开头的方法名允许接收一个相同类型的参数来作为默认值,
254 | // 当键不存在或者转换失败时,则会直接返回该默认值。
255 | // 但是,MustString 方法必须传递一个默认值。
256 |
257 | v = cfg.Seciont("").Key("String").MustString("default")
258 | v = cfg.Section("").Key("BOOL").MustBool(true)
259 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
260 | v = cfg.Section("").Key("INT").MustInt(10)
261 | v = cfg.Section("").Key("INT64").MustInt64(99)
262 | v = cfg.Section("").Key("UINT").MustUint(3)
263 | v = cfg.Section("").Key("UINT64").MustUint64(6)
264 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
265 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
266 | ```
267 |
268 | 如果我的值有好多行怎么办?
269 |
270 | ```ini
271 | [advance]
272 | ADDRESS = """404 road,
273 | NotFound, State, 5000
274 | Earth"""
275 | ```
276 |
277 | 嗯哼?小 case!
278 |
279 | ```go
280 | cfg.Section("advance").Key("ADDRESS").String()
281 |
282 | /* --- start ---
283 | 404 road,
284 | NotFound, State, 5000
285 | Earth
286 | ------ end --- */
287 | ```
288 |
289 | 赞爆了!那要是我属于一行的内容写不下想要写到第二行怎么办?
290 |
291 | ```ini
292 | [advance]
293 | two_lines = how about \
294 | continuation lines?
295 | lots_of_lines = 1 \
296 | 2 \
297 | 3 \
298 | 4
299 | ```
300 |
301 | 简直是小菜一碟!
302 |
303 | ```go
304 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
305 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
306 | ```
307 |
308 | 可是我有时候觉得两行连在一起特别没劲,怎么才能不自动连接两行呢?
309 |
310 | ```go
311 | cfg, err := ini.LoadSources(ini.LoadOptions{
312 | IgnoreContinuation: true,
313 | }, "filename")
314 | ```
315 |
316 | 哇靠给力啊!
317 |
318 | 需要注意的是,值两侧的单引号会被自动剔除:
319 |
320 | ```ini
321 | foo = "some value" // foo: some value
322 | bar = 'some value' // bar: some value
323 | ```
324 |
325 | 这就是全部了?哈哈,当然不是。
326 |
327 | #### 操作键值的辅助方法
328 |
329 | 获取键值时设定候选值:
330 |
331 | ```go
332 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
333 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
334 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
335 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
336 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
337 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
338 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
339 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
340 | ```
341 |
342 | 如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
343 |
344 | 验证获取的值是否在指定范围内:
345 |
346 | ```go
347 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
348 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
349 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
350 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
351 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
352 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
353 | vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
354 | ```
355 |
356 | ##### 自动分割键值到切片(slice)
357 |
358 | 当存在无效输入时,使用零值代替:
359 |
360 | ```go
361 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
362 | // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
363 | vals = cfg.Section("").Key("STRINGS").Strings(",")
364 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
365 | vals = cfg.Section("").Key("INTS").Ints(",")
366 | vals = cfg.Section("").Key("INT64S").Int64s(",")
367 | vals = cfg.Section("").Key("UINTS").Uints(",")
368 | vals = cfg.Section("").Key("UINT64S").Uint64s(",")
369 | vals = cfg.Section("").Key("TIMES").Times(",")
370 | ```
371 |
372 | 从结果切片中剔除无效输入:
373 |
374 | ```go
375 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
376 | // Input: how, 2.2, are, you -> [2.2]
377 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
378 | vals = cfg.Section("").Key("INTS").ValidInts(",")
379 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
380 | vals = cfg.Section("").Key("UINTS").ValidUints(",")
381 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
382 | vals = cfg.Section("").Key("TIMES").ValidTimes(",")
383 | ```
384 |
385 | 当存在无效输入时,直接返回错误:
386 |
387 | ```go
388 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
389 | // Input: how, 2.2, are, you -> error
390 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
391 | vals = cfg.Section("").Key("INTS").StrictInts(",")
392 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
393 | vals = cfg.Section("").Key("UINTS").StrictUints(",")
394 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
395 | vals = cfg.Section("").Key("TIMES").StrictTimes(",")
396 | ```
397 |
398 | ### 保存配置
399 |
400 | 终于到了这个时刻,是时候保存一下配置了。
401 |
402 | 比较原始的做法是输出配置到某个文件:
403 |
404 | ```go
405 | // ...
406 | err = cfg.SaveTo("my.ini")
407 | err = cfg.SaveToIndent("my.ini", "\t")
408 | ```
409 |
410 | 另一个比较高级的做法是写入到任何实现 `io.Writer` 接口的对象中:
411 |
412 | ```go
413 | // ...
414 | cfg.WriteTo(writer)
415 | cfg.WriteToIndent(writer, "\t")
416 | ```
417 |
418 | 默认情况下,空格将被用于对齐键值之间的等号以美化输出结果,以下代码可以禁用该功能:
419 |
420 | ```go
421 | ini.PrettyFormat = false
422 | ```
423 |
424 | ## 高级用法
425 |
426 | ### 递归读取键值
427 |
428 | 在获取所有键值的过程中,特殊语法 `%()s` 会被应用,其中 `` 可以是相同分区或者默认分区下的键名。字符串 `%()s` 会被相应的键值所替代,如果指定的键不存在,则会用空字符串替代。您可以最多使用 99 层的递归嵌套。
429 |
430 | ```ini
431 | NAME = ini
432 |
433 | [author]
434 | NAME = Unknwon
435 | GITHUB = https://github.com/%(NAME)s
436 |
437 | [package]
438 | FULL_NAME = github.com/go-ini/%(NAME)s
439 | ```
440 |
441 | ```go
442 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
443 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
444 | ```
445 |
446 | ### 读取父子分区
447 |
448 | 您可以在分区名称中使用 `.` 来表示两个或多个分区之间的父子关系。如果某个键在子分区中不存在,则会去它的父分区中再次寻找,直到没有父分区为止。
449 |
450 | ```ini
451 | NAME = ini
452 | VERSION = v1
453 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
454 |
455 | [package]
456 | CLONE_URL = https://%(IMPORT_PATH)s
457 |
458 | [package.sub]
459 | ```
460 |
461 | ```go
462 | cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
463 | ```
464 |
465 | #### 获取上级父分区下的所有键名
466 |
467 | ```go
468 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
469 | ```
470 |
471 | ### 无法解析的分区
472 |
473 | 如果遇到一些比较特殊的分区,它们不包含常见的键值对,而是没有固定格式的纯文本,则可以使用 `LoadOptions.UnparsableSections` 进行处理:
474 |
475 | ```go
476 | cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
477 | <1> This slide has the fuel listed in the wrong units `))
478 |
479 | body := cfg.Section("COMMENTS").Body()
480 |
481 | /* --- start ---
482 | <1> This slide has the fuel listed in the wrong units
483 | ------ end --- */
484 | ```
485 |
486 | ### 读取自增键名
487 |
488 | 如果数据源中的键名为 `-`,则认为该键使用了自增键名的特殊语法。计数器从 1 开始,并且分区之间是相互独立的。
489 |
490 | ```ini
491 | [features]
492 | -: Support read/write comments of keys and sections
493 | -: Support auto-increment of key names
494 | -: Support load multiple files to overwrite key values
495 | ```
496 |
497 | ```go
498 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
499 | ```
500 |
501 | ### 映射到结构
502 |
503 | 想要使用更加面向对象的方式玩转 INI 吗?好主意。
504 |
505 | ```ini
506 | Name = Unknwon
507 | age = 21
508 | Male = true
509 | Born = 1993-01-01T20:17:05Z
510 |
511 | [Note]
512 | Content = Hi is a good man!
513 | Cities = HangZhou, Boston
514 | ```
515 |
516 | ```go
517 | type Note struct {
518 | Content string
519 | Cities []string
520 | }
521 |
522 | type Person struct {
523 | Name string
524 | Age int `ini:"age"`
525 | Male bool
526 | Born time.Time
527 | Note
528 | Created time.Time `ini:"-"`
529 | }
530 |
531 | func main() {
532 | cfg, err := ini.Load("path/to/ini")
533 | // ...
534 | p := new(Person)
535 | err = cfg.MapTo(p)
536 | // ...
537 |
538 | // 一切竟可以如此的简单。
539 | err = ini.MapTo(p, "path/to/ini")
540 | // ...
541 |
542 | // 嗯哼?只需要映射一个分区吗?
543 | n := new(Note)
544 | err = cfg.Section("Note").MapTo(n)
545 | // ...
546 | }
547 | ```
548 |
549 | 结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
550 |
551 | ```go
552 | // ...
553 | p := &Person{
554 | Name: "Joe",
555 | }
556 | // ...
557 | ```
558 |
559 | 这样玩 INI 真的好酷啊!然而,如果不能还给我原来的配置文件,有什么卵用?
560 |
561 | ### 从结构反射
562 |
563 | 可是,我有说不能吗?
564 |
565 | ```go
566 | type Embeded struct {
567 | Dates []time.Time `delim:"|"`
568 | Places []string `ini:"places,omitempty"`
569 | None []int `ini:",omitempty"`
570 | }
571 |
572 | type Author struct {
573 | Name string `ini:"NAME"`
574 | Male bool
575 | Age int
576 | GPA float64
577 | NeverMind string `ini:"-"`
578 | *Embeded
579 | }
580 |
581 | func main() {
582 | a := &Author{"Unknwon", true, 21, 2.8, "",
583 | &Embeded{
584 | []time.Time{time.Now(), time.Now()},
585 | []string{"HangZhou", "Boston"},
586 | []int{},
587 | }}
588 | cfg := ini.Empty()
589 | err = ini.ReflectFrom(cfg, a)
590 | // ...
591 | }
592 | ```
593 |
594 | 瞧瞧,奇迹发生了。
595 |
596 | ```ini
597 | NAME = Unknwon
598 | Male = true
599 | Age = 21
600 | GPA = 2.8
601 |
602 | [Embeded]
603 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
604 | places = HangZhou,Boston
605 | ```
606 |
607 | #### 名称映射器(Name Mapper)
608 |
609 | 为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
610 |
611 | 目前有 2 款内置的映射器:
612 |
613 | - `AllCapsUnderscore`:该映射器将字段名转换至格式 `ALL_CAPS_UNDERSCORE` 后再去匹配分区名和键名。
614 | - `TitleUnderscore`:该映射器将字段名转换至格式 `title_underscore` 后再去匹配分区名和键名。
615 |
616 | 使用方法:
617 |
618 | ```go
619 | type Info struct{
620 | PackageName string
621 | }
622 |
623 | func main() {
624 | err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
625 | // ...
626 |
627 | cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
628 | // ...
629 | info := new(Info)
630 | cfg.NameMapper = ini.AllCapsUnderscore
631 | err = cfg.MapTo(info)
632 | // ...
633 | }
634 | ```
635 |
636 | 使用函数 `ini.ReflectFromWithMapper` 时也可应用相同的规则。
637 |
638 | #### 值映射器(Value Mapper)
639 |
640 | 值映射器允许使用一个自定义函数自动展开值的具体内容,例如:运行时获取环境变量:
641 |
642 | ```go
643 | type Env struct {
644 | Foo string `ini:"foo"`
645 | }
646 |
647 | func main() {
648 | cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
649 | cfg.ValueMapper = os.ExpandEnv
650 | // ...
651 | env := &Env{}
652 | err = cfg.Section("env").MapTo(env)
653 | }
654 | ```
655 |
656 | 本例中,`env.Foo` 将会是运行时所获取到环境变量 `MY_VAR` 的值。
657 |
658 | #### 映射/反射的其它说明
659 |
660 | 任何嵌入的结构都会被默认认作一个不同的分区,并且不会自动产生所谓的父子分区关联:
661 |
662 | ```go
663 | type Child struct {
664 | Age string
665 | }
666 |
667 | type Parent struct {
668 | Name string
669 | Child
670 | }
671 |
672 | type Config struct {
673 | City string
674 | Parent
675 | }
676 | ```
677 |
678 | 示例配置文件:
679 |
680 | ```ini
681 | City = Boston
682 |
683 | [Parent]
684 | Name = Unknwon
685 |
686 | [Child]
687 | Age = 21
688 | ```
689 |
690 | 很好,但是,我就是要嵌入结构也在同一个分区。好吧,你爹是李刚!
691 |
692 | ```go
693 | type Child struct {
694 | Age string
695 | }
696 |
697 | type Parent struct {
698 | Name string
699 | Child `ini:"Parent"`
700 | }
701 |
702 | type Config struct {
703 | City string
704 | Parent
705 | }
706 | ```
707 |
708 | 示例配置文件:
709 |
710 | ```ini
711 | City = Boston
712 |
713 | [Parent]
714 | Name = Unknwon
715 | Age = 21
716 | ```
717 |
718 | ## 获取帮助
719 |
720 | - [API 文档](https://gowalker.org/gopkg.in/ini.v1)
721 | - [创建工单](https://github.com/go-ini/ini/issues/new)
722 |
723 | ## 常见问题
724 |
725 | ### 字段 `BlockMode` 是什么?
726 |
727 | 默认情况下,本库会在您进行读写操作时采用锁机制来确保数据时间。但在某些情况下,您非常确定只进行读操作。此时,您可以通过设置 `cfg.BlockMode = false` 来将读操作提升大约 **50-70%** 的性能。
728 |
729 | ### 为什么要写另一个 INI 解析库?
730 |
731 | 许多人都在使用我的 [goconfig](https://github.com/Unknwon/goconfig) 来完成对 INI 文件的操作,但我希望使用更加 Go 风格的代码。并且当您设置 `cfg.BlockMode = false` 时,会有大约 **10-30%** 的性能提升。
732 |
733 | 为了做出这些改变,我必须对 API 进行破坏,所以新开一个仓库是最安全的做法。除此之外,本库直接使用 `gopkg.in` 来进行版本化发布。(其实真相是导入路径更短了)
734 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/README.md:
--------------------------------------------------------------------------------
1 | INI [](https://travis-ci.org/go-ini/ini) [](https://sourcegraph.com/github.com/go-ini/ini?badge)
2 | ===
3 |
4 | 
5 |
6 | Package ini provides INI file read and write functionality in Go.
7 |
8 | [简体中文](README_ZH.md)
9 |
10 | ## Feature
11 |
12 | - Load multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites.
13 | - Read with recursion values.
14 | - Read with parent-child sections.
15 | - Read with auto-increment key names.
16 | - Read with multiple-line values.
17 | - Read with tons of helper methods.
18 | - Read and convert values to Go types.
19 | - Read and **WRITE** comments of sections and keys.
20 | - Manipulate sections, keys and comments with ease.
21 | - Keep sections and keys in order as you parse and save.
22 |
23 | ## Installation
24 |
25 | To use a tagged revision:
26 |
27 | go get gopkg.in/ini.v1
28 |
29 | To use with latest changes:
30 |
31 | go get github.com/go-ini/ini
32 |
33 | Please add `-u` flag to update in the future.
34 |
35 | ### Testing
36 |
37 | If you want to test on your machine, please apply `-t` flag:
38 |
39 | go get -t gopkg.in/ini.v1
40 |
41 | Please add `-u` flag to update in the future.
42 |
43 | ## Getting Started
44 |
45 | ### Loading from data sources
46 |
47 | 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.
48 |
49 | ```go
50 | cfg, err := ini.Load([]byte("raw data"), "filename", ioutil.NopCloser(bytes.NewReader([]byte("some other data"))))
51 | ```
52 |
53 | Or start with an empty object:
54 |
55 | ```go
56 | cfg := ini.Empty()
57 | ```
58 |
59 | When you cannot decide how many data sources to load at the beginning, you will still be able to **Append()** them later.
60 |
61 | ```go
62 | err := cfg.Append("other file", []byte("other raw data"))
63 | ```
64 |
65 | 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.
66 |
67 | ```go
68 | cfg, err := ini.LooseLoad("filename", "filename_404")
69 | ```
70 |
71 | The cool thing is, whenever the file is available to load while you're calling `Reload` method, it will be counted as usual.
72 |
73 | #### Ignore cases of key name
74 |
75 | 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.
76 |
77 | ```go
78 | cfg, err := ini.InsensitiveLoad("filename")
79 | //...
80 |
81 | // sec1 and sec2 are the exactly same section object
82 | sec1, err := cfg.GetSection("Section")
83 | sec2, err := cfg.GetSection("SecTIOn")
84 |
85 | // key1 and key2 are the exactly same key object
86 | key1, err := sec1.GetKey("Key")
87 | key2, err := sec2.GetKey("KeY")
88 | ```
89 |
90 | #### MySQL-like boolean key
91 |
92 | MySQL's configuration allows a key without value as follows:
93 |
94 | ```ini
95 | [mysqld]
96 | ...
97 | skip-host-cache
98 | skip-name-resolve
99 | ```
100 |
101 | 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:
102 |
103 | ```go
104 | cfg, err := LoadSources(LoadOptions{AllowBooleanKeys: true}, "my.cnf"))
105 | ```
106 |
107 | 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.
108 |
109 | To generate such keys in your program, you could use `NewBooleanKey`:
110 |
111 | ```go
112 | key, err := sec.NewBooleanKey("skip-host-cache")
113 | ```
114 |
115 | #### Comment
116 |
117 | Take care that following format will be treated as comment:
118 |
119 | 1. Line begins with `#` or `;`
120 | 2. Words after `#` or `;`
121 | 3. Words after section name (i.e words after `[some section name]`)
122 |
123 | If you want to save a value with `#` or `;`, please quote them with ``` ` ``` or ``` """ ```.
124 |
125 | Alternatively, you can use following `LoadOptions` to completely ignore inline comments:
126 |
127 | ```go
128 | cfg, err := LoadSources(LoadOptions{IgnoreInlineComment: true}, "app.ini"))
129 | ```
130 |
131 | ### Working with sections
132 |
133 | To get a section, you would need to:
134 |
135 | ```go
136 | section, err := cfg.GetSection("section name")
137 | ```
138 |
139 | For a shortcut for default section, just give an empty string as name:
140 |
141 | ```go
142 | section, err := cfg.GetSection("")
143 | ```
144 |
145 | When you're pretty sure the section exists, following code could make your life easier:
146 |
147 | ```go
148 | section := cfg.Section("section name")
149 | ```
150 |
151 | What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
152 |
153 | To create a new section:
154 |
155 | ```go
156 | err := cfg.NewSection("new section")
157 | ```
158 |
159 | To get a list of sections or section names:
160 |
161 | ```go
162 | sections := cfg.Sections()
163 | names := cfg.SectionStrings()
164 | ```
165 |
166 | ### Working with keys
167 |
168 | To get a key under a section:
169 |
170 | ```go
171 | key, err := cfg.Section("").GetKey("key name")
172 | ```
173 |
174 | Same rule applies to key operations:
175 |
176 | ```go
177 | key := cfg.Section("").Key("key name")
178 | ```
179 |
180 | To check if a key exists:
181 |
182 | ```go
183 | yes := cfg.Section("").HasKey("key name")
184 | ```
185 |
186 | To create a new key:
187 |
188 | ```go
189 | err := cfg.Section("").NewKey("name", "value")
190 | ```
191 |
192 | To get a list of keys or key names:
193 |
194 | ```go
195 | keys := cfg.Section("").Keys()
196 | names := cfg.Section("").KeyStrings()
197 | ```
198 |
199 | To get a clone hash of keys and corresponding values:
200 |
201 | ```go
202 | hash := cfg.Section("").KeysHash()
203 | ```
204 |
205 | ### Working with values
206 |
207 | To get a string value:
208 |
209 | ```go
210 | val := cfg.Section("").Key("key name").String()
211 | ```
212 |
213 | To validate key value on the fly:
214 |
215 | ```go
216 | val := cfg.Section("").Key("key name").Validate(func(in string) string {
217 | if len(in) == 0 {
218 | return "default"
219 | }
220 | return in
221 | })
222 | ```
223 |
224 | 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):
225 |
226 | ```go
227 | val := cfg.Section("").Key("key name").Value()
228 | ```
229 |
230 | To check if raw value exists:
231 |
232 | ```go
233 | yes := cfg.Section("").HasValue("test value")
234 | ```
235 |
236 | To get value with types:
237 |
238 | ```go
239 | // For boolean values:
240 | // true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On
241 | // false when value is: 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off
242 | v, err = cfg.Section("").Key("BOOL").Bool()
243 | v, err = cfg.Section("").Key("FLOAT64").Float64()
244 | v, err = cfg.Section("").Key("INT").Int()
245 | v, err = cfg.Section("").Key("INT64").Int64()
246 | v, err = cfg.Section("").Key("UINT").Uint()
247 | v, err = cfg.Section("").Key("UINT64").Uint64()
248 | v, err = cfg.Section("").Key("TIME").TimeFormat(time.RFC3339)
249 | v, err = cfg.Section("").Key("TIME").Time() // RFC3339
250 |
251 | v = cfg.Section("").Key("BOOL").MustBool()
252 | v = cfg.Section("").Key("FLOAT64").MustFloat64()
253 | v = cfg.Section("").Key("INT").MustInt()
254 | v = cfg.Section("").Key("INT64").MustInt64()
255 | v = cfg.Section("").Key("UINT").MustUint()
256 | v = cfg.Section("").Key("UINT64").MustUint64()
257 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339)
258 | v = cfg.Section("").Key("TIME").MustTime() // RFC3339
259 |
260 | // Methods start with Must also accept one argument for default value
261 | // when key not found or fail to parse value to given type.
262 | // Except method MustString, which you have to pass a default value.
263 |
264 | v = cfg.Section("").Key("String").MustString("default")
265 | v = cfg.Section("").Key("BOOL").MustBool(true)
266 | v = cfg.Section("").Key("FLOAT64").MustFloat64(1.25)
267 | v = cfg.Section("").Key("INT").MustInt(10)
268 | v = cfg.Section("").Key("INT64").MustInt64(99)
269 | v = cfg.Section("").Key("UINT").MustUint(3)
270 | v = cfg.Section("").Key("UINT64").MustUint64(6)
271 | v = cfg.Section("").Key("TIME").MustTimeFormat(time.RFC3339, time.Now())
272 | v = cfg.Section("").Key("TIME").MustTime(time.Now()) // RFC3339
273 | ```
274 |
275 | What if my value is three-line long?
276 |
277 | ```ini
278 | [advance]
279 | ADDRESS = """404 road,
280 | NotFound, State, 5000
281 | Earth"""
282 | ```
283 |
284 | Not a problem!
285 |
286 | ```go
287 | cfg.Section("advance").Key("ADDRESS").String()
288 |
289 | /* --- start ---
290 | 404 road,
291 | NotFound, State, 5000
292 | Earth
293 | ------ end --- */
294 | ```
295 |
296 | That's cool, how about continuation lines?
297 |
298 | ```ini
299 | [advance]
300 | two_lines = how about \
301 | continuation lines?
302 | lots_of_lines = 1 \
303 | 2 \
304 | 3 \
305 | 4
306 | ```
307 |
308 | Piece of cake!
309 |
310 | ```go
311 | cfg.Section("advance").Key("two_lines").String() // how about continuation lines?
312 | cfg.Section("advance").Key("lots_of_lines").String() // 1 2 3 4
313 | ```
314 |
315 | Well, I hate continuation lines, how do I disable that?
316 |
317 | ```go
318 | cfg, err := ini.LoadSources(ini.LoadOptions{
319 | IgnoreContinuation: true,
320 | }, "filename")
321 | ```
322 |
323 | Holy crap!
324 |
325 | Note that single quotes around values will be stripped:
326 |
327 | ```ini
328 | foo = "some value" // foo: some value
329 | bar = 'some value' // bar: some value
330 | ```
331 |
332 | That's all? Hmm, no.
333 |
334 | #### Helper methods of working with values
335 |
336 | To get value with given candidates:
337 |
338 | ```go
339 | v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
340 | v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
341 | v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
342 | v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
343 | v = cfg.Section("").Key("UINT").InUint(4, []int{3, 6, 9})
344 | v = cfg.Section("").Key("UINT64").InUint64(8, []int64{3, 6, 9})
345 | v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
346 | v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
347 | ```
348 |
349 | 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.
350 |
351 | To validate value in a given range:
352 |
353 | ```go
354 | vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
355 | vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
356 | vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
357 | vals = cfg.Section("").Key("UINT").RangeUint(0, 3, 9)
358 | vals = cfg.Section("").Key("UINT64").RangeUint64(0, 3, 9)
359 | vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
360 | vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
361 | ```
362 |
363 | ##### Auto-split values into a slice
364 |
365 | To use zero value of type for invalid inputs:
366 |
367 | ```go
368 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
369 | // Input: how, 2.2, are, you -> [0.0 2.2 0.0 0.0]
370 | vals = cfg.Section("").Key("STRINGS").Strings(",")
371 | vals = cfg.Section("").Key("FLOAT64S").Float64s(",")
372 | vals = cfg.Section("").Key("INTS").Ints(",")
373 | vals = cfg.Section("").Key("INT64S").Int64s(",")
374 | vals = cfg.Section("").Key("UINTS").Uints(",")
375 | vals = cfg.Section("").Key("UINT64S").Uint64s(",")
376 | vals = cfg.Section("").Key("TIMES").Times(",")
377 | ```
378 |
379 | To exclude invalid values out of result slice:
380 |
381 | ```go
382 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
383 | // Input: how, 2.2, are, you -> [2.2]
384 | vals = cfg.Section("").Key("FLOAT64S").ValidFloat64s(",")
385 | vals = cfg.Section("").Key("INTS").ValidInts(",")
386 | vals = cfg.Section("").Key("INT64S").ValidInt64s(",")
387 | vals = cfg.Section("").Key("UINTS").ValidUints(",")
388 | vals = cfg.Section("").Key("UINT64S").ValidUint64s(",")
389 | vals = cfg.Section("").Key("TIMES").ValidTimes(",")
390 | ```
391 |
392 | Or to return nothing but error when have invalid inputs:
393 |
394 | ```go
395 | // Input: 1.1, 2.2, 3.3, 4.4 -> [1.1 2.2 3.3 4.4]
396 | // Input: how, 2.2, are, you -> error
397 | vals = cfg.Section("").Key("FLOAT64S").StrictFloat64s(",")
398 | vals = cfg.Section("").Key("INTS").StrictInts(",")
399 | vals = cfg.Section("").Key("INT64S").StrictInt64s(",")
400 | vals = cfg.Section("").Key("UINTS").StrictUints(",")
401 | vals = cfg.Section("").Key("UINT64S").StrictUint64s(",")
402 | vals = cfg.Section("").Key("TIMES").StrictTimes(",")
403 | ```
404 |
405 | ### Save your configuration
406 |
407 | Finally, it's time to save your configuration to somewhere.
408 |
409 | A typical way to save configuration is writing it to a file:
410 |
411 | ```go
412 | // ...
413 | err = cfg.SaveTo("my.ini")
414 | err = cfg.SaveToIndent("my.ini", "\t")
415 | ```
416 |
417 | Another way to save is writing to a `io.Writer` interface:
418 |
419 | ```go
420 | // ...
421 | cfg.WriteTo(writer)
422 | cfg.WriteToIndent(writer, "\t")
423 | ```
424 |
425 | By default, spaces are used to align "=" sign between key and values, to disable that:
426 |
427 | ```go
428 | ini.PrettyFormat = false
429 | ```
430 |
431 | ## Advanced Usage
432 |
433 | ### Recursive Values
434 |
435 | 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.
436 |
437 | ```ini
438 | NAME = ini
439 |
440 | [author]
441 | NAME = Unknwon
442 | GITHUB = https://github.com/%(NAME)s
443 |
444 | [package]
445 | FULL_NAME = github.com/go-ini/%(NAME)s
446 | ```
447 |
448 | ```go
449 | cfg.Section("author").Key("GITHUB").String() // https://github.com/Unknwon
450 | cfg.Section("package").Key("FULL_NAME").String() // github.com/go-ini/ini
451 | ```
452 |
453 | ### Parent-child Sections
454 |
455 | 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.
456 |
457 | ```ini
458 | NAME = ini
459 | VERSION = v1
460 | IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
461 |
462 | [package]
463 | CLONE_URL = https://%(IMPORT_PATH)s
464 |
465 | [package.sub]
466 | ```
467 |
468 | ```go
469 | cfg.Section("package.sub").Key("CLONE_URL").String() // https://gopkg.in/ini.v1
470 | ```
471 |
472 | #### Retrieve parent keys available to a child section
473 |
474 | ```go
475 | cfg.Section("package.sub").ParentKeys() // ["CLONE_URL"]
476 | ```
477 |
478 | ### Unparseable Sections
479 |
480 | Sometimes, you have sections that do not contain key-value pairs but raw content, to handle such case, you can use `LoadOptions.UnparsableSections`:
481 |
482 | ```go
483 | cfg, err := LoadSources(LoadOptions{UnparseableSections: []string{"COMMENTS"}}, `[COMMENTS]
484 | <1> This slide has the fuel listed in the wrong units `))
485 |
486 | body := cfg.Section("COMMENTS").Body()
487 |
488 | /* --- start ---
489 | <1> This slide has the fuel listed in the wrong units
490 | ------ end --- */
491 | ```
492 |
493 | ### Auto-increment Key Names
494 |
495 | 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.
496 |
497 | ```ini
498 | [features]
499 | -: Support read/write comments of keys and sections
500 | -: Support auto-increment of key names
501 | -: Support load multiple files to overwrite key values
502 | ```
503 |
504 | ```go
505 | cfg.Section("features").KeyStrings() // []{"#1", "#2", "#3"}
506 | ```
507 |
508 | ### Map To Struct
509 |
510 | Want more objective way to play with INI? Cool.
511 |
512 | ```ini
513 | Name = Unknwon
514 | age = 21
515 | Male = true
516 | Born = 1993-01-01T20:17:05Z
517 |
518 | [Note]
519 | Content = Hi is a good man!
520 | Cities = HangZhou, Boston
521 | ```
522 |
523 | ```go
524 | type Note struct {
525 | Content string
526 | Cities []string
527 | }
528 |
529 | type Person struct {
530 | Name string
531 | Age int `ini:"age"`
532 | Male bool
533 | Born time.Time
534 | Note
535 | Created time.Time `ini:"-"`
536 | }
537 |
538 | func main() {
539 | cfg, err := ini.Load("path/to/ini")
540 | // ...
541 | p := new(Person)
542 | err = cfg.MapTo(p)
543 | // ...
544 |
545 | // Things can be simpler.
546 | err = ini.MapTo(p, "path/to/ini")
547 | // ...
548 |
549 | // Just map a section? Fine.
550 | n := new(Note)
551 | err = cfg.Section("Note").MapTo(n)
552 | // ...
553 | }
554 | ```
555 |
556 | Can I have default value for field? Absolutely.
557 |
558 | 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.
559 |
560 | ```go
561 | // ...
562 | p := &Person{
563 | Name: "Joe",
564 | }
565 | // ...
566 | ```
567 |
568 | It's really cool, but what's the point if you can't give me my file back from struct?
569 |
570 | ### Reflect From Struct
571 |
572 | Why not?
573 |
574 | ```go
575 | type Embeded struct {
576 | Dates []time.Time `delim:"|"`
577 | Places []string `ini:"places,omitempty"`
578 | None []int `ini:",omitempty"`
579 | }
580 |
581 | type Author struct {
582 | Name string `ini:"NAME"`
583 | Male bool
584 | Age int
585 | GPA float64
586 | NeverMind string `ini:"-"`
587 | *Embeded
588 | }
589 |
590 | func main() {
591 | a := &Author{"Unknwon", true, 21, 2.8, "",
592 | &Embeded{
593 | []time.Time{time.Now(), time.Now()},
594 | []string{"HangZhou", "Boston"},
595 | []int{},
596 | }}
597 | cfg := ini.Empty()
598 | err = ini.ReflectFrom(cfg, a)
599 | // ...
600 | }
601 | ```
602 |
603 | So, what do I get?
604 |
605 | ```ini
606 | NAME = Unknwon
607 | Male = true
608 | Age = 21
609 | GPA = 2.8
610 |
611 | [Embeded]
612 | Dates = 2015-08-07T22:14:22+08:00|2015-08-07T22:14:22+08:00
613 | places = HangZhou,Boston
614 | ```
615 |
616 | #### Name Mapper
617 |
618 | 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.
619 |
620 | There are 2 built-in name mappers:
621 |
622 | - `AllCapsUnderscore`: it converts to format `ALL_CAPS_UNDERSCORE` then match section or key.
623 | - `TitleUnderscore`: it converts to format `title_underscore` then match section or key.
624 |
625 | To use them:
626 |
627 | ```go
628 | type Info struct {
629 | PackageName string
630 | }
631 |
632 | func main() {
633 | err = ini.MapToWithMapper(&Info{}, ini.TitleUnderscore, []byte("package_name=ini"))
634 | // ...
635 |
636 | cfg, err := ini.Load([]byte("PACKAGE_NAME=ini"))
637 | // ...
638 | info := new(Info)
639 | cfg.NameMapper = ini.AllCapsUnderscore
640 | err = cfg.MapTo(info)
641 | // ...
642 | }
643 | ```
644 |
645 | Same rules of name mapper apply to `ini.ReflectFromWithMapper` function.
646 |
647 | #### Value Mapper
648 |
649 | To expand values (e.g. from environment variables), you can use the `ValueMapper` to transform values:
650 |
651 | ```go
652 | type Env struct {
653 | Foo string `ini:"foo"`
654 | }
655 |
656 | func main() {
657 | cfg, err := ini.Load([]byte("[env]\nfoo = ${MY_VAR}\n")
658 | cfg.ValueMapper = os.ExpandEnv
659 | // ...
660 | env := &Env{}
661 | err = cfg.Section("env").MapTo(env)
662 | }
663 | ```
664 |
665 | This would set the value of `env.Foo` to the value of the environment variable `MY_VAR`.
666 |
667 | #### Other Notes On Map/Reflect
668 |
669 | Any embedded struct is treated as a section by default, and there is no automatic parent-child relations in map/reflect feature:
670 |
671 | ```go
672 | type Child struct {
673 | Age string
674 | }
675 |
676 | type Parent struct {
677 | Name string
678 | Child
679 | }
680 |
681 | type Config struct {
682 | City string
683 | Parent
684 | }
685 | ```
686 |
687 | Example configuration:
688 |
689 | ```ini
690 | City = Boston
691 |
692 | [Parent]
693 | Name = Unknwon
694 |
695 | [Child]
696 | Age = 21
697 | ```
698 |
699 | What if, yes, I'm paranoid, I want embedded struct to be in the same section. Well, all roads lead to Rome.
700 |
701 | ```go
702 | type Child struct {
703 | Age string
704 | }
705 |
706 | type Parent struct {
707 | Name string
708 | Child `ini:"Parent"`
709 | }
710 |
711 | type Config struct {
712 | City string
713 | Parent
714 | }
715 | ```
716 |
717 | Example configuration:
718 |
719 | ```ini
720 | City = Boston
721 |
722 | [Parent]
723 | Name = Unknwon
724 | Age = 21
725 | ```
726 |
727 | ## Getting Help
728 |
729 | - [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
730 | - [File An Issue](https://github.com/go-ini/ini/issues/new)
731 |
732 | ## FAQs
733 |
734 | ### What does `BlockMode` field do?
735 |
736 | 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.
737 |
738 | ### Why another INI library?
739 |
740 | 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.
741 |
742 | 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)
743 |
744 | ## License
745 |
746 | This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
747 |
--------------------------------------------------------------------------------
/vendor/gopkg.in/ini.v1/key.go:
--------------------------------------------------------------------------------
1 | // Copyright 2014 Unknwon
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may
4 | // not use this file except in compliance with the License. You may obtain
5 | // a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 | // License for the specific language governing permissions and limitations
13 | // under the License.
14 |
15 | package ini
16 |
17 | import (
18 | "errors"
19 | "fmt"
20 | "strconv"
21 | "strings"
22 | "time"
23 | )
24 |
25 | // Key represents a key under a section.
26 | type Key struct {
27 | s *Section
28 | name string
29 | value string
30 | isAutoIncrement bool
31 | isBooleanType bool
32 |
33 | isShadow bool
34 | shadows []*Key
35 |
36 | Comment string
37 | }
38 |
39 | // newKey simply return a key object with given values.
40 | func newKey(s *Section, name, val string) *Key {
41 | return &Key{
42 | s: s,
43 | name: name,
44 | value: val,
45 | }
46 | }
47 |
48 | func (k *Key) addShadow(val string) error {
49 | if k.isShadow {
50 | return errors.New("cannot add shadow to another shadow key")
51 | } else if k.isAutoIncrement || k.isBooleanType {
52 | return errors.New("cannot add shadow to auto-increment or boolean key")
53 | }
54 |
55 | shadow := newKey(k.s, k.name, val)
56 | shadow.isShadow = true
57 | k.shadows = append(k.shadows, shadow)
58 | return nil
59 | }
60 |
61 | // AddShadow adds a new shadow key to itself.
62 | func (k *Key) AddShadow(val string) error {
63 | if !k.s.f.options.AllowShadows {
64 | return errors.New("shadow key is not allowed")
65 | }
66 | return k.addShadow(val)
67 | }
68 |
69 | // ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
70 | type ValueMapper func(string) string
71 |
72 | // Name returns name of key.
73 | func (k *Key) Name() string {
74 | return k.name
75 | }
76 |
77 | // Value returns raw value of key for performance purpose.
78 | func (k *Key) Value() string {
79 | return k.value
80 | }
81 |
82 | // ValueWithShadows returns raw values of key and its shadows if any.
83 | func (k *Key) ValueWithShadows() []string {
84 | if len(k.shadows) == 0 {
85 | return []string{k.value}
86 | }
87 | vals := make([]string, len(k.shadows)+1)
88 | vals[0] = k.value
89 | for i := range k.shadows {
90 | vals[i+1] = k.shadows[i].value
91 | }
92 | return vals
93 | }
94 |
95 | // transformValue takes a raw value and transforms to its final string.
96 | func (k *Key) transformValue(val string) string {
97 | if k.s.f.ValueMapper != nil {
98 | val = k.s.f.ValueMapper(val)
99 | }
100 |
101 | // Fail-fast if no indicate char found for recursive value
102 | if !strings.Contains(val, "%") {
103 | return val
104 | }
105 | for i := 0; i < _DEPTH_VALUES; i++ {
106 | vr := varPattern.FindString(val)
107 | if len(vr) == 0 {
108 | break
109 | }
110 |
111 | // Take off leading '%(' and trailing ')s'.
112 | noption := strings.TrimLeft(vr, "%(")
113 | noption = strings.TrimRight(noption, ")s")
114 |
115 | // Search in the same section.
116 | nk, err := k.s.GetKey(noption)
117 | if err != nil {
118 | // Search again in default section.
119 | nk, _ = k.s.f.Section("").GetKey(noption)
120 | }
121 |
122 | // Substitute by new value and take off leading '%(' and trailing ')s'.
123 | val = strings.Replace(val, vr, nk.value, -1)
124 | }
125 | return val
126 | }
127 |
128 | // String returns string representation of value.
129 | func (k *Key) String() string {
130 | return k.transformValue(k.value)
131 | }
132 |
133 | // Validate accepts a validate function which can
134 | // return modifed result as key value.
135 | func (k *Key) Validate(fn func(string) string) string {
136 | return fn(k.String())
137 | }
138 |
139 | // parseBool returns the boolean value represented by the string.
140 | //
141 | // It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
142 | // 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
143 | // Any other value returns an error.
144 | func parseBool(str string) (value bool, err error) {
145 | switch str {
146 | case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
147 | return true, nil
148 | case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
149 | return false, nil
150 | }
151 | return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
152 | }
153 |
154 | // Bool returns bool type value.
155 | func (k *Key) Bool() (bool, error) {
156 | return parseBool(k.String())
157 | }
158 |
159 | // Float64 returns float64 type value.
160 | func (k *Key) Float64() (float64, error) {
161 | return strconv.ParseFloat(k.String(), 64)
162 | }
163 |
164 | // Int returns int type value.
165 | func (k *Key) Int() (int, error) {
166 | return strconv.Atoi(k.String())
167 | }
168 |
169 | // Int64 returns int64 type value.
170 | func (k *Key) Int64() (int64, error) {
171 | return strconv.ParseInt(k.String(), 10, 64)
172 | }
173 |
174 | // Uint returns uint type valued.
175 | func (k *Key) Uint() (uint, error) {
176 | u, e := strconv.ParseUint(k.String(), 10, 64)
177 | return uint(u), e
178 | }
179 |
180 | // Uint64 returns uint64 type value.
181 | func (k *Key) Uint64() (uint64, error) {
182 | return strconv.ParseUint(k.String(), 10, 64)
183 | }
184 |
185 | // Duration returns time.Duration type value.
186 | func (k *Key) Duration() (time.Duration, error) {
187 | return time.ParseDuration(k.String())
188 | }
189 |
190 | // TimeFormat parses with given format and returns time.Time type value.
191 | func (k *Key) TimeFormat(format string) (time.Time, error) {
192 | return time.Parse(format, k.String())
193 | }
194 |
195 | // Time parses with RFC3339 format and returns time.Time type value.
196 | func (k *Key) Time() (time.Time, error) {
197 | return k.TimeFormat(time.RFC3339)
198 | }
199 |
200 | // MustString returns default value if key value is empty.
201 | func (k *Key) MustString(defaultVal string) string {
202 | val := k.String()
203 | if len(val) == 0 {
204 | k.value = defaultVal
205 | return defaultVal
206 | }
207 | return val
208 | }
209 |
210 | // MustBool always returns value without error,
211 | // it returns false if error occurs.
212 | func (k *Key) MustBool(defaultVal ...bool) bool {
213 | val, err := k.Bool()
214 | if len(defaultVal) > 0 && err != nil {
215 | k.value = strconv.FormatBool(defaultVal[0])
216 | return defaultVal[0]
217 | }
218 | return val
219 | }
220 |
221 | // MustFloat64 always returns value without error,
222 | // it returns 0.0 if error occurs.
223 | func (k *Key) MustFloat64(defaultVal ...float64) float64 {
224 | val, err := k.Float64()
225 | if len(defaultVal) > 0 && err != nil {
226 | k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
227 | return defaultVal[0]
228 | }
229 | return val
230 | }
231 |
232 | // MustInt always returns value without error,
233 | // it returns 0 if error occurs.
234 | func (k *Key) MustInt(defaultVal ...int) int {
235 | val, err := k.Int()
236 | if len(defaultVal) > 0 && err != nil {
237 | k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
238 | return defaultVal[0]
239 | }
240 | return val
241 | }
242 |
243 | // MustInt64 always returns value without error,
244 | // it returns 0 if error occurs.
245 | func (k *Key) MustInt64(defaultVal ...int64) int64 {
246 | val, err := k.Int64()
247 | if len(defaultVal) > 0 && err != nil {
248 | k.value = strconv.FormatInt(defaultVal[0], 10)
249 | return defaultVal[0]
250 | }
251 | return val
252 | }
253 |
254 | // MustUint always returns value without error,
255 | // it returns 0 if error occurs.
256 | func (k *Key) MustUint(defaultVal ...uint) uint {
257 | val, err := k.Uint()
258 | if len(defaultVal) > 0 && err != nil {
259 | k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
260 | return defaultVal[0]
261 | }
262 | return val
263 | }
264 |
265 | // MustUint64 always returns value without error,
266 | // it returns 0 if error occurs.
267 | func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
268 | val, err := k.Uint64()
269 | if len(defaultVal) > 0 && err != nil {
270 | k.value = strconv.FormatUint(defaultVal[0], 10)
271 | return defaultVal[0]
272 | }
273 | return val
274 | }
275 |
276 | // MustDuration always returns value without error,
277 | // it returns zero value if error occurs.
278 | func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
279 | val, err := k.Duration()
280 | if len(defaultVal) > 0 && err != nil {
281 | k.value = defaultVal[0].String()
282 | return defaultVal[0]
283 | }
284 | return val
285 | }
286 |
287 | // MustTimeFormat always parses with given format and returns value without error,
288 | // it returns zero value if error occurs.
289 | func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
290 | val, err := k.TimeFormat(format)
291 | if len(defaultVal) > 0 && err != nil {
292 | k.value = defaultVal[0].Format(format)
293 | return defaultVal[0]
294 | }
295 | return val
296 | }
297 |
298 | // MustTime always parses with RFC3339 format and returns value without error,
299 | // it returns zero value if error occurs.
300 | func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
301 | return k.MustTimeFormat(time.RFC3339, defaultVal...)
302 | }
303 |
304 | // In always returns value without error,
305 | // it returns default value if error occurs or doesn't fit into candidates.
306 | func (k *Key) In(defaultVal string, candidates []string) string {
307 | val := k.String()
308 | for _, cand := range candidates {
309 | if val == cand {
310 | return val
311 | }
312 | }
313 | return defaultVal
314 | }
315 |
316 | // InFloat64 always returns value without error,
317 | // it returns default value if error occurs or doesn't fit into candidates.
318 | func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
319 | val := k.MustFloat64()
320 | for _, cand := range candidates {
321 | if val == cand {
322 | return val
323 | }
324 | }
325 | return defaultVal
326 | }
327 |
328 | // InInt always returns value without error,
329 | // it returns default value if error occurs or doesn't fit into candidates.
330 | func (k *Key) InInt(defaultVal int, candidates []int) int {
331 | val := k.MustInt()
332 | for _, cand := range candidates {
333 | if val == cand {
334 | return val
335 | }
336 | }
337 | return defaultVal
338 | }
339 |
340 | // InInt64 always returns value without error,
341 | // it returns default value if error occurs or doesn't fit into candidates.
342 | func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
343 | val := k.MustInt64()
344 | for _, cand := range candidates {
345 | if val == cand {
346 | return val
347 | }
348 | }
349 | return defaultVal
350 | }
351 |
352 | // InUint always returns value without error,
353 | // it returns default value if error occurs or doesn't fit into candidates.
354 | func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
355 | val := k.MustUint()
356 | for _, cand := range candidates {
357 | if val == cand {
358 | return val
359 | }
360 | }
361 | return defaultVal
362 | }
363 |
364 | // InUint64 always returns value without error,
365 | // it returns default value if error occurs or doesn't fit into candidates.
366 | func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
367 | val := k.MustUint64()
368 | for _, cand := range candidates {
369 | if val == cand {
370 | return val
371 | }
372 | }
373 | return defaultVal
374 | }
375 |
376 | // InTimeFormat always parses with given format and returns value without error,
377 | // it returns default value if error occurs or doesn't fit into candidates.
378 | func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
379 | val := k.MustTimeFormat(format)
380 | for _, cand := range candidates {
381 | if val == cand {
382 | return val
383 | }
384 | }
385 | return defaultVal
386 | }
387 |
388 | // InTime always parses with RFC3339 format and returns value without error,
389 | // it returns default value if error occurs or doesn't fit into candidates.
390 | func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
391 | return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
392 | }
393 |
394 | // RangeFloat64 checks if value is in given range inclusively,
395 | // and returns default value if it's not.
396 | func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
397 | val := k.MustFloat64()
398 | if val < min || val > max {
399 | return defaultVal
400 | }
401 | return val
402 | }
403 |
404 | // RangeInt checks if value is in given range inclusively,
405 | // and returns default value if it's not.
406 | func (k *Key) RangeInt(defaultVal, min, max int) int {
407 | val := k.MustInt()
408 | if val < min || val > max {
409 | return defaultVal
410 | }
411 | return val
412 | }
413 |
414 | // RangeInt64 checks if value is in given range inclusively,
415 | // and returns default value if it's not.
416 | func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
417 | val := k.MustInt64()
418 | if val < min || val > max {
419 | return defaultVal
420 | }
421 | return val
422 | }
423 |
424 | // RangeTimeFormat checks if value with given format is in given range inclusively,
425 | // and returns default value if it's not.
426 | func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
427 | val := k.MustTimeFormat(format)
428 | if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
429 | return defaultVal
430 | }
431 | return val
432 | }
433 |
434 | // RangeTime checks if value with RFC3339 format is in given range inclusively,
435 | // and returns default value if it's not.
436 | func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
437 | return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
438 | }
439 |
440 | // Strings returns list of string divided by given delimiter.
441 | func (k *Key) Strings(delim string) []string {
442 | str := k.String()
443 | if len(str) == 0 {
444 | return []string{}
445 | }
446 |
447 | vals := strings.Split(str, delim)
448 | for i := range vals {
449 | // vals[i] = k.transformValue(strings.TrimSpace(vals[i]))
450 | vals[i] = strings.TrimSpace(vals[i])
451 | }
452 | return vals
453 | }
454 |
455 | // StringsWithShadows returns list of string divided by given delimiter.
456 | // Shadows will also be appended if any.
457 | func (k *Key) StringsWithShadows(delim string) []string {
458 | vals := k.ValueWithShadows()
459 | results := make([]string, 0, len(vals)*2)
460 | for i := range vals {
461 | if len(vals) == 0 {
462 | continue
463 | }
464 |
465 | results = append(results, strings.Split(vals[i], delim)...)
466 | }
467 |
468 | for i := range results {
469 | results[i] = k.transformValue(strings.TrimSpace(results[i]))
470 | }
471 | return results
472 | }
473 |
474 | // Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
475 | func (k *Key) Float64s(delim string) []float64 {
476 | vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
477 | return vals
478 | }
479 |
480 | // Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
481 | func (k *Key) Ints(delim string) []int {
482 | vals, _ := k.parseInts(k.Strings(delim), true, false)
483 | return vals
484 | }
485 |
486 | // Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
487 | func (k *Key) Int64s(delim string) []int64 {
488 | vals, _ := k.parseInt64s(k.Strings(delim), true, false)
489 | return vals
490 | }
491 |
492 | // Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
493 | func (k *Key) Uints(delim string) []uint {
494 | vals, _ := k.parseUints(k.Strings(delim), true, false)
495 | return vals
496 | }
497 |
498 | // Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
499 | func (k *Key) Uint64s(delim string) []uint64 {
500 | vals, _ := k.parseUint64s(k.Strings(delim), true, false)
501 | return vals
502 | }
503 |
504 | // TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
505 | // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
506 | func (k *Key) TimesFormat(format, delim string) []time.Time {
507 | vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
508 | return vals
509 | }
510 |
511 | // Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
512 | // Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
513 | func (k *Key) Times(delim string) []time.Time {
514 | return k.TimesFormat(time.RFC3339, delim)
515 | }
516 |
517 | // ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
518 | // it will not be included to result list.
519 | func (k *Key) ValidFloat64s(delim string) []float64 {
520 | vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
521 | return vals
522 | }
523 |
524 | // ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
525 | // not be included to result list.
526 | func (k *Key) ValidInts(delim string) []int {
527 | vals, _ := k.parseInts(k.Strings(delim), false, false)
528 | return vals
529 | }
530 |
531 | // ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
532 | // then it will not be included to result list.
533 | func (k *Key) ValidInt64s(delim string) []int64 {
534 | vals, _ := k.parseInt64s(k.Strings(delim), false, false)
535 | return vals
536 | }
537 |
538 | // ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
539 | // then it will not be included to result list.
540 | func (k *Key) ValidUints(delim string) []uint {
541 | vals, _ := k.parseUints(k.Strings(delim), false, false)
542 | return vals
543 | }
544 |
545 | // ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
546 | // integer, then it will not be included to result list.
547 | func (k *Key) ValidUint64s(delim string) []uint64 {
548 | vals, _ := k.parseUint64s(k.Strings(delim), false, false)
549 | return vals
550 | }
551 |
552 | // ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
553 | func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
554 | vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
555 | return vals
556 | }
557 |
558 | // ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
559 | func (k *Key) ValidTimes(delim string) []time.Time {
560 | return k.ValidTimesFormat(time.RFC3339, delim)
561 | }
562 |
563 | // StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
564 | func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
565 | return k.parseFloat64s(k.Strings(delim), false, true)
566 | }
567 |
568 | // StrictInts returns list of int divided by given delimiter or error on first invalid input.
569 | func (k *Key) StrictInts(delim string) ([]int, error) {
570 | return k.parseInts(k.Strings(delim), false, true)
571 | }
572 |
573 | // StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
574 | func (k *Key) StrictInt64s(delim string) ([]int64, error) {
575 | return k.parseInt64s(k.Strings(delim), false, true)
576 | }
577 |
578 | // StrictUints returns list of uint divided by given delimiter or error on first invalid input.
579 | func (k *Key) StrictUints(delim string) ([]uint, error) {
580 | return k.parseUints(k.Strings(delim), false, true)
581 | }
582 |
583 | // StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
584 | func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
585 | return k.parseUint64s(k.Strings(delim), false, true)
586 | }
587 |
588 | // StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
589 | // or error on first invalid input.
590 | func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
591 | return k.parseTimesFormat(format, k.Strings(delim), false, true)
592 | }
593 |
594 | // StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
595 | // or error on first invalid input.
596 | func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
597 | return k.StrictTimesFormat(time.RFC3339, delim)
598 | }
599 |
600 | // parseFloat64s transforms strings to float64s.
601 | func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
602 | vals := make([]float64, 0, len(strs))
603 | for _, str := range strs {
604 | val, err := strconv.ParseFloat(str, 64)
605 | if err != nil && returnOnInvalid {
606 | return nil, err
607 | }
608 | if err == nil || addInvalid {
609 | vals = append(vals, val)
610 | }
611 | }
612 | return vals, nil
613 | }
614 |
615 | // parseInts transforms strings to ints.
616 | func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
617 | vals := make([]int, 0, len(strs))
618 | for _, str := range strs {
619 | val, err := strconv.Atoi(str)
620 | if err != nil && returnOnInvalid {
621 | return nil, err
622 | }
623 | if err == nil || addInvalid {
624 | vals = append(vals, val)
625 | }
626 | }
627 | return vals, nil
628 | }
629 |
630 | // parseInt64s transforms strings to int64s.
631 | func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
632 | vals := make([]int64, 0, len(strs))
633 | for _, str := range strs {
634 | val, err := strconv.ParseInt(str, 10, 64)
635 | if err != nil && returnOnInvalid {
636 | return nil, err
637 | }
638 | if err == nil || addInvalid {
639 | vals = append(vals, val)
640 | }
641 | }
642 | return vals, nil
643 | }
644 |
645 | // parseUints transforms strings to uints.
646 | func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
647 | vals := make([]uint, 0, len(strs))
648 | for _, str := range strs {
649 | val, err := strconv.ParseUint(str, 10, 0)
650 | if err != nil && returnOnInvalid {
651 | return nil, err
652 | }
653 | if err == nil || addInvalid {
654 | vals = append(vals, uint(val))
655 | }
656 | }
657 | return vals, nil
658 | }
659 |
660 | // parseUint64s transforms strings to uint64s.
661 | func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
662 | vals := make([]uint64, 0, len(strs))
663 | for _, str := range strs {
664 | val, err := strconv.ParseUint(str, 10, 64)
665 | if err != nil && returnOnInvalid {
666 | return nil, err
667 | }
668 | if err == nil || addInvalid {
669 | vals = append(vals, val)
670 | }
671 | }
672 | return vals, nil
673 | }
674 |
675 | // parseTimesFormat transforms strings to times in given format.
676 | func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
677 | vals := make([]time.Time, 0, len(strs))
678 | for _, str := range strs {
679 | val, err := time.Parse(format, str)
680 | if err != nil && returnOnInvalid {
681 | return nil, err
682 | }
683 | if err == nil || addInvalid {
684 | vals = append(vals, val)
685 | }
686 | }
687 | return vals, nil
688 | }
689 |
690 | // SetValue changes key value.
691 | func (k *Key) SetValue(v string) {
692 | if k.s.f.BlockMode {
693 | k.s.f.lock.Lock()
694 | defer k.s.f.lock.Unlock()
695 | }
696 |
697 | k.value = v
698 | k.s.keysHash[k.name] = v
699 | }
700 |
--------------------------------------------------------------------------------