├── .gitignore ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── cmd └── cmd.go ├── delayer.conf ├── logic └── timer.go ├── main └── delayer.go ├── utils ├── config.go ├── logger.go ├── sys.go └── type_cast.go └── vendor ├── github.com └── gomodule │ └── redigo │ ├── LICENSE │ ├── internal │ └── commandinfo.go │ └── redis │ ├── conn.go │ ├── doc.go │ ├── go16.go │ ├── go17.go │ ├── go18.go │ ├── log.go │ ├── pool.go │ ├── pool17.go │ ├── pubsub.go │ ├── redis.go │ ├── reply.go │ ├── scan.go │ └── script.go └── gopkg.in └── ini.v1 ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── error.go ├── file.go ├── ini.go ├── key.go ├── parser.go ├── section.go └── struct.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | logs -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | digest = "1:38ec74012390146c45af1f92d46e5382b50531247929ff3a685d2b2be65155ac" 6 | name = "github.com/gomodule/redigo" 7 | packages = [ 8 | "internal", 9 | "redis", 10 | ] 11 | pruneopts = "UT" 12 | revision = "9c11da706d9b7902c6da69c592f75637793fe121" 13 | version = "v2.0.0" 14 | 15 | [[projects]] 16 | digest = "1:b98e7574fc27ec166fb31195ec72c3bd0bffd73926d3612eb4c929bc5236f75b" 17 | name = "gopkg.in/ini.v1" 18 | packages = ["."] 19 | pruneopts = "UT" 20 | revision = "7b294651033cd7d9e7f0d9ffa1b75ed1e198e737" 21 | version = "v1.38.3" 22 | 23 | [solve-meta] 24 | analyzer-name = "dep" 25 | analyzer-version = 1 26 | input-imports = [ 27 | "github.com/gomodule/redigo/redis", 28 | "gopkg.in/ini.v1", 29 | ] 30 | solver-name = "gps-cdcl" 31 | solver-version = 1 32 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [prune] 29 | go-tests = true 30 | unused-packages = true 31 | -------------------------------------------------------------------------------- /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, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Delayer 2 | 3 | 基于 Redis 的延迟消息队列中间件,采用 Golang 开发,支持 PHP、Golang 等多种语言客户端。 4 | 5 | 参考 [有赞延迟队列设计](http://tech.youzan.com/queuing_delay) 中的部分设计,优化后实现。 6 | 7 | ## 应用场景 8 | 9 | - 订单超过30分钟未支付,自动关闭订单。 10 | - 订单完成后, 如果用户一直未评价, 5天后自动好评。 11 | - 会员到期前3天,短信通知续费。 12 | - 其他针对某个任务,延迟执行功能的需求。 13 | 14 | ## 实现原理 15 | 16 | - 客户端:push 任务时,任务数据存入 hash 中,jobID 存入 zset 中,pop 时从指定的 list 中取准备好的数据。 17 | - 服务器端:定时使用连接池并行将 zset 中到期的 jobID 放入对应的 list 中,供客户端 pop 取出。 18 | 19 | ## 核心特征 20 | 21 | - 使用 Golang 开发,高性能。 22 | - 高可用:服务器端操作是原子的,并且做了优雅停止,不会丢失数据,在redis断线时会自动重连。 23 | - 可通过配置文件控制执行性能参数。 24 | - 提供多种语言的 SDK,使用简单快捷。 25 | 26 | ## 如何使用 27 | 28 | `delayer` 分为: 29 | 30 | - 服务器端:负责定时扫描到期的任务,并放入队列,需在服务器上常驻执行。 31 | - 客户端:在代码中使用,以类库的形式,提供 `push`、`pop`、`bPop`、`remove` 方法操作任务。 32 | 33 | ## 服务器端 34 | 35 | 在 https://github.com/mix-basic/delayer/releases 中下载对应平台的程序。 36 | 37 | > 支持 windows、linux、mac 三种平台 38 | 39 | 然后修改配置文件 `delayer.conf`: 40 | 41 | ``` 42 | [delayer] 43 | pid = /var/run/delayer.pid ; 需单例执行时配置, 多实例执行时留空, Win不支持单例 44 | timer_interval = 1000 ; 计算间隔时间, 单位毫秒 45 | access_log = logs/access.log ; 存取日志 46 | error_log = logs/error.log ; 错误日志 47 | 48 | [redis] 49 | host = 127.0.0.1 ; 连接地址 50 | port = 6379 ; 连接端口 51 | database = 0 ; 数据库编号 52 | password = ; 密码, 无需密码留空 53 | max_idle = 2 ; 最大空闲连接数 54 | max_active = 20 ; 最大激活连接数 55 | idle_timeout = 3600 ; 空闲连接超时时间, 单位秒 56 | conn_max_lifetime = 3600 ; 连接最大生存时间, 单位秒 57 | ``` 58 | 59 | 查看帮助: 60 | 61 | ``` 62 | [root@localhost bin]# ./delayer -h 63 | Usage: delayer [options] 64 | 65 | Options: 66 | -d/--daemon run in the background 67 | -c/--configuration FILENAME -- configuration file path (searches if not given) 68 | -h/--help -- print this usage message and exit 69 | -v/--version -- print version number and exit 70 | ``` 71 | 72 | 启动: 73 | 74 | ``` 75 | [root@localhost bin]# ./delayer 76 | ____ __ 77 | / __ \___ / /___ ___ _____ _____ 78 | / / / / _ \/ / __ `/ / / / _ \/ ___/ 79 | / /_/ / __/ / /_/ / /_/ / __/ / 80 | /_____/\___/_/\__,_/\__, /\___/_/ 81 | /____/ 82 | Service: delayer 83 | Version: 1.0.1 84 | [info] 2018/10/21 11:24:24 Service started successfully, PID: 31023 85 | ``` 86 | 87 | ## 客户端 88 | 89 | 我们提供了以下几种语言: 90 | 91 | > 根据对应项目的说明使用 92 | 93 | - PHP:https://github.com/mix-basic/delayer-client-php 94 | - Golang:https://github.com/mix-basic/delayer-client-golang 95 | - Java:待定 96 | - Python:待定 97 | 98 | ## License 99 | 100 | Apache License Version 2.0, http://www.apache.org/licenses/ 101 | -------------------------------------------------------------------------------- /cmd/cmd.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "delayer/utils" 5 | "delayer/logic" 6 | "fmt" 7 | "os" 8 | "flag" 9 | "os/signal" 10 | "syscall" 11 | "io/ioutil" 12 | ) 13 | 14 | const ( 15 | APP_VERSION = "1.0.4" 16 | ) 17 | 18 | // 命令类 19 | type Cmd struct { 20 | config utils.Config 21 | logger utils.Logger 22 | timer logic.Timer 23 | exit chan bool 24 | } 25 | 26 | // 执行 27 | func (p *Cmd) Run() { 28 | // 命令行参数处理 29 | daemon, configuration := p.handleFlags() 30 | // 守护进程 31 | if daemon { 32 | utils.Daemon() 33 | } 34 | // 变量定义 35 | p.exit = make(chan bool) 36 | // 欢迎 37 | welcome() 38 | // 实例化公共组件 39 | p.config = utils.LoadConfig(configuration) 40 | p.logger = utils.NewLogger(p.config) 41 | // pid处理 42 | p.handlePid() 43 | // 输出启动日志 44 | p.logger.Info(fmt.Sprintf("Service started successfully, PID: %d", os.Getpid())) 45 | // 启动定时器 46 | p.timer = logic.Timer{ 47 | Config: p.config, 48 | Logger: p.logger, 49 | } 50 | p.timer.Init() 51 | p.timer.Start() 52 | // 信号处理 53 | p.handleSignal() 54 | // 退出 55 | <-p.exit 56 | // 输出停止日志 57 | p.logger.Info(fmt.Sprintf("Service stopped successfully, PID: %d", os.Getpid())) 58 | } 59 | 60 | // 欢迎信息 61 | func welcome() { 62 | fmt.Println(" ____ __ "); 63 | fmt.Println(" / __ \\___ / /___ ___ _____ _____"); 64 | fmt.Println(" / / / / _ \\/ / __ `/ / / / _ \\/ ___/"); 65 | fmt.Println(" / /_/ / __/ / /_/ / /_/ / __/ / "); 66 | fmt.Println("/_____/\\___/_/\\__,_/\\__, /\\___/_/ "); 67 | fmt.Println(" /____/ "); 68 | fmt.Println("Service: delayer"); 69 | fmt.Println("Version: " + APP_VERSION); 70 | } 71 | 72 | // PID处理 73 | func (p *Cmd) handlePid() { 74 | // 不处理 75 | if (p.config.Delayer.Pid == "") { 76 | return 77 | } 78 | // 读取 79 | pidStr, err := ioutil.ReadFile(p.config.Delayer.Pid) 80 | if err != nil { 81 | p.writePidFile(p.config.Delayer.Pid) 82 | return 83 | } 84 | // 重复启动处理 85 | pid, err := utils.ByteToInt(pidStr) 86 | if (err != nil) { 87 | p.writePidFile(p.config.Delayer.Pid) 88 | return 89 | } 90 | pro, err := os.FindProcess(pid) 91 | if err != nil { 92 | p.writePidFile(p.config.Delayer.Pid) 93 | return 94 | } 95 | // Win 中全部返回错误: not supported by windows 96 | err = pro.Signal(os.Signal(syscall.Signal(0))) 97 | if err != nil { 98 | // os: process already finished 99 | // not supported by windows 100 | p.writePidFile(p.config.Delayer.Pid) 101 | return 102 | } 103 | p.logger.Error(fmt.Sprintf("ERROR: Service is being executed, PID: %d", pid), true) 104 | } 105 | 106 | // 写入PID文件 107 | func (p *Cmd) writePidFile(pidFile string) { 108 | err := ioutil.WriteFile(pidFile, utils.IntToByte(os.Getpid()), 0644) 109 | if err != nil { 110 | p.logger.Error(fmt.Sprintf("PID file cannot be written: %s", pidFile), true) 111 | } 112 | } 113 | 114 | // 信号处理 115 | func (p *Cmd) handleSignal() { 116 | ch := make(chan os.Signal) 117 | signal.Notify(ch, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) 118 | go func() { 119 | sig := <-ch 120 | switch sig { 121 | case syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT: 122 | p.timer.Stop() 123 | p.exit <- true 124 | } 125 | }() 126 | } 127 | 128 | // 参数处理 129 | func (p *Cmd) handleFlags() (bool, string) { 130 | // 参数解析 131 | flagD := flag.Bool("d", false, "") 132 | flagDaemon := flag.Bool("daemon", false, "") 133 | flagH := flag.Bool("h", false, "") 134 | flagHelp := flag.Bool("help", false, "") 135 | flagV := flag.Bool("v", false, "") 136 | flagVersion := flag.Bool("version", false, "") 137 | flagC := flag.String("c", "", "") 138 | flagConfiguration := flag.String("configuration", "", "") 139 | flag.Parse() 140 | // 参数取值 141 | daemon := *flagD || *flagDaemon 142 | help := *flagH || *flagHelp 143 | version := *flagV || *flagVersion 144 | configuration := "" 145 | if (*flagC == "") { 146 | configuration = *flagConfiguration 147 | } else { 148 | configuration = *flagC 149 | } 150 | // 打印型命令处理 151 | if help { 152 | printHelp() 153 | } 154 | if version { 155 | printVersion() 156 | } 157 | // 返回参数值 158 | return daemon, configuration 159 | } 160 | 161 | // 打印帮助 162 | func printHelp() { 163 | fmt.Println("Usage: delayer [options]"); 164 | fmt.Println() 165 | fmt.Println("Options:"); 166 | fmt.Println("-d/--daemon run in the background"); 167 | fmt.Println("-c/--configuration FILENAME -- configuration file path (searches if not given)"); 168 | fmt.Println("-h/--help -- print this usage message and exit"); 169 | fmt.Println("-v/--version -- print version number and exit"); 170 | fmt.Println() 171 | os.Exit(0) 172 | } 173 | 174 | // 打印版本 175 | func printVersion() { 176 | fmt.Println(APP_VERSION); 177 | fmt.Println() 178 | os.Exit(0) 179 | } 180 | -------------------------------------------------------------------------------- /delayer.conf: -------------------------------------------------------------------------------- 1 | [delayer] 2 | pid = /var/run/delayer.pid ; 需单例执行时配置, 多实例执行时留空, Win不支持单例 3 | timer_interval = 1000 ; 计算间隔时间, 单位毫秒 4 | access_log = logs/access.log ; 存取日志 5 | error_log = logs/error.log ; 错误日志 6 | 7 | [redis] 8 | host = 127.0.0.1 ; 连接地址 9 | port = 6379 ; 连接端口 10 | database = 0 ; 数据库编号 11 | password = ; 密码, 无需密码留空 12 | max_idle = 2 ; 最大空闲连接数 13 | max_active = 20 ; 最大激活连接数 14 | idle_timeout = 3600 ; 空闲连接超时时间, 单位秒 15 | conn_max_lifetime = 3600 ; 连接最大生存时间, 单位秒 16 | -------------------------------------------------------------------------------- /logic/timer.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "delayer/utils" 5 | "github.com/gomodule/redigo/redis" 6 | "time" 7 | "strings" 8 | "fmt" 9 | ) 10 | 11 | // 定时器类 12 | type Timer struct { 13 | Config utils.Config 14 | Logger utils.Logger 15 | Ticker *time.Ticker 16 | Pool *redis.Pool 17 | HandleError func(err error, funcName string, data string) 18 | } 19 | 20 | const ( 21 | KEY_JOB_POOL = "delayer:job_pool" 22 | PREFIX_JOB_BUCKET = "delayer:job_bucket:" 23 | PREFIX_READY_QUEUE = "delayer:ready_queue:" 24 | ) 25 | 26 | // 初始化 27 | func (p *Timer) Init() { 28 | pool := &redis.Pool{ 29 | Dial: func() (redis.Conn, error) { 30 | c, err := redis.Dial("tcp", p.Config.Redis.Host+":"+p.Config.Redis.Port) 31 | if err != nil { 32 | return nil, err 33 | } 34 | if (p.Config.Redis.Password != "") { 35 | if _, err := c.Do("AUTH", p.Config.Redis.Password); err != nil { 36 | c.Close() 37 | return nil, err 38 | } 39 | } 40 | if _, err := c.Do("SELECT", p.Config.Redis.Database); err != nil { 41 | c.Close() 42 | return nil, err 43 | } 44 | return c, nil 45 | }, 46 | MaxIdle: p.Config.Redis.MaxIdle, 47 | MaxActive: p.Config.Redis.MaxActive, 48 | IdleTimeout: time.Duration(p.Config.Redis.IdleTimeout) * time.Second, 49 | MaxConnLifetime: time.Duration(p.Config.Redis.ConnMaxLifetime) * time.Second, 50 | } 51 | p.Pool = pool 52 | handleError := func(err error, funcName string, data string) { 53 | if (err != nil) { 54 | if (data != "") { 55 | data = ", [" + data + "]" 56 | } 57 | p.Logger.Error(fmt.Sprintf("FAILURE: func %s, %s%s.", funcName, err.Error(), data), false) 58 | } 59 | } 60 | p.HandleError = handleError 61 | } 62 | 63 | // 开始 64 | func (p *Timer) Start() { 65 | ticker := time.NewTicker(time.Duration(p.Config.Delayer.TimerInterval) * time.Millisecond) 66 | go func() { 67 | for range ticker.C { 68 | p.run() 69 | } 70 | }() 71 | p.Ticker = ticker 72 | } 73 | 74 | // 执行任务 75 | func (p *Timer) run() { 76 | // 获取到期的任务 77 | jobs, err := p.getExpireJobs() 78 | if (err != nil) { 79 | p.HandleError(err, "getExpireJobs", "") 80 | return 81 | } 82 | // 并行获取Topic 83 | topics := make(map[string][]string) 84 | ch := make(chan []string) 85 | for _, jobID := range jobs { 86 | go p.getJobTopic(jobID, ch) 87 | } 88 | // Topic分组 89 | for i := 0; i < len(jobs); i++ { 90 | arr := <-ch 91 | if (arr[1] != "") { 92 | if _, ok := topics[arr[1]]; !ok { 93 | jobIDs := []string{arr[0]} 94 | topics[arr[1]] = jobIDs 95 | } else { 96 | topics[arr[1]] = append(topics[arr[1]], arr[0]) 97 | } 98 | } 99 | } 100 | // 并行移动至Topic对应的ReadyQueue 101 | for topic, jobIDs := range topics { 102 | go p.moveJobToReadyQueue(jobIDs, topic) 103 | } 104 | } 105 | 106 | // 获取到期的任务 107 | func (p *Timer) getExpireJobs() ([]string, error) { 108 | conn := p.Pool.Get() 109 | defer conn.Close() 110 | return redis.Strings(conn.Do("ZRANGEBYSCORE", KEY_JOB_POOL, "0", time.Now().Unix())) 111 | } 112 | 113 | // 获取任务的Topic 114 | func (p *Timer) getJobTopic(jobID string, ch chan []string) { 115 | conn := p.Pool.Get() 116 | defer conn.Close() 117 | topic, err := redis.Strings(conn.Do("HMGET", PREFIX_JOB_BUCKET+jobID, "topic")) 118 | if (err != nil) { 119 | p.HandleError(err, "getJobTopic", jobID) 120 | ch <- []string{jobID, ""} 121 | return 122 | } 123 | arr := []string{jobID, topic[0]} 124 | ch <- arr 125 | } 126 | 127 | // 移动任务至ReadyQueue 128 | func (p *Timer) moveJobToReadyQueue(jobIDs []string, topic string) { 129 | // 获取连接 130 | conn := p.Pool.Get() 131 | defer conn.Close() 132 | jobIDsStr := strings.Join(jobIDs, ",") 133 | // 开启事物 134 | if err := p.startTrans(conn); err != nil { 135 | p.HandleError(err, "startTrans", jobIDsStr) 136 | return 137 | } 138 | // 移除JobPool 139 | if err := p.delJobPool(conn, jobIDs, topic); err != nil { 140 | p.HandleError(err, "delJobPool", jobIDsStr) 141 | return 142 | } 143 | // 插入ReadyQueue 144 | if err := p.addReadyQueue(conn, jobIDs, topic); err != nil { 145 | p.HandleError(err, "addReadyQueue", jobIDsStr) 146 | return 147 | } 148 | // 提交事物 149 | values, err := p.commit(conn) 150 | if err != nil { 151 | p.HandleError(err, "commit", jobIDsStr) 152 | return 153 | } 154 | // 事务结果处理 155 | v := values[0].(int64) 156 | v1 := values[1].(int64) 157 | if v == 0 || v1 == 0 { 158 | p.HandleError(err, "commit", jobIDsStr) 159 | return 160 | } 161 | // 打印日志 162 | p.Logger.Info(fmt.Sprintf("Job is ready, Topic: %s, IDs: [%s]", topic, jobIDsStr)) 163 | } 164 | 165 | // 开启事务 166 | func (p *Timer) startTrans(conn redis.Conn) error { 167 | return conn.Send("MULTI") 168 | } 169 | 170 | // 提交事务 171 | func (p *Timer) commit(conn redis.Conn) ([]interface{}, error) { 172 | return redis.Values(conn.Do("EXEC")) 173 | } 174 | 175 | // 移除JobPool 176 | func (p *Timer) delJobPool(conn redis.Conn, jobIDs []string, topic string) error { 177 | args := make([]interface{}, len(jobIDs)+1) 178 | args[0] = KEY_JOB_POOL 179 | for k, v := range jobIDs { 180 | args[k+1] = v 181 | } 182 | return conn.Send("ZREM", args...) 183 | } 184 | 185 | // 插入ReadyQueue 186 | func (p *Timer) addReadyQueue(conn redis.Conn, jobIDs []string, topic string) error { 187 | args := make([]interface{}, len(jobIDs)+1) 188 | args[0] = PREFIX_READY_QUEUE + topic 189 | for k, v := range jobIDs { 190 | args[k+1] = v 191 | } 192 | return conn.Send("LPUSH", args...) 193 | } 194 | 195 | // 执行 196 | func (p *Timer) Stop() { 197 | p.Ticker.Stop() 198 | } 199 | -------------------------------------------------------------------------------- /main/delayer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "delayer/cmd" 4 | 5 | func main() { 6 | c := cmd.Cmd{} 7 | c.Run() 8 | } 9 | -------------------------------------------------------------------------------- /utils/config.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "gopkg.in/ini.v1" 5 | "log" 6 | "fmt" 7 | ) 8 | 9 | // 配置数据 10 | type Config struct { 11 | Delayer Delayer 12 | Redis Redis 13 | } 14 | 15 | // delayer 节点数据 16 | type Delayer struct { 17 | Pid string 18 | TimerInterval int64 19 | BucketMaxLifetime int64 20 | AccessLog string 21 | ErrorLog string 22 | } 23 | 24 | // redis 节点数据 25 | type Redis struct { 26 | Host string 27 | Port string 28 | Database int 29 | Password string 30 | MaxIdle int 31 | MaxActive int 32 | IdleTimeout int64 33 | ConnMaxLifetime int64 34 | } 35 | 36 | // 载入配置 37 | func LoadConfig(fileName string) Config { 38 | // 默认文件 39 | if fileName == "" { 40 | fileName = "delayer.conf" 41 | } 42 | // 读取配置文件 43 | conf, err := ini.Load(fileName) 44 | if err != nil { 45 | log.Fatalln(fmt.Sprintf("Configuration file read error: %s", fileName)) 46 | } 47 | // 提取数据 48 | delayer := conf.Section("delayer") 49 | pid := delayer.Key("pid").String() 50 | timerInterval, _ := delayer.Key("timer_interval").Int64() 51 | accessLog := delayer.Key("access_log").String() 52 | errorLog := delayer.Key("error_log").String() 53 | redis := conf.Section("redis") 54 | host := redis.Key("host").String() 55 | port := redis.Key("port").String() 56 | database, _ := redis.Key("database").Int() 57 | password := redis.Key("password").String() 58 | maxIdle, _ := redis.Key("max_idle").Int() 59 | maxActive, _ := redis.Key("max_active").Int() 60 | idleTimeout, _ := redis.Key("idle_timeout").Int64() 61 | connMaxLifetime, _ := redis.Key("conn_max_lifetime").Int64() 62 | // 返回 63 | data := Config{ 64 | Delayer: Delayer{ 65 | Pid: pid, 66 | TimerInterval: timerInterval, 67 | AccessLog: accessLog, 68 | ErrorLog: errorLog, 69 | }, 70 | Redis: Redis{ 71 | Host: host, 72 | Port: port, 73 | Database: database, 74 | Password: password, 75 | MaxIdle: maxIdle, 76 | MaxActive: maxActive, 77 | IdleTimeout: idleTimeout, 78 | ConnMaxLifetime: connMaxLifetime, 79 | }, 80 | } 81 | return data 82 | } 83 | -------------------------------------------------------------------------------- /utils/logger.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "io" 7 | "fmt" 8 | ) 9 | 10 | // 日志类 11 | type Logger struct { 12 | AccessLog string 13 | ErrorLog string 14 | } 15 | 16 | // 打开文件 17 | func (p *Logger) openFile(fileName string) *os.File { 18 | logFile, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 19 | if err != nil { 20 | log.Fatalln(fmt.Sprintf("Open file Failed: %s", fileName)) 21 | } 22 | return logFile 23 | } 24 | 25 | // 信息日志 26 | func (p *Logger) Info(message string) { 27 | fileName := p.AccessLog 28 | var out io.Writer 29 | if (fileName == "") { 30 | out = os.Stdout 31 | } else { 32 | logFile := p.openFile(fileName) 33 | defer logFile.Close() 34 | out = io.MultiWriter(os.Stdout, logFile) 35 | } 36 | logLogger := log.New(out, "[info] ", log.LstdFlags) 37 | logLogger.Println(message) 38 | } 39 | 40 | // 错误日志 41 | func (p *Logger) Error(message string, exit bool) { 42 | fileName := p.ErrorLog 43 | var out io.Writer 44 | if (fileName == "") { 45 | out = os.Stdout 46 | } else { 47 | logFile := p.openFile(fileName) 48 | defer logFile.Close() 49 | out = io.MultiWriter(logFile, os.Stdout) 50 | } 51 | logLogger := log.New(out, "[error] ", log.LstdFlags) 52 | if exit { 53 | logLogger.Fatalln(message) 54 | } 55 | logLogger.Println(message) 56 | } 57 | 58 | // 创建实例 59 | func NewLogger(config Config) Logger { 60 | logger := Logger{ 61 | AccessLog: config.Delayer.AccessLog, 62 | ErrorLog: config.Delayer.ErrorLog, 63 | } 64 | return logger 65 | } 66 | -------------------------------------------------------------------------------- /utils/sys.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "os/exec" 6 | ) 7 | 8 | // 守护执行 9 | func Daemon() { 10 | args := os.Args 11 | var nargs []string 12 | for _, arg := range args { 13 | d := arg == "-d" 14 | daemon := arg == "-daemon" 15 | if d && daemon { 16 | nargs = append(nargs, arg) 17 | } 18 | } 19 | cmd := exec.Command(args[0], nargs...) 20 | cmd.Start() 21 | os.Exit(0) 22 | } 23 | -------------------------------------------------------------------------------- /utils/type_cast.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func IntToByte(data int) []byte { 8 | return []byte(IntToString(data)) 9 | } 10 | 11 | func ByteToInt(data []byte) (int, error) { 12 | return StringToInt(string(data)) 13 | } 14 | 15 | func StringToInt(data string) (int, error) { 16 | return strconv.Atoi(data) 17 | } 18 | 19 | func StringToInt64(data string) (int64, error) { 20 | return strconv.ParseInt(data, 10, 64) 21 | } 22 | 23 | func IntToString(data int) string { 24 | return strconv.Itoa(data) 25 | } 26 | 27 | func Int64ToString(data int64) string { 28 | return strconv.FormatInt(data, 10) 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/internal/commandinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Gary Burd 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 internal // import "github.com/gomodule/redigo/internal" 16 | 17 | import ( 18 | "strings" 19 | ) 20 | 21 | const ( 22 | WatchState = 1 << iota 23 | MultiState 24 | SubscribeState 25 | MonitorState 26 | ) 27 | 28 | type CommandInfo struct { 29 | Set, Clear int 30 | } 31 | 32 | var commandInfos = map[string]CommandInfo{ 33 | "WATCH": {Set: WatchState}, 34 | "UNWATCH": {Clear: WatchState}, 35 | "MULTI": {Set: MultiState}, 36 | "EXEC": {Clear: WatchState | MultiState}, 37 | "DISCARD": {Clear: WatchState | MultiState}, 38 | "PSUBSCRIBE": {Set: SubscribeState}, 39 | "SUBSCRIBE": {Set: SubscribeState}, 40 | "MONITOR": {Set: MonitorState}, 41 | } 42 | 43 | func init() { 44 | for n, ci := range commandInfos { 45 | commandInfos[strings.ToLower(n)] = ci 46 | } 47 | } 48 | 49 | func LookupCommandInfo(commandName string) CommandInfo { 50 | if ci, ok := commandInfos[commandName]; ok { 51 | return ci 52 | } 53 | return commandInfos[strings.ToUpper(commandName)] 54 | } 55 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/conn.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "bufio" 19 | "bytes" 20 | "crypto/tls" 21 | "errors" 22 | "fmt" 23 | "io" 24 | "net" 25 | "net/url" 26 | "regexp" 27 | "strconv" 28 | "sync" 29 | "time" 30 | ) 31 | 32 | var ( 33 | _ ConnWithTimeout = (*conn)(nil) 34 | ) 35 | 36 | // conn is the low-level implementation of Conn 37 | type conn struct { 38 | // Shared 39 | mu sync.Mutex 40 | pending int 41 | err error 42 | conn net.Conn 43 | 44 | // Read 45 | readTimeout time.Duration 46 | br *bufio.Reader 47 | 48 | // Write 49 | writeTimeout time.Duration 50 | bw *bufio.Writer 51 | 52 | // Scratch space for formatting argument length. 53 | // '*' or '$', length, "\r\n" 54 | lenScratch [32]byte 55 | 56 | // Scratch space for formatting integers and floats. 57 | numScratch [40]byte 58 | } 59 | 60 | // DialTimeout acts like Dial but takes timeouts for establishing the 61 | // connection to the server, writing a command and reading a reply. 62 | // 63 | // Deprecated: Use Dial with options instead. 64 | func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { 65 | return Dial(network, address, 66 | DialConnectTimeout(connectTimeout), 67 | DialReadTimeout(readTimeout), 68 | DialWriteTimeout(writeTimeout)) 69 | } 70 | 71 | // DialOption specifies an option for dialing a Redis server. 72 | type DialOption struct { 73 | f func(*dialOptions) 74 | } 75 | 76 | type dialOptions struct { 77 | readTimeout time.Duration 78 | writeTimeout time.Duration 79 | dialer *net.Dialer 80 | dial func(network, addr string) (net.Conn, error) 81 | db int 82 | password string 83 | useTLS bool 84 | skipVerify bool 85 | tlsConfig *tls.Config 86 | } 87 | 88 | // DialReadTimeout specifies the timeout for reading a single command reply. 89 | func DialReadTimeout(d time.Duration) DialOption { 90 | return DialOption{func(do *dialOptions) { 91 | do.readTimeout = d 92 | }} 93 | } 94 | 95 | // DialWriteTimeout specifies the timeout for writing a single command. 96 | func DialWriteTimeout(d time.Duration) DialOption { 97 | return DialOption{func(do *dialOptions) { 98 | do.writeTimeout = d 99 | }} 100 | } 101 | 102 | // DialConnectTimeout specifies the timeout for connecting to the Redis server when 103 | // no DialNetDial option is specified. 104 | func DialConnectTimeout(d time.Duration) DialOption { 105 | return DialOption{func(do *dialOptions) { 106 | do.dialer.Timeout = d 107 | }} 108 | } 109 | 110 | // DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server 111 | // when no DialNetDial option is specified. 112 | // If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then 113 | // the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. 114 | func DialKeepAlive(d time.Duration) DialOption { 115 | return DialOption{func(do *dialOptions) { 116 | do.dialer.KeepAlive = d 117 | }} 118 | } 119 | 120 | // DialNetDial specifies a custom dial function for creating TCP 121 | // connections, otherwise a net.Dialer customized via the other options is used. 122 | // DialNetDial overrides DialConnectTimeout and DialKeepAlive. 123 | func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { 124 | return DialOption{func(do *dialOptions) { 125 | do.dial = dial 126 | }} 127 | } 128 | 129 | // DialDatabase specifies the database to select when dialing a connection. 130 | func DialDatabase(db int) DialOption { 131 | return DialOption{func(do *dialOptions) { 132 | do.db = db 133 | }} 134 | } 135 | 136 | // DialPassword specifies the password to use when connecting to 137 | // the Redis server. 138 | func DialPassword(password string) DialOption { 139 | return DialOption{func(do *dialOptions) { 140 | do.password = password 141 | }} 142 | } 143 | 144 | // DialTLSConfig specifies the config to use when a TLS connection is dialed. 145 | // Has no effect when not dialing a TLS connection. 146 | func DialTLSConfig(c *tls.Config) DialOption { 147 | return DialOption{func(do *dialOptions) { 148 | do.tlsConfig = c 149 | }} 150 | } 151 | 152 | // DialTLSSkipVerify disables server name verification when connecting over 153 | // TLS. Has no effect when not dialing a TLS connection. 154 | func DialTLSSkipVerify(skip bool) DialOption { 155 | return DialOption{func(do *dialOptions) { 156 | do.skipVerify = skip 157 | }} 158 | } 159 | 160 | // DialUseTLS specifies whether TLS should be used when connecting to the 161 | // server. This option is ignore by DialURL. 162 | func DialUseTLS(useTLS bool) DialOption { 163 | return DialOption{func(do *dialOptions) { 164 | do.useTLS = useTLS 165 | }} 166 | } 167 | 168 | // Dial connects to the Redis server at the given network and 169 | // address using the specified options. 170 | func Dial(network, address string, options ...DialOption) (Conn, error) { 171 | do := dialOptions{ 172 | dialer: &net.Dialer{ 173 | KeepAlive: time.Minute * 5, 174 | }, 175 | } 176 | for _, option := range options { 177 | option.f(&do) 178 | } 179 | if do.dial == nil { 180 | do.dial = do.dialer.Dial 181 | } 182 | 183 | netConn, err := do.dial(network, address) 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | if do.useTLS { 189 | var tlsConfig *tls.Config 190 | if do.tlsConfig == nil { 191 | tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} 192 | } else { 193 | tlsConfig = cloneTLSConfig(do.tlsConfig) 194 | } 195 | if tlsConfig.ServerName == "" { 196 | host, _, err := net.SplitHostPort(address) 197 | if err != nil { 198 | netConn.Close() 199 | return nil, err 200 | } 201 | tlsConfig.ServerName = host 202 | } 203 | 204 | tlsConn := tls.Client(netConn, tlsConfig) 205 | if err := tlsConn.Handshake(); err != nil { 206 | netConn.Close() 207 | return nil, err 208 | } 209 | netConn = tlsConn 210 | } 211 | 212 | c := &conn{ 213 | conn: netConn, 214 | bw: bufio.NewWriter(netConn), 215 | br: bufio.NewReader(netConn), 216 | readTimeout: do.readTimeout, 217 | writeTimeout: do.writeTimeout, 218 | } 219 | 220 | if do.password != "" { 221 | if _, err := c.Do("AUTH", do.password); err != nil { 222 | netConn.Close() 223 | return nil, err 224 | } 225 | } 226 | 227 | if do.db != 0 { 228 | if _, err := c.Do("SELECT", do.db); err != nil { 229 | netConn.Close() 230 | return nil, err 231 | } 232 | } 233 | 234 | return c, nil 235 | } 236 | 237 | var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) 238 | 239 | // DialURL connects to a Redis server at the given URL using the Redis 240 | // URI scheme. URLs should follow the draft IANA specification for the 241 | // scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). 242 | func DialURL(rawurl string, options ...DialOption) (Conn, error) { 243 | u, err := url.Parse(rawurl) 244 | if err != nil { 245 | return nil, err 246 | } 247 | 248 | if u.Scheme != "redis" && u.Scheme != "rediss" { 249 | return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) 250 | } 251 | 252 | // As per the IANA draft spec, the host defaults to localhost and 253 | // the port defaults to 6379. 254 | host, port, err := net.SplitHostPort(u.Host) 255 | if err != nil { 256 | // assume port is missing 257 | host = u.Host 258 | port = "6379" 259 | } 260 | if host == "" { 261 | host = "localhost" 262 | } 263 | address := net.JoinHostPort(host, port) 264 | 265 | if u.User != nil { 266 | password, isSet := u.User.Password() 267 | if isSet { 268 | options = append(options, DialPassword(password)) 269 | } 270 | } 271 | 272 | match := pathDBRegexp.FindStringSubmatch(u.Path) 273 | if len(match) == 2 { 274 | db := 0 275 | if len(match[1]) > 0 { 276 | db, err = strconv.Atoi(match[1]) 277 | if err != nil { 278 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 279 | } 280 | } 281 | if db != 0 { 282 | options = append(options, DialDatabase(db)) 283 | } 284 | } else if u.Path != "" { 285 | return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) 286 | } 287 | 288 | options = append(options, DialUseTLS(u.Scheme == "rediss")) 289 | 290 | return Dial("tcp", address, options...) 291 | } 292 | 293 | // NewConn returns a new Redigo connection for the given net connection. 294 | func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { 295 | return &conn{ 296 | conn: netConn, 297 | bw: bufio.NewWriter(netConn), 298 | br: bufio.NewReader(netConn), 299 | readTimeout: readTimeout, 300 | writeTimeout: writeTimeout, 301 | } 302 | } 303 | 304 | func (c *conn) Close() error { 305 | c.mu.Lock() 306 | err := c.err 307 | if c.err == nil { 308 | c.err = errors.New("redigo: closed") 309 | err = c.conn.Close() 310 | } 311 | c.mu.Unlock() 312 | return err 313 | } 314 | 315 | func (c *conn) fatal(err error) error { 316 | c.mu.Lock() 317 | if c.err == nil { 318 | c.err = err 319 | // Close connection to force errors on subsequent calls and to unblock 320 | // other reader or writer. 321 | c.conn.Close() 322 | } 323 | c.mu.Unlock() 324 | return err 325 | } 326 | 327 | func (c *conn) Err() error { 328 | c.mu.Lock() 329 | err := c.err 330 | c.mu.Unlock() 331 | return err 332 | } 333 | 334 | func (c *conn) writeLen(prefix byte, n int) error { 335 | c.lenScratch[len(c.lenScratch)-1] = '\n' 336 | c.lenScratch[len(c.lenScratch)-2] = '\r' 337 | i := len(c.lenScratch) - 3 338 | for { 339 | c.lenScratch[i] = byte('0' + n%10) 340 | i -= 1 341 | n = n / 10 342 | if n == 0 { 343 | break 344 | } 345 | } 346 | c.lenScratch[i] = prefix 347 | _, err := c.bw.Write(c.lenScratch[i:]) 348 | return err 349 | } 350 | 351 | func (c *conn) writeString(s string) error { 352 | c.writeLen('$', len(s)) 353 | c.bw.WriteString(s) 354 | _, err := c.bw.WriteString("\r\n") 355 | return err 356 | } 357 | 358 | func (c *conn) writeBytes(p []byte) error { 359 | c.writeLen('$', len(p)) 360 | c.bw.Write(p) 361 | _, err := c.bw.WriteString("\r\n") 362 | return err 363 | } 364 | 365 | func (c *conn) writeInt64(n int64) error { 366 | return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) 367 | } 368 | 369 | func (c *conn) writeFloat64(n float64) error { 370 | return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) 371 | } 372 | 373 | func (c *conn) writeCommand(cmd string, args []interface{}) error { 374 | c.writeLen('*', 1+len(args)) 375 | if err := c.writeString(cmd); err != nil { 376 | return err 377 | } 378 | for _, arg := range args { 379 | if err := c.writeArg(arg, true); err != nil { 380 | return err 381 | } 382 | } 383 | return nil 384 | } 385 | 386 | func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { 387 | switch arg := arg.(type) { 388 | case string: 389 | return c.writeString(arg) 390 | case []byte: 391 | return c.writeBytes(arg) 392 | case int: 393 | return c.writeInt64(int64(arg)) 394 | case int64: 395 | return c.writeInt64(arg) 396 | case float64: 397 | return c.writeFloat64(arg) 398 | case bool: 399 | if arg { 400 | return c.writeString("1") 401 | } else { 402 | return c.writeString("0") 403 | } 404 | case nil: 405 | return c.writeString("") 406 | case Argument: 407 | if argumentTypeOK { 408 | return c.writeArg(arg.RedisArg(), false) 409 | } 410 | // See comment in default clause below. 411 | var buf bytes.Buffer 412 | fmt.Fprint(&buf, arg) 413 | return c.writeBytes(buf.Bytes()) 414 | default: 415 | // This default clause is intended to handle builtin numeric types. 416 | // The function should return an error for other types, but this is not 417 | // done for compatibility with previous versions of the package. 418 | var buf bytes.Buffer 419 | fmt.Fprint(&buf, arg) 420 | return c.writeBytes(buf.Bytes()) 421 | } 422 | } 423 | 424 | type protocolError string 425 | 426 | func (pe protocolError) Error() string { 427 | return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) 428 | } 429 | 430 | func (c *conn) readLine() ([]byte, error) { 431 | p, err := c.br.ReadSlice('\n') 432 | if err == bufio.ErrBufferFull { 433 | return nil, protocolError("long response line") 434 | } 435 | if err != nil { 436 | return nil, err 437 | } 438 | i := len(p) - 2 439 | if i < 0 || p[i] != '\r' { 440 | return nil, protocolError("bad response line terminator") 441 | } 442 | return p[:i], nil 443 | } 444 | 445 | // parseLen parses bulk string and array lengths. 446 | func parseLen(p []byte) (int, error) { 447 | if len(p) == 0 { 448 | return -1, protocolError("malformed length") 449 | } 450 | 451 | if p[0] == '-' && len(p) == 2 && p[1] == '1' { 452 | // handle $-1 and $-1 null replies. 453 | return -1, nil 454 | } 455 | 456 | var n int 457 | for _, b := range p { 458 | n *= 10 459 | if b < '0' || b > '9' { 460 | return -1, protocolError("illegal bytes in length") 461 | } 462 | n += int(b - '0') 463 | } 464 | 465 | return n, nil 466 | } 467 | 468 | // parseInt parses an integer reply. 469 | func parseInt(p []byte) (interface{}, error) { 470 | if len(p) == 0 { 471 | return 0, protocolError("malformed integer") 472 | } 473 | 474 | var negate bool 475 | if p[0] == '-' { 476 | negate = true 477 | p = p[1:] 478 | if len(p) == 0 { 479 | return 0, protocolError("malformed integer") 480 | } 481 | } 482 | 483 | var n int64 484 | for _, b := range p { 485 | n *= 10 486 | if b < '0' || b > '9' { 487 | return 0, protocolError("illegal bytes in length") 488 | } 489 | n += int64(b - '0') 490 | } 491 | 492 | if negate { 493 | n = -n 494 | } 495 | return n, nil 496 | } 497 | 498 | var ( 499 | okReply interface{} = "OK" 500 | pongReply interface{} = "PONG" 501 | ) 502 | 503 | func (c *conn) readReply() (interface{}, error) { 504 | line, err := c.readLine() 505 | if err != nil { 506 | return nil, err 507 | } 508 | if len(line) == 0 { 509 | return nil, protocolError("short response line") 510 | } 511 | switch line[0] { 512 | case '+': 513 | switch { 514 | case len(line) == 3 && line[1] == 'O' && line[2] == 'K': 515 | // Avoid allocation for frequent "+OK" response. 516 | return okReply, nil 517 | case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': 518 | // Avoid allocation in PING command benchmarks :) 519 | return pongReply, nil 520 | default: 521 | return string(line[1:]), nil 522 | } 523 | case '-': 524 | return Error(string(line[1:])), nil 525 | case ':': 526 | return parseInt(line[1:]) 527 | case '$': 528 | n, err := parseLen(line[1:]) 529 | if n < 0 || err != nil { 530 | return nil, err 531 | } 532 | p := make([]byte, n) 533 | _, err = io.ReadFull(c.br, p) 534 | if err != nil { 535 | return nil, err 536 | } 537 | if line, err := c.readLine(); err != nil { 538 | return nil, err 539 | } else if len(line) != 0 { 540 | return nil, protocolError("bad bulk string format") 541 | } 542 | return p, nil 543 | case '*': 544 | n, err := parseLen(line[1:]) 545 | if n < 0 || err != nil { 546 | return nil, err 547 | } 548 | r := make([]interface{}, n) 549 | for i := range r { 550 | r[i], err = c.readReply() 551 | if err != nil { 552 | return nil, err 553 | } 554 | } 555 | return r, nil 556 | } 557 | return nil, protocolError("unexpected response line") 558 | } 559 | 560 | func (c *conn) Send(cmd string, args ...interface{}) error { 561 | c.mu.Lock() 562 | c.pending += 1 563 | c.mu.Unlock() 564 | if c.writeTimeout != 0 { 565 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 566 | } 567 | if err := c.writeCommand(cmd, args); err != nil { 568 | return c.fatal(err) 569 | } 570 | return nil 571 | } 572 | 573 | func (c *conn) Flush() error { 574 | if c.writeTimeout != 0 { 575 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 576 | } 577 | if err := c.bw.Flush(); err != nil { 578 | return c.fatal(err) 579 | } 580 | return nil 581 | } 582 | 583 | func (c *conn) Receive() (interface{}, error) { 584 | return c.ReceiveWithTimeout(c.readTimeout) 585 | } 586 | 587 | func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { 588 | var deadline time.Time 589 | if timeout != 0 { 590 | deadline = time.Now().Add(timeout) 591 | } 592 | c.conn.SetReadDeadline(deadline) 593 | 594 | if reply, err = c.readReply(); err != nil { 595 | return nil, c.fatal(err) 596 | } 597 | // When using pub/sub, the number of receives can be greater than the 598 | // number of sends. To enable normal use of the connection after 599 | // unsubscribing from all channels, we do not decrement pending to a 600 | // negative value. 601 | // 602 | // The pending field is decremented after the reply is read to handle the 603 | // case where Receive is called before Send. 604 | c.mu.Lock() 605 | if c.pending > 0 { 606 | c.pending -= 1 607 | } 608 | c.mu.Unlock() 609 | if err, ok := reply.(Error); ok { 610 | return nil, err 611 | } 612 | return 613 | } 614 | 615 | func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { 616 | return c.DoWithTimeout(c.readTimeout, cmd, args...) 617 | } 618 | 619 | func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { 620 | c.mu.Lock() 621 | pending := c.pending 622 | c.pending = 0 623 | c.mu.Unlock() 624 | 625 | if cmd == "" && pending == 0 { 626 | return nil, nil 627 | } 628 | 629 | if c.writeTimeout != 0 { 630 | c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) 631 | } 632 | 633 | if cmd != "" { 634 | if err := c.writeCommand(cmd, args); err != nil { 635 | return nil, c.fatal(err) 636 | } 637 | } 638 | 639 | if err := c.bw.Flush(); err != nil { 640 | return nil, c.fatal(err) 641 | } 642 | 643 | var deadline time.Time 644 | if readTimeout != 0 { 645 | deadline = time.Now().Add(readTimeout) 646 | } 647 | c.conn.SetReadDeadline(deadline) 648 | 649 | if cmd == "" { 650 | reply := make([]interface{}, pending) 651 | for i := range reply { 652 | r, e := c.readReply() 653 | if e != nil { 654 | return nil, c.fatal(e) 655 | } 656 | reply[i] = r 657 | } 658 | return reply, nil 659 | } 660 | 661 | var err error 662 | var reply interface{} 663 | for i := 0; i <= pending; i++ { 664 | var e error 665 | if reply, e = c.readReply(); e != nil { 666 | return nil, c.fatal(e) 667 | } 668 | if e, ok := reply.(Error); ok && err == nil { 669 | err = e 670 | } 671 | } 672 | return reply, err 673 | } 674 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis is a client for the Redis database. 16 | // 17 | // The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more 18 | // documentation about this package. 19 | // 20 | // Connections 21 | // 22 | // The Conn interface is the primary interface for working with Redis. 23 | // Applications create connections by calling the Dial, DialWithTimeout or 24 | // NewConn functions. In the future, functions will be added for creating 25 | // sharded and other types of connections. 26 | // 27 | // The application must call the connection Close method when the application 28 | // is done with the connection. 29 | // 30 | // Executing Commands 31 | // 32 | // The Conn interface has a generic method for executing Redis commands: 33 | // 34 | // Do(commandName string, args ...interface{}) (reply interface{}, err error) 35 | // 36 | // The Redis command reference (http://redis.io/commands) lists the available 37 | // commands. An example of using the Redis APPEND command is: 38 | // 39 | // n, err := conn.Do("APPEND", "key", "value") 40 | // 41 | // The Do method converts command arguments to bulk strings for transmission 42 | // to the server as follows: 43 | // 44 | // Go Type Conversion 45 | // []byte Sent as is 46 | // string Sent as is 47 | // int, int64 strconv.FormatInt(v) 48 | // float64 strconv.FormatFloat(v, 'g', -1, 64) 49 | // bool true -> "1", false -> "0" 50 | // nil "" 51 | // all other types fmt.Fprint(w, v) 52 | // 53 | // Redis command reply types are represented using the following Go types: 54 | // 55 | // Redis type Go type 56 | // error redis.Error 57 | // integer int64 58 | // simple string string 59 | // bulk string []byte or nil if value not present. 60 | // array []interface{} or nil if value not present. 61 | // 62 | // Use type assertions or the reply helper functions to convert from 63 | // interface{} to the specific Go type for the command result. 64 | // 65 | // Pipelining 66 | // 67 | // Connections support pipelining using the Send, Flush and Receive methods. 68 | // 69 | // Send(commandName string, args ...interface{}) error 70 | // Flush() error 71 | // Receive() (reply interface{}, err error) 72 | // 73 | // Send writes the command to the connection's output buffer. Flush flushes the 74 | // connection's output buffer to the server. Receive reads a single reply from 75 | // the server. The following example shows a simple pipeline. 76 | // 77 | // c.Send("SET", "foo", "bar") 78 | // c.Send("GET", "foo") 79 | // c.Flush() 80 | // c.Receive() // reply from SET 81 | // v, err = c.Receive() // reply from GET 82 | // 83 | // The Do method combines the functionality of the Send, Flush and Receive 84 | // methods. The Do method starts by writing the command and flushing the output 85 | // buffer. Next, the Do method receives all pending replies including the reply 86 | // for the command just sent by Do. If any of the received replies is an error, 87 | // then Do returns the error. If there are no errors, then Do returns the last 88 | // reply. If the command argument to the Do method is "", then the Do method 89 | // will flush the output buffer and receive pending replies without sending a 90 | // command. 91 | // 92 | // Use the Send and Do methods to implement pipelined transactions. 93 | // 94 | // c.Send("MULTI") 95 | // c.Send("INCR", "foo") 96 | // c.Send("INCR", "bar") 97 | // r, err := c.Do("EXEC") 98 | // fmt.Println(r) // prints [1, 1] 99 | // 100 | // Concurrency 101 | // 102 | // Connections support one concurrent caller to the Receive method and one 103 | // concurrent caller to the Send and Flush methods. No other concurrency is 104 | // supported including concurrent calls to the Do method. 105 | // 106 | // For full concurrent access to Redis, use the thread-safe Pool to get, use 107 | // and release a connection from within a goroutine. Connections returned from 108 | // a Pool have the concurrency restrictions described in the previous 109 | // paragraph. 110 | // 111 | // Publish and Subscribe 112 | // 113 | // Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. 114 | // 115 | // c.Send("SUBSCRIBE", "example") 116 | // c.Flush() 117 | // for { 118 | // reply, err := c.Receive() 119 | // if err != nil { 120 | // return err 121 | // } 122 | // // process pushed message 123 | // } 124 | // 125 | // The PubSubConn type wraps a Conn with convenience methods for implementing 126 | // subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods 127 | // send and flush a subscription management command. The receive method 128 | // converts a pushed message to convenient types for use in a type switch. 129 | // 130 | // psc := redis.PubSubConn{Conn: c} 131 | // psc.Subscribe("example") 132 | // for { 133 | // switch v := psc.Receive().(type) { 134 | // case redis.Message: 135 | // fmt.Printf("%s: message: %s\n", v.Channel, v.Data) 136 | // case redis.Subscription: 137 | // fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) 138 | // case error: 139 | // return v 140 | // } 141 | // } 142 | // 143 | // Reply Helpers 144 | // 145 | // The Bool, Int, Bytes, String, Strings and Values functions convert a reply 146 | // to a value of a specific type. To allow convenient wrapping of calls to the 147 | // connection Do and Receive methods, the functions take a second argument of 148 | // type error. If the error is non-nil, then the helper function returns the 149 | // error. If the error is nil, the function converts the reply to the specified 150 | // type: 151 | // 152 | // exists, err := redis.Bool(c.Do("EXISTS", "foo")) 153 | // if err != nil { 154 | // // handle error return from c.Do or type conversion error. 155 | // } 156 | // 157 | // The Scan function converts elements of a array reply to Go types: 158 | // 159 | // var value1 int 160 | // var value2 string 161 | // reply, err := redis.Values(c.Do("MGET", "key1", "key2")) 162 | // if err != nil { 163 | // // handle error 164 | // } 165 | // if _, err := redis.Scan(reply, &value1, &value2); err != nil { 166 | // // handle error 167 | // } 168 | // 169 | // Errors 170 | // 171 | // Connection methods return error replies from the server as type redis.Error. 172 | // 173 | // Call the connection Err() method to determine if the connection encountered 174 | // non-recoverable error such as a network error or protocol parsing error. If 175 | // Err() returns a non-nil value, then the connection is not usable and should 176 | // be closed. 177 | package redis // import "github.com/gomodule/redigo/redis" 178 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/go16.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 8 | return &tls.Config{ 9 | Rand: cfg.Rand, 10 | Time: cfg.Time, 11 | Certificates: cfg.Certificates, 12 | NameToCertificate: cfg.NameToCertificate, 13 | GetCertificate: cfg.GetCertificate, 14 | RootCAs: cfg.RootCAs, 15 | NextProtos: cfg.NextProtos, 16 | ServerName: cfg.ServerName, 17 | ClientAuth: cfg.ClientAuth, 18 | ClientCAs: cfg.ClientCAs, 19 | InsecureSkipVerify: cfg.InsecureSkipVerify, 20 | CipherSuites: cfg.CipherSuites, 21 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 22 | ClientSessionCache: cfg.ClientSessionCache, 23 | MinVersion: cfg.MinVersion, 24 | MaxVersion: cfg.MaxVersion, 25 | CurvePreferences: cfg.CurvePreferences, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/go17.go: -------------------------------------------------------------------------------- 1 | // +build go1.7,!go1.8 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 8 | return &tls.Config{ 9 | Rand: cfg.Rand, 10 | Time: cfg.Time, 11 | Certificates: cfg.Certificates, 12 | NameToCertificate: cfg.NameToCertificate, 13 | GetCertificate: cfg.GetCertificate, 14 | RootCAs: cfg.RootCAs, 15 | NextProtos: cfg.NextProtos, 16 | ServerName: cfg.ServerName, 17 | ClientAuth: cfg.ClientAuth, 18 | ClientCAs: cfg.ClientCAs, 19 | InsecureSkipVerify: cfg.InsecureSkipVerify, 20 | CipherSuites: cfg.CipherSuites, 21 | PreferServerCipherSuites: cfg.PreferServerCipherSuites, 22 | ClientSessionCache: cfg.ClientSessionCache, 23 | MinVersion: cfg.MinVersion, 24 | MaxVersion: cfg.MaxVersion, 25 | CurvePreferences: cfg.CurvePreferences, 26 | DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, 27 | Renegotiation: cfg.Renegotiation, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/go18.go: -------------------------------------------------------------------------------- 1 | // +build go1.8 2 | 3 | package redis 4 | 5 | import "crypto/tls" 6 | 7 | func cloneTLSConfig(cfg *tls.Config) *tls.Config { 8 | return cfg.Clone() 9 | } 10 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "log" 21 | "time" 22 | ) 23 | 24 | var ( 25 | _ ConnWithTimeout = (*loggingConn)(nil) 26 | ) 27 | 28 | // NewLoggingConn returns a logging wrapper around a connection. 29 | func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { 30 | if prefix != "" { 31 | prefix = prefix + "." 32 | } 33 | return &loggingConn{conn, logger, prefix} 34 | } 35 | 36 | type loggingConn struct { 37 | Conn 38 | logger *log.Logger 39 | prefix string 40 | } 41 | 42 | func (c *loggingConn) Close() error { 43 | err := c.Conn.Close() 44 | var buf bytes.Buffer 45 | fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) 46 | c.logger.Output(2, buf.String()) 47 | return err 48 | } 49 | 50 | func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { 51 | const chop = 32 52 | switch v := v.(type) { 53 | case []byte: 54 | if len(v) > chop { 55 | fmt.Fprintf(buf, "%q...", v[:chop]) 56 | } else { 57 | fmt.Fprintf(buf, "%q", v) 58 | } 59 | case string: 60 | if len(v) > chop { 61 | fmt.Fprintf(buf, "%q...", v[:chop]) 62 | } else { 63 | fmt.Fprintf(buf, "%q", v) 64 | } 65 | case []interface{}: 66 | if len(v) == 0 { 67 | buf.WriteString("[]") 68 | } else { 69 | sep := "[" 70 | fin := "]" 71 | if len(v) > chop { 72 | v = v[:chop] 73 | fin = "...]" 74 | } 75 | for _, vv := range v { 76 | buf.WriteString(sep) 77 | c.printValue(buf, vv) 78 | sep = ", " 79 | } 80 | buf.WriteString(fin) 81 | } 82 | default: 83 | fmt.Fprint(buf, v) 84 | } 85 | } 86 | 87 | func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { 88 | var buf bytes.Buffer 89 | fmt.Fprintf(&buf, "%s%s(", c.prefix, method) 90 | if method != "Receive" { 91 | buf.WriteString(commandName) 92 | for _, arg := range args { 93 | buf.WriteString(", ") 94 | c.printValue(&buf, arg) 95 | } 96 | } 97 | buf.WriteString(") -> (") 98 | if method != "Send" { 99 | c.printValue(&buf, reply) 100 | buf.WriteString(", ") 101 | } 102 | fmt.Fprintf(&buf, "%v)", err) 103 | c.logger.Output(3, buf.String()) 104 | } 105 | 106 | func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { 107 | reply, err := c.Conn.Do(commandName, args...) 108 | c.print("Do", commandName, args, reply, err) 109 | return reply, err 110 | } 111 | 112 | func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { 113 | reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) 114 | c.print("DoWithTimeout", commandName, args, reply, err) 115 | return reply, err 116 | } 117 | 118 | func (c *loggingConn) Send(commandName string, args ...interface{}) error { 119 | err := c.Conn.Send(commandName, args...) 120 | c.print("Send", commandName, args, nil, err) 121 | return err 122 | } 123 | 124 | func (c *loggingConn) Receive() (interface{}, error) { 125 | reply, err := c.Conn.Receive() 126 | c.print("Receive", "", nil, reply, err) 127 | return reply, err 128 | } 129 | 130 | func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { 131 | reply, err := ReceiveWithTimeout(c.Conn, timeout) 132 | c.print("ReceiveWithTimeout", "", nil, reply, err) 133 | return reply, err 134 | } 135 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/pool.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "bytes" 19 | "crypto/rand" 20 | "crypto/sha1" 21 | "errors" 22 | "io" 23 | "strconv" 24 | "sync" 25 | "sync/atomic" 26 | "time" 27 | 28 | "github.com/gomodule/redigo/internal" 29 | ) 30 | 31 | var ( 32 | _ ConnWithTimeout = (*activeConn)(nil) 33 | _ ConnWithTimeout = (*errorConn)(nil) 34 | ) 35 | 36 | var nowFunc = time.Now // for testing 37 | 38 | // ErrPoolExhausted is returned from a pool connection method (Do, Send, 39 | // Receive, Flush, Err) when the maximum number of database connections in the 40 | // pool has been reached. 41 | var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") 42 | 43 | var ( 44 | errPoolClosed = errors.New("redigo: connection pool closed") 45 | errConnClosed = errors.New("redigo: connection closed") 46 | ) 47 | 48 | // Pool maintains a pool of connections. The application calls the Get method 49 | // to get a connection from the pool and the connection's Close method to 50 | // return the connection's resources to the pool. 51 | // 52 | // The following example shows how to use a pool in a web application. The 53 | // application creates a pool at application startup and makes it available to 54 | // request handlers using a package level variable. The pool configuration used 55 | // here is an example, not a recommendation. 56 | // 57 | // func newPool(addr string) *redis.Pool { 58 | // return &redis.Pool{ 59 | // MaxIdle: 3, 60 | // IdleTimeout: 240 * time.Second, 61 | // Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, 62 | // } 63 | // } 64 | // 65 | // var ( 66 | // pool *redis.Pool 67 | // redisServer = flag.String("redisServer", ":6379", "") 68 | // ) 69 | // 70 | // func main() { 71 | // flag.Parse() 72 | // pool = newPool(*redisServer) 73 | // ... 74 | // } 75 | // 76 | // A request handler gets a connection from the pool and closes the connection 77 | // when the handler is done: 78 | // 79 | // func serveHome(w http.ResponseWriter, r *http.Request) { 80 | // conn := pool.Get() 81 | // defer conn.Close() 82 | // ... 83 | // } 84 | // 85 | // Use the Dial function to authenticate connections with the AUTH command or 86 | // select a database with the SELECT command: 87 | // 88 | // pool := &redis.Pool{ 89 | // // Other pool configuration not shown in this example. 90 | // Dial: func () (redis.Conn, error) { 91 | // c, err := redis.Dial("tcp", server) 92 | // if err != nil { 93 | // return nil, err 94 | // } 95 | // if _, err := c.Do("AUTH", password); err != nil { 96 | // c.Close() 97 | // return nil, err 98 | // } 99 | // if _, err := c.Do("SELECT", db); err != nil { 100 | // c.Close() 101 | // return nil, err 102 | // } 103 | // return c, nil 104 | // }, 105 | // } 106 | // 107 | // Use the TestOnBorrow function to check the health of an idle connection 108 | // before the connection is returned to the application. This example PINGs 109 | // connections that have been idle more than a minute: 110 | // 111 | // pool := &redis.Pool{ 112 | // // Other pool configuration not shown in this example. 113 | // TestOnBorrow: func(c redis.Conn, t time.Time) error { 114 | // if time.Since(t) < time.Minute { 115 | // return nil 116 | // } 117 | // _, err := c.Do("PING") 118 | // return err 119 | // }, 120 | // } 121 | // 122 | type Pool struct { 123 | // Dial is an application supplied function for creating and configuring a 124 | // connection. 125 | // 126 | // The connection returned from Dial must not be in a special state 127 | // (subscribed to pubsub channel, transaction started, ...). 128 | Dial func() (Conn, error) 129 | 130 | // TestOnBorrow is an optional application supplied function for checking 131 | // the health of an idle connection before the connection is used again by 132 | // the application. Argument t is the time that the connection was returned 133 | // to the pool. If the function returns an error, then the connection is 134 | // closed. 135 | TestOnBorrow func(c Conn, t time.Time) error 136 | 137 | // Maximum number of idle connections in the pool. 138 | MaxIdle int 139 | 140 | // Maximum number of connections allocated by the pool at a given time. 141 | // When zero, there is no limit on the number of connections in the pool. 142 | MaxActive int 143 | 144 | // Close connections after remaining idle for this duration. If the value 145 | // is zero, then idle connections are not closed. Applications should set 146 | // the timeout to a value less than the server's timeout. 147 | IdleTimeout time.Duration 148 | 149 | // If Wait is true and the pool is at the MaxActive limit, then Get() waits 150 | // for a connection to be returned to the pool before returning. 151 | Wait bool 152 | 153 | // Close connections older than this duration. If the value is zero, then 154 | // the pool does not close connections based on age. 155 | MaxConnLifetime time.Duration 156 | 157 | chInitialized uint32 // set to 1 when field ch is initialized 158 | 159 | mu sync.Mutex // mu protects the following fields 160 | closed bool // set to true when the pool is closed. 161 | active int // the number of open connections in the pool 162 | ch chan struct{} // limits open connections when p.Wait is true 163 | idle idleList // idle connections 164 | } 165 | 166 | // NewPool creates a new pool. 167 | // 168 | // Deprecated: Initialize the Pool directory as shown in the example. 169 | func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { 170 | return &Pool{Dial: newFn, MaxIdle: maxIdle} 171 | } 172 | 173 | // Get gets a connection. The application must close the returned connection. 174 | // This method always returns a valid connection so that applications can defer 175 | // error handling to the first use of the connection. If there is an error 176 | // getting an underlying connection, then the connection Err, Do, Send, Flush 177 | // and Receive methods return that error. 178 | func (p *Pool) Get() Conn { 179 | pc, err := p.get(nil) 180 | if err != nil { 181 | return errorConn{err} 182 | } 183 | return &activeConn{p: p, pc: pc} 184 | } 185 | 186 | // PoolStats contains pool statistics. 187 | type PoolStats struct { 188 | // ActiveCount is the number of connections in the pool. The count includes 189 | // idle connections and connections in use. 190 | ActiveCount int 191 | // IdleCount is the number of idle connections in the pool. 192 | IdleCount int 193 | } 194 | 195 | // Stats returns pool's statistics. 196 | func (p *Pool) Stats() PoolStats { 197 | p.mu.Lock() 198 | stats := PoolStats{ 199 | ActiveCount: p.active, 200 | IdleCount: p.idle.count, 201 | } 202 | p.mu.Unlock() 203 | 204 | return stats 205 | } 206 | 207 | // ActiveCount returns the number of connections in the pool. The count 208 | // includes idle connections and connections in use. 209 | func (p *Pool) ActiveCount() int { 210 | p.mu.Lock() 211 | active := p.active 212 | p.mu.Unlock() 213 | return active 214 | } 215 | 216 | // IdleCount returns the number of idle connections in the pool. 217 | func (p *Pool) IdleCount() int { 218 | p.mu.Lock() 219 | idle := p.idle.count 220 | p.mu.Unlock() 221 | return idle 222 | } 223 | 224 | // Close releases the resources used by the pool. 225 | func (p *Pool) Close() error { 226 | p.mu.Lock() 227 | if p.closed { 228 | p.mu.Unlock() 229 | return nil 230 | } 231 | p.closed = true 232 | p.active -= p.idle.count 233 | pc := p.idle.front 234 | p.idle.count = 0 235 | p.idle.front, p.idle.back = nil, nil 236 | if p.ch != nil { 237 | close(p.ch) 238 | } 239 | p.mu.Unlock() 240 | for ; pc != nil; pc = pc.next { 241 | pc.c.Close() 242 | } 243 | return nil 244 | } 245 | 246 | func (p *Pool) lazyInit() { 247 | // Fast path. 248 | if atomic.LoadUint32(&p.chInitialized) == 1 { 249 | return 250 | } 251 | // Slow path. 252 | p.mu.Lock() 253 | if p.chInitialized == 0 { 254 | p.ch = make(chan struct{}, p.MaxActive) 255 | if p.closed { 256 | close(p.ch) 257 | } else { 258 | for i := 0; i < p.MaxActive; i++ { 259 | p.ch <- struct{}{} 260 | } 261 | } 262 | atomic.StoreUint32(&p.chInitialized, 1) 263 | } 264 | p.mu.Unlock() 265 | } 266 | 267 | // get prunes stale connections and returns a connection from the idle list or 268 | // creates a new connection. 269 | func (p *Pool) get(ctx interface { 270 | Done() <-chan struct{} 271 | Err() error 272 | }) (*poolConn, error) { 273 | 274 | // Handle limit for p.Wait == true. 275 | if p.Wait && p.MaxActive > 0 { 276 | p.lazyInit() 277 | if ctx == nil { 278 | <-p.ch 279 | } else { 280 | select { 281 | case <-p.ch: 282 | case <-ctx.Done(): 283 | return nil, ctx.Err() 284 | } 285 | } 286 | } 287 | 288 | p.mu.Lock() 289 | 290 | // Prune stale connections at the back of the idle list. 291 | if p.IdleTimeout > 0 { 292 | n := p.idle.count 293 | for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { 294 | pc := p.idle.back 295 | p.idle.popBack() 296 | p.mu.Unlock() 297 | pc.c.Close() 298 | p.mu.Lock() 299 | p.active-- 300 | } 301 | } 302 | 303 | // Get idle connection from the front of idle list. 304 | for p.idle.front != nil { 305 | pc := p.idle.front 306 | p.idle.popFront() 307 | p.mu.Unlock() 308 | if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && 309 | (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { 310 | return pc, nil 311 | } 312 | pc.c.Close() 313 | p.mu.Lock() 314 | p.active-- 315 | } 316 | 317 | // Check for pool closed before dialing a new connection. 318 | if p.closed { 319 | p.mu.Unlock() 320 | return nil, errors.New("redigo: get on closed pool") 321 | } 322 | 323 | // Handle limit for p.Wait == false. 324 | if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { 325 | p.mu.Unlock() 326 | return nil, ErrPoolExhausted 327 | } 328 | 329 | p.active++ 330 | p.mu.Unlock() 331 | c, err := p.Dial() 332 | if err != nil { 333 | c = nil 334 | p.mu.Lock() 335 | p.active-- 336 | if p.ch != nil && !p.closed { 337 | p.ch <- struct{}{} 338 | } 339 | p.mu.Unlock() 340 | } 341 | return &poolConn{c: c, created: nowFunc()}, err 342 | } 343 | 344 | func (p *Pool) put(pc *poolConn, forceClose bool) error { 345 | p.mu.Lock() 346 | if !p.closed && !forceClose { 347 | pc.t = nowFunc() 348 | p.idle.pushFront(pc) 349 | if p.idle.count > p.MaxIdle { 350 | pc = p.idle.back 351 | p.idle.popBack() 352 | } else { 353 | pc = nil 354 | } 355 | } 356 | 357 | if pc != nil { 358 | p.mu.Unlock() 359 | pc.c.Close() 360 | p.mu.Lock() 361 | p.active-- 362 | } 363 | 364 | if p.ch != nil && !p.closed { 365 | p.ch <- struct{}{} 366 | } 367 | p.mu.Unlock() 368 | return nil 369 | } 370 | 371 | type activeConn struct { 372 | p *Pool 373 | pc *poolConn 374 | state int 375 | } 376 | 377 | var ( 378 | sentinel []byte 379 | sentinelOnce sync.Once 380 | ) 381 | 382 | func initSentinel() { 383 | p := make([]byte, 64) 384 | if _, err := rand.Read(p); err == nil { 385 | sentinel = p 386 | } else { 387 | h := sha1.New() 388 | io.WriteString(h, "Oops, rand failed. Use time instead.") 389 | io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) 390 | sentinel = h.Sum(nil) 391 | } 392 | } 393 | 394 | func (ac *activeConn) Close() error { 395 | pc := ac.pc 396 | if pc == nil { 397 | return nil 398 | } 399 | ac.pc = nil 400 | 401 | if ac.state&internal.MultiState != 0 { 402 | pc.c.Send("DISCARD") 403 | ac.state &^= (internal.MultiState | internal.WatchState) 404 | } else if ac.state&internal.WatchState != 0 { 405 | pc.c.Send("UNWATCH") 406 | ac.state &^= internal.WatchState 407 | } 408 | if ac.state&internal.SubscribeState != 0 { 409 | pc.c.Send("UNSUBSCRIBE") 410 | pc.c.Send("PUNSUBSCRIBE") 411 | // To detect the end of the message stream, ask the server to echo 412 | // a sentinel value and read until we see that value. 413 | sentinelOnce.Do(initSentinel) 414 | pc.c.Send("ECHO", sentinel) 415 | pc.c.Flush() 416 | for { 417 | p, err := pc.c.Receive() 418 | if err != nil { 419 | break 420 | } 421 | if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { 422 | ac.state &^= internal.SubscribeState 423 | break 424 | } 425 | } 426 | } 427 | pc.c.Do("") 428 | ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil) 429 | return nil 430 | } 431 | 432 | func (ac *activeConn) Err() error { 433 | pc := ac.pc 434 | if pc == nil { 435 | return errConnClosed 436 | } 437 | return pc.c.Err() 438 | } 439 | 440 | func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { 441 | pc := ac.pc 442 | if pc == nil { 443 | return nil, errConnClosed 444 | } 445 | ci := internal.LookupCommandInfo(commandName) 446 | ac.state = (ac.state | ci.Set) &^ ci.Clear 447 | return pc.c.Do(commandName, args...) 448 | } 449 | 450 | func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { 451 | pc := ac.pc 452 | if pc == nil { 453 | return nil, errConnClosed 454 | } 455 | cwt, ok := pc.c.(ConnWithTimeout) 456 | if !ok { 457 | return nil, errTimeoutNotSupported 458 | } 459 | ci := internal.LookupCommandInfo(commandName) 460 | ac.state = (ac.state | ci.Set) &^ ci.Clear 461 | return cwt.DoWithTimeout(timeout, commandName, args...) 462 | } 463 | 464 | func (ac *activeConn) Send(commandName string, args ...interface{}) error { 465 | pc := ac.pc 466 | if pc == nil { 467 | return errConnClosed 468 | } 469 | ci := internal.LookupCommandInfo(commandName) 470 | ac.state = (ac.state | ci.Set) &^ ci.Clear 471 | return pc.c.Send(commandName, args...) 472 | } 473 | 474 | func (ac *activeConn) Flush() error { 475 | pc := ac.pc 476 | if pc == nil { 477 | return errConnClosed 478 | } 479 | return pc.c.Flush() 480 | } 481 | 482 | func (ac *activeConn) Receive() (reply interface{}, err error) { 483 | pc := ac.pc 484 | if pc == nil { 485 | return nil, errConnClosed 486 | } 487 | return pc.c.Receive() 488 | } 489 | 490 | func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { 491 | pc := ac.pc 492 | if pc == nil { 493 | return nil, errConnClosed 494 | } 495 | cwt, ok := pc.c.(ConnWithTimeout) 496 | if !ok { 497 | return nil, errTimeoutNotSupported 498 | } 499 | return cwt.ReceiveWithTimeout(timeout) 500 | } 501 | 502 | type errorConn struct{ err error } 503 | 504 | func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } 505 | func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { 506 | return nil, ec.err 507 | } 508 | func (ec errorConn) Send(string, ...interface{}) error { return ec.err } 509 | func (ec errorConn) Err() error { return ec.err } 510 | func (ec errorConn) Close() error { return nil } 511 | func (ec errorConn) Flush() error { return ec.err } 512 | func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } 513 | func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } 514 | 515 | type idleList struct { 516 | count int 517 | front, back *poolConn 518 | } 519 | 520 | type poolConn struct { 521 | c Conn 522 | t time.Time 523 | created time.Time 524 | next, prev *poolConn 525 | } 526 | 527 | func (l *idleList) pushFront(pc *poolConn) { 528 | pc.next = l.front 529 | pc.prev = nil 530 | if l.count == 0 { 531 | l.back = pc 532 | } else { 533 | l.front.prev = pc 534 | } 535 | l.front = pc 536 | l.count++ 537 | return 538 | } 539 | 540 | func (l *idleList) popFront() { 541 | pc := l.front 542 | l.count-- 543 | if l.count == 0 { 544 | l.front, l.back = nil, nil 545 | } else { 546 | pc.next.prev = nil 547 | l.front = pc.next 548 | } 549 | pc.next, pc.prev = nil, nil 550 | } 551 | 552 | func (l *idleList) popBack() { 553 | pc := l.back 554 | l.count-- 555 | if l.count == 0 { 556 | l.front, l.back = nil, nil 557 | } else { 558 | pc.prev.next = nil 559 | l.back = pc.prev 560 | } 561 | pc.next, pc.prev = nil, nil 562 | } 563 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/pool17.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Gary Burd 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 | // +build go1.7 16 | 17 | package redis 18 | 19 | import "context" 20 | 21 | // GetContext gets a connection using the provided context. 22 | // 23 | // The provided Context must be non-nil. If the context expires before the 24 | // connection is complete, an error is returned. Any expiration on the context 25 | // will not affect the returned connection. 26 | // 27 | // If the function completes without error, then the application must close the 28 | // returned connection. 29 | func (p *Pool) GetContext(ctx context.Context) (Conn, error) { 30 | pc, err := p.get(ctx) 31 | if err != nil { 32 | return errorConn{err}, err 33 | } 34 | return &activeConn{p: p, pc: pc}, nil 35 | } 36 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/pubsub.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "errors" 19 | "time" 20 | ) 21 | 22 | // Subscription represents a subscribe or unsubscribe notification. 23 | type Subscription struct { 24 | // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" 25 | Kind string 26 | 27 | // The channel that was changed. 28 | Channel string 29 | 30 | // The current number of subscriptions for connection. 31 | Count int 32 | } 33 | 34 | // Message represents a message notification. 35 | type Message struct { 36 | // The originating channel. 37 | Channel string 38 | 39 | // The matched pattern, if any 40 | Pattern string 41 | 42 | // The message data. 43 | Data []byte 44 | } 45 | 46 | // Pong represents a pubsub pong notification. 47 | type Pong struct { 48 | Data string 49 | } 50 | 51 | // PubSubConn wraps a Conn with convenience methods for subscribers. 52 | type PubSubConn struct { 53 | Conn Conn 54 | } 55 | 56 | // Close closes the connection. 57 | func (c PubSubConn) Close() error { 58 | return c.Conn.Close() 59 | } 60 | 61 | // Subscribe subscribes the connection to the specified channels. 62 | func (c PubSubConn) Subscribe(channel ...interface{}) error { 63 | c.Conn.Send("SUBSCRIBE", channel...) 64 | return c.Conn.Flush() 65 | } 66 | 67 | // PSubscribe subscribes the connection to the given patterns. 68 | func (c PubSubConn) PSubscribe(channel ...interface{}) error { 69 | c.Conn.Send("PSUBSCRIBE", channel...) 70 | return c.Conn.Flush() 71 | } 72 | 73 | // Unsubscribe unsubscribes the connection from the given channels, or from all 74 | // of them if none is given. 75 | func (c PubSubConn) Unsubscribe(channel ...interface{}) error { 76 | c.Conn.Send("UNSUBSCRIBE", channel...) 77 | return c.Conn.Flush() 78 | } 79 | 80 | // PUnsubscribe unsubscribes the connection from the given patterns, or from all 81 | // of them if none is given. 82 | func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { 83 | c.Conn.Send("PUNSUBSCRIBE", channel...) 84 | return c.Conn.Flush() 85 | } 86 | 87 | // Ping sends a PING to the server with the specified data. 88 | // 89 | // The connection must be subscribed to at least one channel or pattern when 90 | // calling this method. 91 | func (c PubSubConn) Ping(data string) error { 92 | c.Conn.Send("PING", data) 93 | return c.Conn.Flush() 94 | } 95 | 96 | // Receive returns a pushed message as a Subscription, Message, Pong or error. 97 | // The return value is intended to be used directly in a type switch as 98 | // illustrated in the PubSubConn example. 99 | func (c PubSubConn) Receive() interface{} { 100 | return c.receiveInternal(c.Conn.Receive()) 101 | } 102 | 103 | // ReceiveWithTimeout is like Receive, but it allows the application to 104 | // override the connection's default timeout. 105 | func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { 106 | return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) 107 | } 108 | 109 | func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { 110 | reply, err := Values(replyArg, errArg) 111 | if err != nil { 112 | return err 113 | } 114 | 115 | var kind string 116 | reply, err = Scan(reply, &kind) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | switch kind { 122 | case "message": 123 | var m Message 124 | if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { 125 | return err 126 | } 127 | return m 128 | case "pmessage": 129 | var m Message 130 | if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { 131 | return err 132 | } 133 | return m 134 | case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": 135 | s := Subscription{Kind: kind} 136 | if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { 137 | return err 138 | } 139 | return s 140 | case "pong": 141 | var p Pong 142 | if _, err := Scan(reply, &p.Data); err != nil { 143 | return err 144 | } 145 | return p 146 | } 147 | return errors.New("redigo: unknown pubsub notification") 148 | } 149 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "errors" 19 | "time" 20 | ) 21 | 22 | // Error represents an error returned in a command reply. 23 | type Error string 24 | 25 | func (err Error) Error() string { return string(err) } 26 | 27 | // Conn represents a connection to a Redis server. 28 | type Conn interface { 29 | // Close closes the connection. 30 | Close() error 31 | 32 | // Err returns a non-nil value when the connection is not usable. 33 | Err() error 34 | 35 | // Do sends a command to the server and returns the received reply. 36 | Do(commandName string, args ...interface{}) (reply interface{}, err error) 37 | 38 | // Send writes the command to the client's output buffer. 39 | Send(commandName string, args ...interface{}) error 40 | 41 | // Flush flushes the output buffer to the Redis server. 42 | Flush() error 43 | 44 | // Receive receives a single reply from the Redis server 45 | Receive() (reply interface{}, err error) 46 | } 47 | 48 | // Argument is the interface implemented by an object which wants to control how 49 | // the object is converted to Redis bulk strings. 50 | type Argument interface { 51 | // RedisArg returns a value to be encoded as a bulk string per the 52 | // conversions listed in the section 'Executing Commands'. 53 | // Implementations should typically return a []byte or string. 54 | RedisArg() interface{} 55 | } 56 | 57 | // Scanner is implemented by an object which wants to control its value is 58 | // interpreted when read from Redis. 59 | type Scanner interface { 60 | // RedisScan assigns a value from a Redis value. The argument src is one of 61 | // the reply types listed in the section `Executing Commands`. 62 | // 63 | // An error should be returned if the value cannot be stored without 64 | // loss of information. 65 | RedisScan(src interface{}) error 66 | } 67 | 68 | // ConnWithTimeout is an optional interface that allows the caller to override 69 | // a connection's default read timeout. This interface is useful for executing 70 | // the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the 71 | // server. 72 | // 73 | // A connection's default read timeout is set with the DialReadTimeout dial 74 | // option. Applications should rely on the default timeout for commands that do 75 | // not block at the server. 76 | // 77 | // All of the Conn implementations in this package satisfy the ConnWithTimeout 78 | // interface. 79 | // 80 | // Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify 81 | // use of this interface. 82 | type ConnWithTimeout interface { 83 | Conn 84 | 85 | // Do sends a command to the server and returns the received reply. 86 | // The timeout overrides the read timeout set when dialing the 87 | // connection. 88 | DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) 89 | 90 | // Receive receives a single reply from the Redis server. The timeout 91 | // overrides the read timeout set when dialing the connection. 92 | ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) 93 | } 94 | 95 | var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") 96 | 97 | // DoWithTimeout executes a Redis command with the specified read timeout. If 98 | // the connection does not satisfy the ConnWithTimeout interface, then an error 99 | // is returned. 100 | func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { 101 | cwt, ok := c.(ConnWithTimeout) 102 | if !ok { 103 | return nil, errTimeoutNotSupported 104 | } 105 | return cwt.DoWithTimeout(timeout, cmd, args...) 106 | } 107 | 108 | // ReceiveWithTimeout receives a reply with the specified read timeout. If the 109 | // connection does not satisfy the ConnWithTimeout interface, then an error is 110 | // returned. 111 | func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { 112 | cwt, ok := c.(ConnWithTimeout) 113 | if !ok { 114 | return nil, errTimeoutNotSupported 115 | } 116 | return cwt.ReceiveWithTimeout(timeout) 117 | } 118 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/reply.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "strconv" 21 | ) 22 | 23 | // ErrNil indicates that a reply value is nil. 24 | var ErrNil = errors.New("redigo: nil returned") 25 | 26 | // Int is a helper that converts a command reply to an integer. If err is not 27 | // equal to nil, then Int returns 0, err. Otherwise, Int converts the 28 | // reply to an int as follows: 29 | // 30 | // Reply type Result 31 | // integer int(reply), nil 32 | // bulk string parsed reply, nil 33 | // nil 0, ErrNil 34 | // other 0, error 35 | func Int(reply interface{}, err error) (int, error) { 36 | if err != nil { 37 | return 0, err 38 | } 39 | switch reply := reply.(type) { 40 | case int64: 41 | x := int(reply) 42 | if int64(x) != reply { 43 | return 0, strconv.ErrRange 44 | } 45 | return x, nil 46 | case []byte: 47 | n, err := strconv.ParseInt(string(reply), 10, 0) 48 | return int(n), err 49 | case nil: 50 | return 0, ErrNil 51 | case Error: 52 | return 0, reply 53 | } 54 | return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) 55 | } 56 | 57 | // Int64 is a helper that converts a command reply to 64 bit integer. If err is 58 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 59 | // reply to an int64 as follows: 60 | // 61 | // Reply type Result 62 | // integer reply, nil 63 | // bulk string parsed reply, nil 64 | // nil 0, ErrNil 65 | // other 0, error 66 | func Int64(reply interface{}, err error) (int64, error) { 67 | if err != nil { 68 | return 0, err 69 | } 70 | switch reply := reply.(type) { 71 | case int64: 72 | return reply, nil 73 | case []byte: 74 | n, err := strconv.ParseInt(string(reply), 10, 64) 75 | return n, err 76 | case nil: 77 | return 0, ErrNil 78 | case Error: 79 | return 0, reply 80 | } 81 | return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) 82 | } 83 | 84 | var errNegativeInt = errors.New("redigo: unexpected value for Uint64") 85 | 86 | // Uint64 is a helper that converts a command reply to 64 bit integer. If err is 87 | // not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the 88 | // reply to an int64 as follows: 89 | // 90 | // Reply type Result 91 | // integer reply, nil 92 | // bulk string parsed reply, nil 93 | // nil 0, ErrNil 94 | // other 0, error 95 | func Uint64(reply interface{}, err error) (uint64, error) { 96 | if err != nil { 97 | return 0, err 98 | } 99 | switch reply := reply.(type) { 100 | case int64: 101 | if reply < 0 { 102 | return 0, errNegativeInt 103 | } 104 | return uint64(reply), nil 105 | case []byte: 106 | n, err := strconv.ParseUint(string(reply), 10, 64) 107 | return n, err 108 | case nil: 109 | return 0, ErrNil 110 | case Error: 111 | return 0, reply 112 | } 113 | return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) 114 | } 115 | 116 | // Float64 is a helper that converts a command reply to 64 bit float. If err is 117 | // not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts 118 | // the reply to an int as follows: 119 | // 120 | // Reply type Result 121 | // bulk string parsed reply, nil 122 | // nil 0, ErrNil 123 | // other 0, error 124 | func Float64(reply interface{}, err error) (float64, error) { 125 | if err != nil { 126 | return 0, err 127 | } 128 | switch reply := reply.(type) { 129 | case []byte: 130 | n, err := strconv.ParseFloat(string(reply), 64) 131 | return n, err 132 | case nil: 133 | return 0, ErrNil 134 | case Error: 135 | return 0, reply 136 | } 137 | return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) 138 | } 139 | 140 | // String is a helper that converts a command reply to a string. If err is not 141 | // equal to nil, then String returns "", err. Otherwise String converts the 142 | // reply to a string as follows: 143 | // 144 | // Reply type Result 145 | // bulk string string(reply), nil 146 | // simple string reply, nil 147 | // nil "", ErrNil 148 | // other "", error 149 | func String(reply interface{}, err error) (string, error) { 150 | if err != nil { 151 | return "", err 152 | } 153 | switch reply := reply.(type) { 154 | case []byte: 155 | return string(reply), nil 156 | case string: 157 | return reply, nil 158 | case nil: 159 | return "", ErrNil 160 | case Error: 161 | return "", reply 162 | } 163 | return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) 164 | } 165 | 166 | // Bytes is a helper that converts a command reply to a slice of bytes. If err 167 | // is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts 168 | // the reply to a slice of bytes as follows: 169 | // 170 | // Reply type Result 171 | // bulk string reply, nil 172 | // simple string []byte(reply), nil 173 | // nil nil, ErrNil 174 | // other nil, error 175 | func Bytes(reply interface{}, err error) ([]byte, error) { 176 | if err != nil { 177 | return nil, err 178 | } 179 | switch reply := reply.(type) { 180 | case []byte: 181 | return reply, nil 182 | case string: 183 | return []byte(reply), nil 184 | case nil: 185 | return nil, ErrNil 186 | case Error: 187 | return nil, reply 188 | } 189 | return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) 190 | } 191 | 192 | // Bool is a helper that converts a command reply to a boolean. If err is not 193 | // equal to nil, then Bool returns false, err. Otherwise Bool converts the 194 | // reply to boolean as follows: 195 | // 196 | // Reply type Result 197 | // integer value != 0, nil 198 | // bulk string strconv.ParseBool(reply) 199 | // nil false, ErrNil 200 | // other false, error 201 | func Bool(reply interface{}, err error) (bool, error) { 202 | if err != nil { 203 | return false, err 204 | } 205 | switch reply := reply.(type) { 206 | case int64: 207 | return reply != 0, nil 208 | case []byte: 209 | return strconv.ParseBool(string(reply)) 210 | case nil: 211 | return false, ErrNil 212 | case Error: 213 | return false, reply 214 | } 215 | return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) 216 | } 217 | 218 | // MultiBulk is a helper that converts an array command reply to a []interface{}. 219 | // 220 | // Deprecated: Use Values instead. 221 | func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } 222 | 223 | // Values is a helper that converts an array command reply to a []interface{}. 224 | // If err is not equal to nil, then Values returns nil, err. Otherwise, Values 225 | // converts the reply as follows: 226 | // 227 | // Reply type Result 228 | // array reply, nil 229 | // nil nil, ErrNil 230 | // other nil, error 231 | func Values(reply interface{}, err error) ([]interface{}, error) { 232 | if err != nil { 233 | return nil, err 234 | } 235 | switch reply := reply.(type) { 236 | case []interface{}: 237 | return reply, nil 238 | case nil: 239 | return nil, ErrNil 240 | case Error: 241 | return nil, reply 242 | } 243 | return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) 244 | } 245 | 246 | func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { 247 | if err != nil { 248 | return err 249 | } 250 | switch reply := reply.(type) { 251 | case []interface{}: 252 | makeSlice(len(reply)) 253 | for i := range reply { 254 | if reply[i] == nil { 255 | continue 256 | } 257 | if err := assign(i, reply[i]); err != nil { 258 | return err 259 | } 260 | } 261 | return nil 262 | case nil: 263 | return ErrNil 264 | case Error: 265 | return reply 266 | } 267 | return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) 268 | } 269 | 270 | // Float64s is a helper that converts an array command reply to a []float64. If 271 | // err is not equal to nil, then Float64s returns nil, err. Nil array items are 272 | // converted to 0 in the output slice. Floats64 returns an error if an array 273 | // item is not a bulk string or nil. 274 | func Float64s(reply interface{}, err error) ([]float64, error) { 275 | var result []float64 276 | err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { 277 | p, ok := v.([]byte) 278 | if !ok { 279 | return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) 280 | } 281 | f, err := strconv.ParseFloat(string(p), 64) 282 | result[i] = f 283 | return err 284 | }) 285 | return result, err 286 | } 287 | 288 | // Strings is a helper that converts an array command reply to a []string. If 289 | // err is not equal to nil, then Strings returns nil, err. Nil array items are 290 | // converted to "" in the output slice. Strings returns an error if an array 291 | // item is not a bulk string or nil. 292 | func Strings(reply interface{}, err error) ([]string, error) { 293 | var result []string 294 | err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { 295 | switch v := v.(type) { 296 | case string: 297 | result[i] = v 298 | return nil 299 | case []byte: 300 | result[i] = string(v) 301 | return nil 302 | default: 303 | return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) 304 | } 305 | }) 306 | return result, err 307 | } 308 | 309 | // ByteSlices is a helper that converts an array command reply to a [][]byte. 310 | // If err is not equal to nil, then ByteSlices returns nil, err. Nil array 311 | // items are stay nil. ByteSlices returns an error if an array item is not a 312 | // bulk string or nil. 313 | func ByteSlices(reply interface{}, err error) ([][]byte, error) { 314 | var result [][]byte 315 | err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { 316 | p, ok := v.([]byte) 317 | if !ok { 318 | return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) 319 | } 320 | result[i] = p 321 | return nil 322 | }) 323 | return result, err 324 | } 325 | 326 | // Int64s is a helper that converts an array command reply to a []int64. 327 | // If err is not equal to nil, then Int64s returns nil, err. Nil array 328 | // items are stay nil. Int64s returns an error if an array item is not a 329 | // bulk string or nil. 330 | func Int64s(reply interface{}, err error) ([]int64, error) { 331 | var result []int64 332 | err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { 333 | switch v := v.(type) { 334 | case int64: 335 | result[i] = v 336 | return nil 337 | case []byte: 338 | n, err := strconv.ParseInt(string(v), 10, 64) 339 | result[i] = n 340 | return err 341 | default: 342 | return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) 343 | } 344 | }) 345 | return result, err 346 | } 347 | 348 | // Ints is a helper that converts an array command reply to a []in. 349 | // If err is not equal to nil, then Ints returns nil, err. Nil array 350 | // items are stay nil. Ints returns an error if an array item is not a 351 | // bulk string or nil. 352 | func Ints(reply interface{}, err error) ([]int, error) { 353 | var result []int 354 | err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { 355 | switch v := v.(type) { 356 | case int64: 357 | n := int(v) 358 | if int64(n) != v { 359 | return strconv.ErrRange 360 | } 361 | result[i] = n 362 | return nil 363 | case []byte: 364 | n, err := strconv.Atoi(string(v)) 365 | result[i] = n 366 | return err 367 | default: 368 | return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) 369 | } 370 | }) 371 | return result, err 372 | } 373 | 374 | // StringMap is a helper that converts an array of strings (alternating key, value) 375 | // into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. 376 | // Requires an even number of values in result. 377 | func StringMap(result interface{}, err error) (map[string]string, error) { 378 | values, err := Values(result, err) 379 | if err != nil { 380 | return nil, err 381 | } 382 | if len(values)%2 != 0 { 383 | return nil, errors.New("redigo: StringMap expects even number of values result") 384 | } 385 | m := make(map[string]string, len(values)/2) 386 | for i := 0; i < len(values); i += 2 { 387 | key, okKey := values[i].([]byte) 388 | value, okValue := values[i+1].([]byte) 389 | if !okKey || !okValue { 390 | return nil, errors.New("redigo: StringMap key not a bulk string value") 391 | } 392 | m[string(key)] = string(value) 393 | } 394 | return m, nil 395 | } 396 | 397 | // IntMap is a helper that converts an array of strings (alternating key, value) 398 | // into a map[string]int. The HGETALL commands return replies in this format. 399 | // Requires an even number of values in result. 400 | func IntMap(result interface{}, err error) (map[string]int, error) { 401 | values, err := Values(result, err) 402 | if err != nil { 403 | return nil, err 404 | } 405 | if len(values)%2 != 0 { 406 | return nil, errors.New("redigo: IntMap expects even number of values result") 407 | } 408 | m := make(map[string]int, len(values)/2) 409 | for i := 0; i < len(values); i += 2 { 410 | key, ok := values[i].([]byte) 411 | if !ok { 412 | return nil, errors.New("redigo: IntMap key not a bulk string value") 413 | } 414 | value, err := Int(values[i+1], nil) 415 | if err != nil { 416 | return nil, err 417 | } 418 | m[string(key)] = value 419 | } 420 | return m, nil 421 | } 422 | 423 | // Int64Map is a helper that converts an array of strings (alternating key, value) 424 | // into a map[string]int64. The HGETALL commands return replies in this format. 425 | // Requires an even number of values in result. 426 | func Int64Map(result interface{}, err error) (map[string]int64, error) { 427 | values, err := Values(result, err) 428 | if err != nil { 429 | return nil, err 430 | } 431 | if len(values)%2 != 0 { 432 | return nil, errors.New("redigo: Int64Map expects even number of values result") 433 | } 434 | m := make(map[string]int64, len(values)/2) 435 | for i := 0; i < len(values); i += 2 { 436 | key, ok := values[i].([]byte) 437 | if !ok { 438 | return nil, errors.New("redigo: Int64Map key not a bulk string value") 439 | } 440 | value, err := Int64(values[i+1], nil) 441 | if err != nil { 442 | return nil, err 443 | } 444 | m[string(key)] = value 445 | } 446 | return m, nil 447 | } 448 | 449 | // Positions is a helper that converts an array of positions (lat, long) 450 | // into a [][2]float64. The GEOPOS command returns replies in this format. 451 | func Positions(result interface{}, err error) ([]*[2]float64, error) { 452 | values, err := Values(result, err) 453 | if err != nil { 454 | return nil, err 455 | } 456 | positions := make([]*[2]float64, len(values)) 457 | for i := range values { 458 | if values[i] == nil { 459 | continue 460 | } 461 | p, ok := values[i].([]interface{}) 462 | if !ok { 463 | return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) 464 | } 465 | if len(p) != 2 { 466 | return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) 467 | } 468 | lat, err := Float64(p[0], nil) 469 | if err != nil { 470 | return nil, err 471 | } 472 | long, err := Float64(p[1], nil) 473 | if err != nil { 474 | return nil, err 475 | } 476 | positions[i] = &[2]float64{lat, long} 477 | } 478 | return positions, nil 479 | } 480 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/scan.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "errors" 19 | "fmt" 20 | "reflect" 21 | "strconv" 22 | "strings" 23 | "sync" 24 | ) 25 | 26 | func ensureLen(d reflect.Value, n int) { 27 | if n > d.Cap() { 28 | d.Set(reflect.MakeSlice(d.Type(), n, n)) 29 | } else { 30 | d.SetLen(n) 31 | } 32 | } 33 | 34 | func cannotConvert(d reflect.Value, s interface{}) error { 35 | var sname string 36 | switch s.(type) { 37 | case string: 38 | sname = "Redis simple string" 39 | case Error: 40 | sname = "Redis error" 41 | case int64: 42 | sname = "Redis integer" 43 | case []byte: 44 | sname = "Redis bulk string" 45 | case []interface{}: 46 | sname = "Redis array" 47 | default: 48 | sname = reflect.TypeOf(s).String() 49 | } 50 | return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) 51 | } 52 | 53 | func convertAssignBulkString(d reflect.Value, s []byte) (err error) { 54 | switch d.Type().Kind() { 55 | case reflect.Float32, reflect.Float64: 56 | var x float64 57 | x, err = strconv.ParseFloat(string(s), d.Type().Bits()) 58 | d.SetFloat(x) 59 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 60 | var x int64 61 | x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) 62 | d.SetInt(x) 63 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 64 | var x uint64 65 | x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) 66 | d.SetUint(x) 67 | case reflect.Bool: 68 | var x bool 69 | x, err = strconv.ParseBool(string(s)) 70 | d.SetBool(x) 71 | case reflect.String: 72 | d.SetString(string(s)) 73 | case reflect.Slice: 74 | if d.Type().Elem().Kind() != reflect.Uint8 { 75 | err = cannotConvert(d, s) 76 | } else { 77 | d.SetBytes(s) 78 | } 79 | default: 80 | err = cannotConvert(d, s) 81 | } 82 | return 83 | } 84 | 85 | func convertAssignInt(d reflect.Value, s int64) (err error) { 86 | switch d.Type().Kind() { 87 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 88 | d.SetInt(s) 89 | if d.Int() != s { 90 | err = strconv.ErrRange 91 | d.SetInt(0) 92 | } 93 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 94 | if s < 0 { 95 | err = strconv.ErrRange 96 | } else { 97 | x := uint64(s) 98 | d.SetUint(x) 99 | if d.Uint() != x { 100 | err = strconv.ErrRange 101 | d.SetUint(0) 102 | } 103 | } 104 | case reflect.Bool: 105 | d.SetBool(s != 0) 106 | default: 107 | err = cannotConvert(d, s) 108 | } 109 | return 110 | } 111 | 112 | func convertAssignValue(d reflect.Value, s interface{}) (err error) { 113 | if d.Kind() != reflect.Ptr { 114 | if d.CanAddr() { 115 | d2 := d.Addr() 116 | if d2.CanInterface() { 117 | if scanner, ok := d2.Interface().(Scanner); ok { 118 | return scanner.RedisScan(s) 119 | } 120 | } 121 | } 122 | } else if d.CanInterface() { 123 | // Already a reflect.Ptr 124 | if d.IsNil() { 125 | d.Set(reflect.New(d.Type().Elem())) 126 | } 127 | if scanner, ok := d.Interface().(Scanner); ok { 128 | return scanner.RedisScan(s) 129 | } 130 | } 131 | 132 | switch s := s.(type) { 133 | case []byte: 134 | err = convertAssignBulkString(d, s) 135 | case int64: 136 | err = convertAssignInt(d, s) 137 | default: 138 | err = cannotConvert(d, s) 139 | } 140 | return err 141 | } 142 | 143 | func convertAssignArray(d reflect.Value, s []interface{}) error { 144 | if d.Type().Kind() != reflect.Slice { 145 | return cannotConvert(d, s) 146 | } 147 | ensureLen(d, len(s)) 148 | for i := 0; i < len(s); i++ { 149 | if err := convertAssignValue(d.Index(i), s[i]); err != nil { 150 | return err 151 | } 152 | } 153 | return nil 154 | } 155 | 156 | func convertAssign(d interface{}, s interface{}) (err error) { 157 | if scanner, ok := d.(Scanner); ok { 158 | return scanner.RedisScan(s) 159 | } 160 | 161 | // Handle the most common destination types using type switches and 162 | // fall back to reflection for all other types. 163 | switch s := s.(type) { 164 | case nil: 165 | // ignore 166 | case []byte: 167 | switch d := d.(type) { 168 | case *string: 169 | *d = string(s) 170 | case *int: 171 | *d, err = strconv.Atoi(string(s)) 172 | case *bool: 173 | *d, err = strconv.ParseBool(string(s)) 174 | case *[]byte: 175 | *d = s 176 | case *interface{}: 177 | *d = s 178 | case nil: 179 | // skip value 180 | default: 181 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 182 | err = cannotConvert(d, s) 183 | } else { 184 | err = convertAssignBulkString(d.Elem(), s) 185 | } 186 | } 187 | case int64: 188 | switch d := d.(type) { 189 | case *int: 190 | x := int(s) 191 | if int64(x) != s { 192 | err = strconv.ErrRange 193 | x = 0 194 | } 195 | *d = x 196 | case *bool: 197 | *d = s != 0 198 | case *interface{}: 199 | *d = s 200 | case nil: 201 | // skip value 202 | default: 203 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 204 | err = cannotConvert(d, s) 205 | } else { 206 | err = convertAssignInt(d.Elem(), s) 207 | } 208 | } 209 | case string: 210 | switch d := d.(type) { 211 | case *string: 212 | *d = s 213 | case *interface{}: 214 | *d = s 215 | case nil: 216 | // skip value 217 | default: 218 | err = cannotConvert(reflect.ValueOf(d), s) 219 | } 220 | case []interface{}: 221 | switch d := d.(type) { 222 | case *[]interface{}: 223 | *d = s 224 | case *interface{}: 225 | *d = s 226 | case nil: 227 | // skip value 228 | default: 229 | if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { 230 | err = cannotConvert(d, s) 231 | } else { 232 | err = convertAssignArray(d.Elem(), s) 233 | } 234 | } 235 | case Error: 236 | err = s 237 | default: 238 | err = cannotConvert(reflect.ValueOf(d), s) 239 | } 240 | return 241 | } 242 | 243 | // Scan copies from src to the values pointed at by dest. 244 | // 245 | // Scan uses RedisScan if available otherwise: 246 | // 247 | // The values pointed at by dest must be an integer, float, boolean, string, 248 | // []byte, interface{} or slices of these types. Scan uses the standard strconv 249 | // package to convert bulk strings to numeric and boolean types. 250 | // 251 | // If a dest value is nil, then the corresponding src value is skipped. 252 | // 253 | // If a src element is nil, then the corresponding dest value is not modified. 254 | // 255 | // To enable easy use of Scan in a loop, Scan returns the slice of src 256 | // following the copied values. 257 | func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { 258 | if len(src) < len(dest) { 259 | return nil, errors.New("redigo.Scan: array short") 260 | } 261 | var err error 262 | for i, d := range dest { 263 | err = convertAssign(d, src[i]) 264 | if err != nil { 265 | err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) 266 | break 267 | } 268 | } 269 | return src[len(dest):], err 270 | } 271 | 272 | type fieldSpec struct { 273 | name string 274 | index []int 275 | omitEmpty bool 276 | } 277 | 278 | type structSpec struct { 279 | m map[string]*fieldSpec 280 | l []*fieldSpec 281 | } 282 | 283 | func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { 284 | return ss.m[string(name)] 285 | } 286 | 287 | func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { 288 | for i := 0; i < t.NumField(); i++ { 289 | f := t.Field(i) 290 | switch { 291 | case f.PkgPath != "" && !f.Anonymous: 292 | // Ignore unexported fields. 293 | case f.Anonymous: 294 | // TODO: Handle pointers. Requires change to decoder and 295 | // protection against infinite recursion. 296 | if f.Type.Kind() == reflect.Struct { 297 | compileStructSpec(f.Type, depth, append(index, i), ss) 298 | } 299 | default: 300 | fs := &fieldSpec{name: f.Name} 301 | tag := f.Tag.Get("redis") 302 | p := strings.Split(tag, ",") 303 | if len(p) > 0 { 304 | if p[0] == "-" { 305 | continue 306 | } 307 | if len(p[0]) > 0 { 308 | fs.name = p[0] 309 | } 310 | for _, s := range p[1:] { 311 | switch s { 312 | case "omitempty": 313 | fs.omitEmpty = true 314 | default: 315 | panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) 316 | } 317 | } 318 | } 319 | d, found := depth[fs.name] 320 | if !found { 321 | d = 1 << 30 322 | } 323 | switch { 324 | case len(index) == d: 325 | // At same depth, remove from result. 326 | delete(ss.m, fs.name) 327 | j := 0 328 | for i := 0; i < len(ss.l); i++ { 329 | if fs.name != ss.l[i].name { 330 | ss.l[j] = ss.l[i] 331 | j += 1 332 | } 333 | } 334 | ss.l = ss.l[:j] 335 | case len(index) < d: 336 | fs.index = make([]int, len(index)+1) 337 | copy(fs.index, index) 338 | fs.index[len(index)] = i 339 | depth[fs.name] = len(index) 340 | ss.m[fs.name] = fs 341 | ss.l = append(ss.l, fs) 342 | } 343 | } 344 | } 345 | } 346 | 347 | var ( 348 | structSpecMutex sync.RWMutex 349 | structSpecCache = make(map[reflect.Type]*structSpec) 350 | defaultFieldSpec = &fieldSpec{} 351 | ) 352 | 353 | func structSpecForType(t reflect.Type) *structSpec { 354 | 355 | structSpecMutex.RLock() 356 | ss, found := structSpecCache[t] 357 | structSpecMutex.RUnlock() 358 | if found { 359 | return ss 360 | } 361 | 362 | structSpecMutex.Lock() 363 | defer structSpecMutex.Unlock() 364 | ss, found = structSpecCache[t] 365 | if found { 366 | return ss 367 | } 368 | 369 | ss = &structSpec{m: make(map[string]*fieldSpec)} 370 | compileStructSpec(t, make(map[string]int), nil, ss) 371 | structSpecCache[t] = ss 372 | return ss 373 | } 374 | 375 | var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") 376 | 377 | // ScanStruct scans alternating names and values from src to a struct. The 378 | // HGETALL and CONFIG GET commands return replies in this format. 379 | // 380 | // ScanStruct uses exported field names to match values in the response. Use 381 | // 'redis' field tag to override the name: 382 | // 383 | // Field int `redis:"myName"` 384 | // 385 | // Fields with the tag redis:"-" are ignored. 386 | // 387 | // Each field uses RedisScan if available otherwise: 388 | // Integer, float, boolean, string and []byte fields are supported. Scan uses the 389 | // standard strconv package to convert bulk string values to numeric and 390 | // boolean types. 391 | // 392 | // If a src element is nil, then the corresponding field is not modified. 393 | func ScanStruct(src []interface{}, dest interface{}) error { 394 | d := reflect.ValueOf(dest) 395 | if d.Kind() != reflect.Ptr || d.IsNil() { 396 | return errScanStructValue 397 | } 398 | d = d.Elem() 399 | if d.Kind() != reflect.Struct { 400 | return errScanStructValue 401 | } 402 | ss := structSpecForType(d.Type()) 403 | 404 | if len(src)%2 != 0 { 405 | return errors.New("redigo.ScanStruct: number of values not a multiple of 2") 406 | } 407 | 408 | for i := 0; i < len(src); i += 2 { 409 | s := src[i+1] 410 | if s == nil { 411 | continue 412 | } 413 | name, ok := src[i].([]byte) 414 | if !ok { 415 | return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) 416 | } 417 | fs := ss.fieldSpec(name) 418 | if fs == nil { 419 | continue 420 | } 421 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 422 | return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) 423 | } 424 | } 425 | return nil 426 | } 427 | 428 | var ( 429 | errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") 430 | ) 431 | 432 | // ScanSlice scans src to the slice pointed to by dest. The elements the dest 433 | // slice must be integer, float, boolean, string, struct or pointer to struct 434 | // values. 435 | // 436 | // Struct fields must be integer, float, boolean or string values. All struct 437 | // fields are used unless a subset is specified using fieldNames. 438 | func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { 439 | d := reflect.ValueOf(dest) 440 | if d.Kind() != reflect.Ptr || d.IsNil() { 441 | return errScanSliceValue 442 | } 443 | d = d.Elem() 444 | if d.Kind() != reflect.Slice { 445 | return errScanSliceValue 446 | } 447 | 448 | isPtr := false 449 | t := d.Type().Elem() 450 | if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { 451 | isPtr = true 452 | t = t.Elem() 453 | } 454 | 455 | if t.Kind() != reflect.Struct { 456 | ensureLen(d, len(src)) 457 | for i, s := range src { 458 | if s == nil { 459 | continue 460 | } 461 | if err := convertAssignValue(d.Index(i), s); err != nil { 462 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) 463 | } 464 | } 465 | return nil 466 | } 467 | 468 | ss := structSpecForType(t) 469 | fss := ss.l 470 | if len(fieldNames) > 0 { 471 | fss = make([]*fieldSpec, len(fieldNames)) 472 | for i, name := range fieldNames { 473 | fss[i] = ss.m[name] 474 | if fss[i] == nil { 475 | return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) 476 | } 477 | } 478 | } 479 | 480 | if len(fss) == 0 { 481 | return errors.New("redigo.ScanSlice: no struct fields") 482 | } 483 | 484 | n := len(src) / len(fss) 485 | if n*len(fss) != len(src) { 486 | return errors.New("redigo.ScanSlice: length not a multiple of struct field count") 487 | } 488 | 489 | ensureLen(d, n) 490 | for i := 0; i < n; i++ { 491 | d := d.Index(i) 492 | if isPtr { 493 | if d.IsNil() { 494 | d.Set(reflect.New(t)) 495 | } 496 | d = d.Elem() 497 | } 498 | for j, fs := range fss { 499 | s := src[i*len(fss)+j] 500 | if s == nil { 501 | continue 502 | } 503 | if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { 504 | return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) 505 | } 506 | } 507 | } 508 | return nil 509 | } 510 | 511 | // Args is a helper for constructing command arguments from structured values. 512 | type Args []interface{} 513 | 514 | // Add returns the result of appending value to args. 515 | func (args Args) Add(value ...interface{}) Args { 516 | return append(args, value...) 517 | } 518 | 519 | // AddFlat returns the result of appending the flattened value of v to args. 520 | // 521 | // Maps are flattened by appending the alternating keys and map values to args. 522 | // 523 | // Slices are flattened by appending the slice elements to args. 524 | // 525 | // Structs are flattened by appending the alternating names and values of 526 | // exported fields to args. If v is a nil struct pointer, then nothing is 527 | // appended. The 'redis' field tag overrides struct field names. See ScanStruct 528 | // for more information on the use of the 'redis' field tag. 529 | // 530 | // Other types are appended to args as is. 531 | func (args Args) AddFlat(v interface{}) Args { 532 | rv := reflect.ValueOf(v) 533 | switch rv.Kind() { 534 | case reflect.Struct: 535 | args = flattenStruct(args, rv) 536 | case reflect.Slice: 537 | for i := 0; i < rv.Len(); i++ { 538 | args = append(args, rv.Index(i).Interface()) 539 | } 540 | case reflect.Map: 541 | for _, k := range rv.MapKeys() { 542 | args = append(args, k.Interface(), rv.MapIndex(k).Interface()) 543 | } 544 | case reflect.Ptr: 545 | if rv.Type().Elem().Kind() == reflect.Struct { 546 | if !rv.IsNil() { 547 | args = flattenStruct(args, rv.Elem()) 548 | } 549 | } else { 550 | args = append(args, v) 551 | } 552 | default: 553 | args = append(args, v) 554 | } 555 | return args 556 | } 557 | 558 | func flattenStruct(args Args, v reflect.Value) Args { 559 | ss := structSpecForType(v.Type()) 560 | for _, fs := range ss.l { 561 | fv := v.FieldByIndex(fs.index) 562 | if fs.omitEmpty { 563 | var empty = false 564 | switch fv.Kind() { 565 | case reflect.Array, reflect.Map, reflect.Slice, reflect.String: 566 | empty = fv.Len() == 0 567 | case reflect.Bool: 568 | empty = !fv.Bool() 569 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 570 | empty = fv.Int() == 0 571 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: 572 | empty = fv.Uint() == 0 573 | case reflect.Float32, reflect.Float64: 574 | empty = fv.Float() == 0 575 | case reflect.Interface, reflect.Ptr: 576 | empty = fv.IsNil() 577 | } 578 | if empty { 579 | continue 580 | } 581 | } 582 | args = append(args, fs.name, fv.Interface()) 583 | } 584 | return args 585 | } 586 | -------------------------------------------------------------------------------- /vendor/github.com/gomodule/redigo/redis/script.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 Gary Burd 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 redis 16 | 17 | import ( 18 | "crypto/sha1" 19 | "encoding/hex" 20 | "io" 21 | "strings" 22 | ) 23 | 24 | // Script encapsulates the source, hash and key count for a Lua script. See 25 | // http://redis.io/commands/eval for information on scripts in Redis. 26 | type Script struct { 27 | keyCount int 28 | src string 29 | hash string 30 | } 31 | 32 | // NewScript returns a new script object. If keyCount is greater than or equal 33 | // to zero, then the count is automatically inserted in the EVAL command 34 | // argument list. If keyCount is less than zero, then the application supplies 35 | // the count as the first value in the keysAndArgs argument to the Do, Send and 36 | // SendHash methods. 37 | func NewScript(keyCount int, src string) *Script { 38 | h := sha1.New() 39 | io.WriteString(h, src) 40 | return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} 41 | } 42 | 43 | func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { 44 | var args []interface{} 45 | if s.keyCount < 0 { 46 | args = make([]interface{}, 1+len(keysAndArgs)) 47 | args[0] = spec 48 | copy(args[1:], keysAndArgs) 49 | } else { 50 | args = make([]interface{}, 2+len(keysAndArgs)) 51 | args[0] = spec 52 | args[1] = s.keyCount 53 | copy(args[2:], keysAndArgs) 54 | } 55 | return args 56 | } 57 | 58 | // Hash returns the script hash. 59 | func (s *Script) Hash() string { 60 | return s.hash 61 | } 62 | 63 | // Do evaluates the script. Under the covers, Do optimistically evaluates the 64 | // script using the EVALSHA command. If the command fails because the script is 65 | // not loaded, then Do evaluates the script using the EVAL command (thus 66 | // causing the script to load). 67 | func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { 68 | v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) 69 | if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { 70 | v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) 71 | } 72 | return v, err 73 | } 74 | 75 | // SendHash evaluates the script without waiting for the reply. The script is 76 | // evaluated with the EVALSHA command. The application must ensure that the 77 | // script is loaded by a previous call to Send, Do or Load methods. 78 | func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { 79 | return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) 80 | } 81 | 82 | // Send evaluates the script without waiting for the reply. 83 | func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { 84 | return c.Send("EVAL", s.args(s.src, keysAndArgs)...) 85 | } 86 | 87 | // Load loads the script without evaluating it. 88 | func (s *Script) Load(c Conn) error { 89 | _, err := c.Do("SCRIPT", "LOAD", s.src) 90 | return err 91 | } 92 | -------------------------------------------------------------------------------- /vendor/gopkg.in/ini.v1/.gitignore: -------------------------------------------------------------------------------- 1 | testdata/conf_out.ini 2 | ini.sublime-project 3 | ini.sublime-workspace 4 | testdata/conf_reflect.ini 5 | .idea 6 | /.vscode 7 | -------------------------------------------------------------------------------- /vendor/gopkg.in/ini.v1/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.6.x 5 | - 1.7.x 6 | - 1.8.x 7 | - 1.9.x 8 | - 1.10.x 9 | 10 | script: 11 | - go get golang.org/x/tools/cmd/cover 12 | - go get github.com/smartystreets/goconvey 13 | - mkdir -p $HOME/gopath/src/gopkg.in 14 | - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1 15 | - cd $HOME/gopath/src/gopkg.in/ini.v1 16 | - go test -v -cover -race 17 | -------------------------------------------------------------------------------- /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 2014 Unknwon 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/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build test bench vet coverage 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 | 14 | coverage: 15 | go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out 16 | -------------------------------------------------------------------------------- /vendor/gopkg.in/ini.v1/README.md: -------------------------------------------------------------------------------- 1 | INI [![Build Status](https://travis-ci.org/go-ini/ini.svg?branch=master)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg)](https://sourcegraph.com/github.com/go-ini/ini) 2 | === 3 | 4 | ![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) 5 | 6 | Package ini provides INI file read and write functionality in Go. 7 | 8 | ## Features 9 | 10 | - Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites. 11 | - Read with recursion values. 12 | - Read with parent-child sections. 13 | - Read with auto-increment key names. 14 | - Read with multiple-line values. 15 | - Read with tons of helper methods. 16 | - Read and convert values to Go types. 17 | - Read and **WRITE** comments of sections and keys. 18 | - Manipulate sections, keys and comments with ease. 19 | - Keep sections and keys in order as you parse and save. 20 | 21 | ## Installation 22 | 23 | The minimum requirement of Go is **1.6**. 24 | 25 | To use a tagged revision: 26 | 27 | ```sh 28 | $ go get gopkg.in/ini.v1 29 | ``` 30 | 31 | To use with latest changes: 32 | 33 | ```sh 34 | $ go get github.com/go-ini/ini 35 | ``` 36 | 37 | Please add `-u` flag to update in the future. 38 | 39 | ## Getting Help 40 | 41 | - [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) 42 | - [API Documentation](https://gowalker.org/gopkg.in/ini.v1) 43 | 44 | ## License 45 | 46 | This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. 47 | -------------------------------------------------------------------------------- /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/gopkg.in/ini.v1/file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 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 | "io" 22 | "io/ioutil" 23 | "os" 24 | "strings" 25 | "sync" 26 | ) 27 | 28 | // File represents a combination of a or more INI file(s) in memory. 29 | type File struct { 30 | options LoadOptions 31 | dataSources []dataSource 32 | 33 | // Should make things safe, but sometimes doesn't matter. 34 | BlockMode bool 35 | lock sync.RWMutex 36 | 37 | // To keep data in order. 38 | sectionList []string 39 | // Actual data is stored here. 40 | sections map[string]*Section 41 | 42 | NameMapper 43 | ValueMapper 44 | } 45 | 46 | // newFile initializes File object with given data sources. 47 | func newFile(dataSources []dataSource, opts LoadOptions) *File { 48 | return &File{ 49 | BlockMode: true, 50 | dataSources: dataSources, 51 | sections: make(map[string]*Section), 52 | sectionList: make([]string, 0, 10), 53 | options: opts, 54 | } 55 | } 56 | 57 | // Empty returns an empty file object. 58 | func Empty() *File { 59 | // Ignore error here, we sure our data is good. 60 | f, _ := Load([]byte("")) 61 | return f 62 | } 63 | 64 | // NewSection creates a new section. 65 | func (f *File) NewSection(name string) (*Section, error) { 66 | if len(name) == 0 { 67 | return nil, errors.New("error creating new section: empty section name") 68 | } else if f.options.Insensitive && name != DEFAULT_SECTION { 69 | name = strings.ToLower(name) 70 | } 71 | 72 | if f.BlockMode { 73 | f.lock.Lock() 74 | defer f.lock.Unlock() 75 | } 76 | 77 | if inSlice(name, f.sectionList) { 78 | return f.sections[name], nil 79 | } 80 | 81 | f.sectionList = append(f.sectionList, name) 82 | f.sections[name] = newSection(f, name) 83 | return f.sections[name], nil 84 | } 85 | 86 | // NewRawSection creates a new section with an unparseable body. 87 | func (f *File) NewRawSection(name, body string) (*Section, error) { 88 | section, err := f.NewSection(name) 89 | if err != nil { 90 | return nil, err 91 | } 92 | 93 | section.isRawSection = true 94 | section.rawBody = body 95 | return section, nil 96 | } 97 | 98 | // NewSections creates a list of sections. 99 | func (f *File) NewSections(names ...string) (err error) { 100 | for _, name := range names { 101 | if _, err = f.NewSection(name); err != nil { 102 | return err 103 | } 104 | } 105 | return nil 106 | } 107 | 108 | // GetSection returns section by given name. 109 | func (f *File) GetSection(name string) (*Section, error) { 110 | if len(name) == 0 { 111 | name = DEFAULT_SECTION 112 | } 113 | if f.options.Insensitive { 114 | name = strings.ToLower(name) 115 | } 116 | 117 | if f.BlockMode { 118 | f.lock.RLock() 119 | defer f.lock.RUnlock() 120 | } 121 | 122 | sec := f.sections[name] 123 | if sec == nil { 124 | return nil, fmt.Errorf("section '%s' does not exist", name) 125 | } 126 | return sec, nil 127 | } 128 | 129 | // Section assumes named section exists and returns a zero-value when not. 130 | func (f *File) Section(name string) *Section { 131 | sec, err := f.GetSection(name) 132 | if err != nil { 133 | // Note: It's OK here because the only possible error is empty section name, 134 | // but if it's empty, this piece of code won't be executed. 135 | sec, _ = f.NewSection(name) 136 | return sec 137 | } 138 | return sec 139 | } 140 | 141 | // Section returns list of Section. 142 | func (f *File) Sections() []*Section { 143 | if f.BlockMode { 144 | f.lock.RLock() 145 | defer f.lock.RUnlock() 146 | } 147 | 148 | sections := make([]*Section, len(f.sectionList)) 149 | for i, name := range f.sectionList { 150 | sections[i] = f.sections[name] 151 | } 152 | return sections 153 | } 154 | 155 | // ChildSections returns a list of child sections of given section name. 156 | func (f *File) ChildSections(name string) []*Section { 157 | return f.Section(name).ChildSections() 158 | } 159 | 160 | // SectionStrings returns list of section names. 161 | func (f *File) SectionStrings() []string { 162 | list := make([]string, len(f.sectionList)) 163 | copy(list, f.sectionList) 164 | return list 165 | } 166 | 167 | // DeleteSection deletes a section. 168 | func (f *File) DeleteSection(name string) { 169 | if f.BlockMode { 170 | f.lock.Lock() 171 | defer f.lock.Unlock() 172 | } 173 | 174 | if len(name) == 0 { 175 | name = DEFAULT_SECTION 176 | } 177 | 178 | for i, s := range f.sectionList { 179 | if s == name { 180 | f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) 181 | delete(f.sections, name) 182 | return 183 | } 184 | } 185 | } 186 | 187 | func (f *File) reload(s dataSource) error { 188 | r, err := s.ReadCloser() 189 | if err != nil { 190 | return err 191 | } 192 | defer r.Close() 193 | 194 | return f.parse(r) 195 | } 196 | 197 | // Reload reloads and parses all data sources. 198 | func (f *File) Reload() (err error) { 199 | for _, s := range f.dataSources { 200 | if err = f.reload(s); err != nil { 201 | // In loose mode, we create an empty default section for nonexistent files. 202 | if os.IsNotExist(err) && f.options.Loose { 203 | f.parse(bytes.NewBuffer(nil)) 204 | continue 205 | } 206 | return err 207 | } 208 | } 209 | return nil 210 | } 211 | 212 | // Append appends one or more data sources and reloads automatically. 213 | func (f *File) Append(source interface{}, others ...interface{}) error { 214 | ds, err := parseDataSource(source) 215 | if err != nil { 216 | return err 217 | } 218 | f.dataSources = append(f.dataSources, ds) 219 | for _, s := range others { 220 | ds, err = parseDataSource(s) 221 | if err != nil { 222 | return err 223 | } 224 | f.dataSources = append(f.dataSources, ds) 225 | } 226 | return f.Reload() 227 | } 228 | 229 | func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { 230 | equalSign := DefaultFormatLeft + "=" + DefaultFormatRight 231 | 232 | if PrettyFormat || PrettyEqual { 233 | equalSign = " = " 234 | } 235 | 236 | // Use buffer to make sure target is safe until finish encoding. 237 | buf := bytes.NewBuffer(nil) 238 | for i, sname := range f.sectionList { 239 | sec := f.Section(sname) 240 | if len(sec.Comment) > 0 { 241 | // Support multiline comments 242 | lines := strings.Split(sec.Comment, LineBreak) 243 | for i := range lines { 244 | if lines[i][0] != '#' && lines[i][0] != ';' { 245 | lines[i] = "; " + lines[i] 246 | } else { 247 | lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) 248 | } 249 | 250 | if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { 251 | return nil, err 252 | } 253 | } 254 | } 255 | 256 | if i > 0 || DefaultHeader { 257 | if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { 258 | return nil, err 259 | } 260 | } else { 261 | // Write nothing if default section is empty 262 | if len(sec.keyList) == 0 { 263 | continue 264 | } 265 | } 266 | 267 | if sec.isRawSection { 268 | if _, err := buf.WriteString(sec.rawBody); err != nil { 269 | return nil, err 270 | } 271 | 272 | if PrettySection { 273 | // Put a line between sections 274 | if _, err := buf.WriteString(LineBreak); err != nil { 275 | return nil, err 276 | } 277 | } 278 | continue 279 | } 280 | 281 | // Count and generate alignment length and buffer spaces using the 282 | // longest key. Keys may be modifed if they contain certain characters so 283 | // we need to take that into account in our calculation. 284 | alignLength := 0 285 | if PrettyFormat { 286 | for _, kname := range sec.keyList { 287 | keyLength := len(kname) 288 | // First case will surround key by ` and second by """ 289 | if strings.ContainsAny(kname, "\"=:") { 290 | keyLength += 2 291 | } else if strings.Contains(kname, "`") { 292 | keyLength += 6 293 | } 294 | 295 | if keyLength > alignLength { 296 | alignLength = keyLength 297 | } 298 | } 299 | } 300 | alignSpaces := bytes.Repeat([]byte(" "), alignLength) 301 | 302 | KEY_LIST: 303 | for _, kname := range sec.keyList { 304 | key := sec.Key(kname) 305 | if len(key.Comment) > 0 { 306 | if len(indent) > 0 && sname != DEFAULT_SECTION { 307 | buf.WriteString(indent) 308 | } 309 | 310 | // Support multiline comments 311 | lines := strings.Split(key.Comment, LineBreak) 312 | for i := range lines { 313 | if lines[i][0] != '#' && lines[i][0] != ';' { 314 | lines[i] = "; " + lines[i] 315 | } else { 316 | lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) 317 | } 318 | 319 | if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { 320 | return nil, err 321 | } 322 | } 323 | } 324 | 325 | if len(indent) > 0 && sname != DEFAULT_SECTION { 326 | buf.WriteString(indent) 327 | } 328 | 329 | switch { 330 | case key.isAutoIncrement: 331 | kname = "-" 332 | case strings.ContainsAny(kname, "\"=:"): 333 | kname = "`" + kname + "`" 334 | case strings.Contains(kname, "`"): 335 | kname = `"""` + kname + `"""` 336 | } 337 | 338 | for _, val := range key.ValueWithShadows() { 339 | if _, err := buf.WriteString(kname); err != nil { 340 | return nil, err 341 | } 342 | 343 | if key.isBooleanType { 344 | if kname != sec.keyList[len(sec.keyList)-1] { 345 | buf.WriteString(LineBreak) 346 | } 347 | continue KEY_LIST 348 | } 349 | 350 | // Write out alignment spaces before "=" sign 351 | if PrettyFormat { 352 | buf.Write(alignSpaces[:alignLength-len(kname)]) 353 | } 354 | 355 | // In case key value contains "\n", "`", "\"", "#" or ";" 356 | if strings.ContainsAny(val, "\n`") { 357 | val = `"""` + val + `"""` 358 | } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { 359 | val = "`" + val + "`" 360 | } 361 | if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { 362 | return nil, err 363 | } 364 | } 365 | 366 | for _, val := range key.nestedValues { 367 | if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { 368 | return nil, err 369 | } 370 | } 371 | } 372 | 373 | if PrettySection { 374 | // Put a line between sections 375 | if _, err := buf.WriteString(LineBreak); err != nil { 376 | return nil, err 377 | } 378 | } 379 | } 380 | 381 | return buf, nil 382 | } 383 | 384 | // WriteToIndent writes content into io.Writer with given indention. 385 | // If PrettyFormat has been set to be true, 386 | // it will align "=" sign with spaces under each section. 387 | func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { 388 | buf, err := f.writeToBuffer(indent) 389 | if err != nil { 390 | return 0, err 391 | } 392 | return buf.WriteTo(w) 393 | } 394 | 395 | // WriteTo writes file content into io.Writer. 396 | func (f *File) WriteTo(w io.Writer) (int64, error) { 397 | return f.WriteToIndent(w, "") 398 | } 399 | 400 | // SaveToIndent writes content to file system with given value indention. 401 | func (f *File) SaveToIndent(filename, indent string) error { 402 | // Note: Because we are truncating with os.Create, 403 | // so it's safer to save to a temporary file location and rename afte done. 404 | buf, err := f.writeToBuffer(indent) 405 | if err != nil { 406 | return err 407 | } 408 | 409 | return ioutil.WriteFile(filename, buf.Bytes(), 0666) 410 | } 411 | 412 | // SaveTo writes content to file system. 413 | func (f *File) SaveTo(filename string) error { 414 | return f.SaveToIndent(filename, "") 415 | } 416 | -------------------------------------------------------------------------------- /vendor/gopkg.in/ini.v1/ini.go: -------------------------------------------------------------------------------- 1 | // +build go1.6 2 | 3 | // Copyright 2014 Unknwon 4 | // 5 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 6 | // not use this file except in compliance with the License. You may obtain 7 | // a copy of the License at 8 | // 9 | // http://www.apache.org/licenses/LICENSE-2.0 10 | // 11 | // Unless required by applicable law or agreed to in writing, software 12 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | // License for the specific language governing permissions and limitations 15 | // under the License. 16 | 17 | // Package ini provides INI file read and write functionality in Go. 18 | package ini 19 | 20 | import ( 21 | "bytes" 22 | "fmt" 23 | "io" 24 | "io/ioutil" 25 | "os" 26 | "regexp" 27 | "runtime" 28 | ) 29 | 30 | const ( 31 | // Name for default section. You can use this constant or the string literal. 32 | // In most of cases, an empty string is all you need to access the section. 33 | DEFAULT_SECTION = "DEFAULT" 34 | 35 | // Maximum allowed depth when recursively substituing variable names. 36 | _DEPTH_VALUES = 99 37 | _VERSION = "1.38.3" 38 | ) 39 | 40 | // Version returns current package version literal. 41 | func Version() string { 42 | return _VERSION 43 | } 44 | 45 | var ( 46 | // Delimiter to determine or compose a new line. 47 | // This variable will be changed to "\r\n" automatically on Windows 48 | // at package init time. 49 | LineBreak = "\n" 50 | 51 | // Place custom spaces when PrettyFormat and PrettyEqual are both disabled 52 | DefaultFormatLeft = "" 53 | DefaultFormatRight = "" 54 | 55 | // Variable regexp pattern: %(variable)s 56 | varPattern = regexp.MustCompile(`%\(([^\)]+)\)s`) 57 | 58 | // Indicate whether to align "=" sign with spaces to produce pretty output 59 | // or reduce all possible spaces for compact format. 60 | PrettyFormat = true 61 | 62 | // Place spaces around "=" sign even when PrettyFormat is false 63 | PrettyEqual = false 64 | 65 | // Explicitly write DEFAULT section header 66 | DefaultHeader = false 67 | 68 | // Indicate whether to put a line between sections 69 | PrettySection = true 70 | ) 71 | 72 | func init() { 73 | if runtime.GOOS == "windows" { 74 | LineBreak = "\r\n" 75 | } 76 | } 77 | 78 | func inSlice(str string, s []string) bool { 79 | for _, v := range s { 80 | if str == v { 81 | return true 82 | } 83 | } 84 | return false 85 | } 86 | 87 | // dataSource is an interface that returns object which can be read and closed. 88 | type dataSource interface { 89 | ReadCloser() (io.ReadCloser, error) 90 | } 91 | 92 | // sourceFile represents an object that contains content on the local file system. 93 | type sourceFile struct { 94 | name string 95 | } 96 | 97 | func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { 98 | return os.Open(s.name) 99 | } 100 | 101 | // sourceData represents an object that contains content in memory. 102 | type sourceData struct { 103 | data []byte 104 | } 105 | 106 | func (s *sourceData) ReadCloser() (io.ReadCloser, error) { 107 | return ioutil.NopCloser(bytes.NewReader(s.data)), nil 108 | } 109 | 110 | // sourceReadCloser represents an input stream with Close method. 111 | type sourceReadCloser struct { 112 | reader io.ReadCloser 113 | } 114 | 115 | func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) { 116 | return s.reader, nil 117 | } 118 | 119 | func parseDataSource(source interface{}) (dataSource, error) { 120 | switch s := source.(type) { 121 | case string: 122 | return sourceFile{s}, nil 123 | case []byte: 124 | return &sourceData{s}, nil 125 | case io.ReadCloser: 126 | return &sourceReadCloser{s}, nil 127 | default: 128 | return nil, fmt.Errorf("error parsing data source: unknown type '%s'", s) 129 | } 130 | } 131 | 132 | type LoadOptions struct { 133 | // Loose indicates whether the parser should ignore nonexistent files or return error. 134 | Loose bool 135 | // Insensitive indicates whether the parser forces all section and key names to lowercase. 136 | Insensitive bool 137 | // IgnoreContinuation indicates whether to ignore continuation lines while parsing. 138 | IgnoreContinuation bool 139 | // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. 140 | IgnoreInlineComment bool 141 | // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. 142 | SkipUnrecognizableLines bool 143 | // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. 144 | // This type of keys are mostly used in my.cnf. 145 | AllowBooleanKeys bool 146 | // AllowShadows indicates whether to keep track of keys with same name under same section. 147 | AllowShadows bool 148 | // AllowNestedValues indicates whether to allow AWS-like nested values. 149 | // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values 150 | AllowNestedValues bool 151 | // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values. 152 | // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure 153 | // Relevant quote: Values can also span multiple lines, as long as they are indented deeper 154 | // than the first line of the value. 155 | AllowPythonMultilineValues bool 156 | // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value. 157 | // Docs: https://docs.python.org/2/library/configparser.html 158 | // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. 159 | // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. 160 | SpaceBeforeInlineComment bool 161 | // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format 162 | // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value" 163 | UnescapeValueDoubleQuotes bool 164 | // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format 165 | // when value is NOT surrounded by any quotes. 166 | // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all. 167 | UnescapeValueCommentSymbols bool 168 | // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise 169 | // conform to key/value pairs. Specify the names of those blocks here. 170 | UnparseableSections []string 171 | } 172 | 173 | func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { 174 | sources := make([]dataSource, len(others)+1) 175 | sources[0], err = parseDataSource(source) 176 | if err != nil { 177 | return nil, err 178 | } 179 | for i := range others { 180 | sources[i+1], err = parseDataSource(others[i]) 181 | if err != nil { 182 | return nil, err 183 | } 184 | } 185 | f := newFile(sources, opts) 186 | if err = f.Reload(); err != nil { 187 | return nil, err 188 | } 189 | return f, nil 190 | } 191 | 192 | // Load loads and parses from INI data sources. 193 | // Arguments can be mixed of file name with string type, or raw data in []byte. 194 | // It will return error if list contains nonexistent files. 195 | func Load(source interface{}, others ...interface{}) (*File, error) { 196 | return LoadSources(LoadOptions{}, source, others...) 197 | } 198 | 199 | // LooseLoad has exactly same functionality as Load function 200 | // except it ignores nonexistent files instead of returning error. 201 | func LooseLoad(source interface{}, others ...interface{}) (*File, error) { 202 | return LoadSources(LoadOptions{Loose: true}, source, others...) 203 | } 204 | 205 | // InsensitiveLoad has exactly same functionality as Load function 206 | // except it forces all section and key names to be lowercased. 207 | func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) { 208 | return LoadSources(LoadOptions{Insensitive: true}, source, others...) 209 | } 210 | 211 | // ShadowLoad has exactly same functionality as Load function 212 | // except it allows have shadow keys. 213 | func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { 214 | return LoadSources(LoadOptions{AllowShadows: true}, source, others...) 215 | } 216 | -------------------------------------------------------------------------------- /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 | "regexp" 23 | "strconv" 24 | "strings" 25 | "unicode" 26 | ) 27 | 28 | var pythonMultiline = regexp.MustCompile("^(\\s+)([^\n]+)") 29 | 30 | type tokenType int 31 | 32 | const ( 33 | _TOKEN_INVALID tokenType = iota 34 | _TOKEN_COMMENT 35 | _TOKEN_SECTION 36 | _TOKEN_KEY 37 | ) 38 | 39 | type parser struct { 40 | buf *bufio.Reader 41 | isEOF bool 42 | count int 43 | comment *bytes.Buffer 44 | } 45 | 46 | func newParser(r io.Reader) *parser { 47 | return &parser{ 48 | buf: bufio.NewReader(r), 49 | count: 1, 50 | comment: &bytes.Buffer{}, 51 | } 52 | } 53 | 54 | // BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format. 55 | // http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding 56 | func (p *parser) BOM() error { 57 | mask, err := p.buf.Peek(2) 58 | if err != nil && err != io.EOF { 59 | return err 60 | } else if len(mask) < 2 { 61 | return nil 62 | } 63 | 64 | switch { 65 | case mask[0] == 254 && mask[1] == 255: 66 | fallthrough 67 | case mask[0] == 255 && mask[1] == 254: 68 | p.buf.Read(mask) 69 | case mask[0] == 239 && mask[1] == 187: 70 | mask, err := p.buf.Peek(3) 71 | if err != nil && err != io.EOF { 72 | return err 73 | } else if len(mask) < 3 { 74 | return nil 75 | } 76 | if mask[2] == 191 { 77 | p.buf.Read(mask) 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func (p *parser) readUntil(delim byte) ([]byte, error) { 84 | data, err := p.buf.ReadBytes(delim) 85 | if err != nil { 86 | if err == io.EOF { 87 | p.isEOF = true 88 | } else { 89 | return nil, err 90 | } 91 | } 92 | return data, nil 93 | } 94 | 95 | func cleanComment(in []byte) ([]byte, bool) { 96 | i := bytes.IndexAny(in, "#;") 97 | if i == -1 { 98 | return nil, false 99 | } 100 | return in[i:], true 101 | } 102 | 103 | func readKeyName(in []byte) (string, int, error) { 104 | line := string(in) 105 | 106 | // Check if key name surrounded by quotes. 107 | var keyQuote string 108 | if line[0] == '"' { 109 | if len(line) > 6 && string(line[0:3]) == `"""` { 110 | keyQuote = `"""` 111 | } else { 112 | keyQuote = `"` 113 | } 114 | } else if line[0] == '`' { 115 | keyQuote = "`" 116 | } 117 | 118 | // Get out key name 119 | endIdx := -1 120 | if len(keyQuote) > 0 { 121 | startIdx := len(keyQuote) 122 | // FIXME: fail case -> """"""name"""=value 123 | pos := strings.Index(line[startIdx:], keyQuote) 124 | if pos == -1 { 125 | return "", -1, fmt.Errorf("missing closing key quote: %s", line) 126 | } 127 | pos += startIdx 128 | 129 | // Find key-value delimiter 130 | i := strings.IndexAny(line[pos+startIdx:], "=:") 131 | if i < 0 { 132 | return "", -1, ErrDelimiterNotFound{line} 133 | } 134 | endIdx = pos + i 135 | return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil 136 | } 137 | 138 | endIdx = strings.IndexAny(line, "=:") 139 | if endIdx < 0 { 140 | return "", -1, ErrDelimiterNotFound{line} 141 | } 142 | return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil 143 | } 144 | 145 | func (p *parser) readMultilines(line, val, valQuote string) (string, error) { 146 | for { 147 | data, err := p.readUntil('\n') 148 | if err != nil { 149 | return "", err 150 | } 151 | next := string(data) 152 | 153 | pos := strings.LastIndex(next, valQuote) 154 | if pos > -1 { 155 | val += next[:pos] 156 | 157 | comment, has := cleanComment([]byte(next[pos:])) 158 | if has { 159 | p.comment.Write(bytes.TrimSpace(comment)) 160 | } 161 | break 162 | } 163 | val += next 164 | if p.isEOF { 165 | return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next) 166 | } 167 | } 168 | return val, nil 169 | } 170 | 171 | func (p *parser) readContinuationLines(val string) (string, error) { 172 | for { 173 | data, err := p.readUntil('\n') 174 | if err != nil { 175 | return "", err 176 | } 177 | next := strings.TrimSpace(string(data)) 178 | 179 | if len(next) == 0 { 180 | break 181 | } 182 | val += next 183 | if val[len(val)-1] != '\\' { 184 | break 185 | } 186 | val = val[:len(val)-1] 187 | } 188 | return val, nil 189 | } 190 | 191 | // hasSurroundedQuote check if and only if the first and last characters 192 | // are quotes \" or \'. 193 | // It returns false if any other parts also contain same kind of quotes. 194 | func hasSurroundedQuote(in string, quote byte) bool { 195 | return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote && 196 | strings.IndexByte(in[1:], quote) == len(in)-2 197 | } 198 | 199 | func (p *parser) readValue(in []byte, 200 | parserBufferSize int, 201 | ignoreContinuation, ignoreInlineComment, unescapeValueDoubleQuotes, unescapeValueCommentSymbols, allowPythonMultilines, spaceBeforeInlineComment bool) (string, error) { 202 | 203 | line := strings.TrimLeftFunc(string(in), unicode.IsSpace) 204 | if len(line) == 0 { 205 | return "", nil 206 | } 207 | 208 | var valQuote string 209 | if len(line) > 3 && string(line[0:3]) == `"""` { 210 | valQuote = `"""` 211 | } else if line[0] == '`' { 212 | valQuote = "`" 213 | } else if unescapeValueDoubleQuotes && line[0] == '"' { 214 | valQuote = `"` 215 | } 216 | 217 | if len(valQuote) > 0 { 218 | startIdx := len(valQuote) 219 | pos := strings.LastIndex(line[startIdx:], valQuote) 220 | // Check for multi-line value 221 | if pos == -1 { 222 | return p.readMultilines(line, line[startIdx:], valQuote) 223 | } 224 | 225 | if unescapeValueDoubleQuotes && valQuote == `"` { 226 | return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil 227 | } 228 | return line[startIdx : pos+startIdx], nil 229 | } 230 | 231 | lastChar := line[len(line)-1] 232 | // Won't be able to reach here if value only contains whitespace 233 | line = strings.TrimSpace(line) 234 | trimmedLastChar := line[len(line)-1] 235 | 236 | // Check continuation lines when desired 237 | if !ignoreContinuation && trimmedLastChar == '\\' { 238 | return p.readContinuationLines(line[:len(line)-1]) 239 | } 240 | 241 | // Check if ignore inline comment 242 | if !ignoreInlineComment { 243 | var i int 244 | if spaceBeforeInlineComment { 245 | i = strings.Index(line, " #") 246 | if i == -1 { 247 | i = strings.Index(line, " ;") 248 | } 249 | 250 | } else { 251 | i = strings.IndexAny(line, "#;") 252 | } 253 | 254 | if i > -1 { 255 | p.comment.WriteString(line[i:]) 256 | line = strings.TrimSpace(line[:i]) 257 | } 258 | 259 | } 260 | 261 | // Trim single and double quotes 262 | if hasSurroundedQuote(line, '\'') || 263 | hasSurroundedQuote(line, '"') { 264 | line = line[1 : len(line)-1] 265 | } else if len(valQuote) == 0 && unescapeValueCommentSymbols { 266 | if strings.Contains(line, `\;`) { 267 | line = strings.Replace(line, `\;`, ";", -1) 268 | } 269 | if strings.Contains(line, `\#`) { 270 | line = strings.Replace(line, `\#`, "#", -1) 271 | } 272 | } else if allowPythonMultilines && lastChar == '\n' { 273 | parserBufferPeekResult, _ := p.buf.Peek(parserBufferSize) 274 | peekBuffer := bytes.NewBuffer(parserBufferPeekResult) 275 | 276 | identSize := -1 277 | val := line 278 | 279 | for { 280 | peekData, peekErr := peekBuffer.ReadBytes('\n') 281 | if peekErr != nil { 282 | if peekErr == io.EOF { 283 | return val, nil 284 | } 285 | return "", peekErr 286 | } 287 | 288 | peekMatches := pythonMultiline.FindStringSubmatch(string(peekData)) 289 | if len(peekMatches) != 3 { 290 | return val, nil 291 | } 292 | 293 | currentIdentSize := len(peekMatches[1]) 294 | // NOTE: Return if not a python-ini multi-line value. 295 | if currentIdentSize < 0 { 296 | return val, nil 297 | } 298 | identSize = currentIdentSize 299 | 300 | // NOTE: Just advance the parser reader (buffer) in-sync with the peek buffer. 301 | _, err := p.readUntil('\n') 302 | if err != nil { 303 | return "", err 304 | } 305 | 306 | val += fmt.Sprintf("\n%s", peekMatches[2]) 307 | } 308 | 309 | // NOTE: If it was a Python multi-line value, 310 | // return the appended value. 311 | if identSize > 0 { 312 | return val, nil 313 | } 314 | } 315 | 316 | return line, nil 317 | } 318 | 319 | // parse parses data through an io.Reader. 320 | func (f *File) parse(reader io.Reader) (err error) { 321 | p := newParser(reader) 322 | if err = p.BOM(); err != nil { 323 | return fmt.Errorf("BOM: %v", err) 324 | } 325 | 326 | // Ignore error because default section name is never empty string. 327 | name := DEFAULT_SECTION 328 | if f.options.Insensitive { 329 | name = strings.ToLower(DEFAULT_SECTION) 330 | } 331 | section, _ := f.NewSection(name) 332 | 333 | // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key 334 | var isLastValueEmpty bool 335 | var lastRegularKey *Key 336 | 337 | var line []byte 338 | var inUnparseableSection bool 339 | 340 | // NOTE: Iterate and increase `currentPeekSize` until 341 | // the size of the parser buffer is found. 342 | // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`. 343 | parserBufferSize := 0 344 | // NOTE: Peek 1kb at a time. 345 | currentPeekSize := 1024 346 | 347 | if f.options.AllowPythonMultilineValues { 348 | for { 349 | peekBytes, _ := p.buf.Peek(currentPeekSize) 350 | peekBytesLength := len(peekBytes) 351 | 352 | if parserBufferSize >= peekBytesLength { 353 | break 354 | } 355 | 356 | currentPeekSize *= 2 357 | parserBufferSize = peekBytesLength 358 | } 359 | } 360 | 361 | for !p.isEOF { 362 | line, err = p.readUntil('\n') 363 | if err != nil { 364 | return err 365 | } 366 | 367 | if f.options.AllowNestedValues && 368 | isLastValueEmpty && len(line) > 0 { 369 | if line[0] == ' ' || line[0] == '\t' { 370 | lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) 371 | continue 372 | } 373 | } 374 | 375 | line = bytes.TrimLeftFunc(line, unicode.IsSpace) 376 | if len(line) == 0 { 377 | continue 378 | } 379 | 380 | // Comments 381 | if line[0] == '#' || line[0] == ';' { 382 | // Note: we do not care ending line break, 383 | // it is needed for adding second line, 384 | // so just clean it once at the end when set to value. 385 | p.comment.Write(line) 386 | continue 387 | } 388 | 389 | // Section 390 | if line[0] == '[' { 391 | // Read to the next ']' (TODO: support quoted strings) 392 | closeIdx := bytes.LastIndexByte(line, ']') 393 | if closeIdx == -1 { 394 | return fmt.Errorf("unclosed section: %s", line) 395 | } 396 | 397 | name := string(line[1:closeIdx]) 398 | section, err = f.NewSection(name) 399 | if err != nil { 400 | return err 401 | } 402 | 403 | comment, has := cleanComment(line[closeIdx+1:]) 404 | if has { 405 | p.comment.Write(comment) 406 | } 407 | 408 | section.Comment = strings.TrimSpace(p.comment.String()) 409 | 410 | // Reset aotu-counter and comments 411 | p.comment.Reset() 412 | p.count = 1 413 | 414 | inUnparseableSection = false 415 | for i := range f.options.UnparseableSections { 416 | if f.options.UnparseableSections[i] == name || 417 | (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) { 418 | inUnparseableSection = true 419 | continue 420 | } 421 | } 422 | continue 423 | } 424 | 425 | if inUnparseableSection { 426 | section.isRawSection = true 427 | section.rawBody += string(line) 428 | continue 429 | } 430 | 431 | kname, offset, err := readKeyName(line) 432 | if err != nil { 433 | // Treat as boolean key when desired, and whole line is key name. 434 | if IsErrDelimiterNotFound(err) { 435 | switch { 436 | case f.options.AllowBooleanKeys: 437 | kname, err := p.readValue(line, 438 | parserBufferSize, 439 | f.options.IgnoreContinuation, 440 | f.options.IgnoreInlineComment, 441 | f.options.UnescapeValueDoubleQuotes, 442 | f.options.UnescapeValueCommentSymbols, 443 | f.options.AllowPythonMultilineValues, 444 | f.options.SpaceBeforeInlineComment) 445 | if err != nil { 446 | return err 447 | } 448 | key, err := section.NewBooleanKey(kname) 449 | if err != nil { 450 | return err 451 | } 452 | key.Comment = strings.TrimSpace(p.comment.String()) 453 | p.comment.Reset() 454 | continue 455 | 456 | case f.options.SkipUnrecognizableLines: 457 | continue 458 | } 459 | } 460 | return err 461 | } 462 | 463 | // Auto increment. 464 | isAutoIncr := false 465 | if kname == "-" { 466 | isAutoIncr = true 467 | kname = "#" + strconv.Itoa(p.count) 468 | p.count++ 469 | } 470 | 471 | value, err := p.readValue(line[offset:], 472 | parserBufferSize, 473 | f.options.IgnoreContinuation, 474 | f.options.IgnoreInlineComment, 475 | f.options.UnescapeValueDoubleQuotes, 476 | f.options.UnescapeValueCommentSymbols, 477 | f.options.AllowPythonMultilineValues, 478 | f.options.SpaceBeforeInlineComment) 479 | if err != nil { 480 | return err 481 | } 482 | isLastValueEmpty = len(value) == 0 483 | 484 | key, err := section.NewKey(kname, value) 485 | if err != nil { 486 | return err 487 | } 488 | key.isAutoIncrement = isAutoIncr 489 | key.Comment = strings.TrimSpace(p.comment.String()) 490 | p.comment.Reset() 491 | lastRegularKey = key 492 | } 493 | return nil 494 | } 495 | -------------------------------------------------------------------------------- /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 | // SetBody updates body content only if section is raw. 58 | func (s *Section) SetBody(body string) { 59 | if !s.isRawSection { 60 | return 61 | } 62 | s.rawBody = body 63 | } 64 | 65 | // NewKey creates a new key to given section. 66 | func (s *Section) NewKey(name, val string) (*Key, error) { 67 | if len(name) == 0 { 68 | return nil, errors.New("error creating new key: empty key name") 69 | } else if s.f.options.Insensitive { 70 | name = strings.ToLower(name) 71 | } 72 | 73 | if s.f.BlockMode { 74 | s.f.lock.Lock() 75 | defer s.f.lock.Unlock() 76 | } 77 | 78 | if inSlice(name, s.keyList) { 79 | if s.f.options.AllowShadows { 80 | if err := s.keys[name].addShadow(val); err != nil { 81 | return nil, err 82 | } 83 | } else { 84 | s.keys[name].value = val 85 | s.keysHash[name] = val 86 | } 87 | return s.keys[name], nil 88 | } 89 | 90 | s.keyList = append(s.keyList, name) 91 | s.keys[name] = newKey(s, name, val) 92 | s.keysHash[name] = val 93 | return s.keys[name], nil 94 | } 95 | 96 | // NewBooleanKey creates a new boolean type key to given section. 97 | func (s *Section) NewBooleanKey(name string) (*Key, error) { 98 | key, err := s.NewKey(name, "true") 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | key.isBooleanType = true 104 | return key, nil 105 | } 106 | 107 | // GetKey returns key in section by given name. 108 | func (s *Section) GetKey(name string) (*Key, error) { 109 | // FIXME: change to section level lock? 110 | if s.f.BlockMode { 111 | s.f.lock.RLock() 112 | } 113 | if s.f.options.Insensitive { 114 | name = strings.ToLower(name) 115 | } 116 | key := s.keys[name] 117 | if s.f.BlockMode { 118 | s.f.lock.RUnlock() 119 | } 120 | 121 | if key == nil { 122 | // Check if it is a child-section. 123 | sname := s.name 124 | for { 125 | if i := strings.LastIndex(sname, "."); i > -1 { 126 | sname = sname[:i] 127 | sec, err := s.f.GetSection(sname) 128 | if err != nil { 129 | continue 130 | } 131 | return sec.GetKey(name) 132 | } else { 133 | break 134 | } 135 | } 136 | return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name) 137 | } 138 | return key, nil 139 | } 140 | 141 | // HasKey returns true if section contains a key with given name. 142 | func (s *Section) HasKey(name string) bool { 143 | key, _ := s.GetKey(name) 144 | return key != nil 145 | } 146 | 147 | // Haskey is a backwards-compatible name for HasKey. 148 | // TODO: delete me in v2 149 | func (s *Section) Haskey(name string) bool { 150 | return s.HasKey(name) 151 | } 152 | 153 | // HasValue returns true if section contains given raw value. 154 | func (s *Section) HasValue(value string) bool { 155 | if s.f.BlockMode { 156 | s.f.lock.RLock() 157 | defer s.f.lock.RUnlock() 158 | } 159 | 160 | for _, k := range s.keys { 161 | if value == k.value { 162 | return true 163 | } 164 | } 165 | return false 166 | } 167 | 168 | // Key assumes named Key exists in section and returns a zero-value when not. 169 | func (s *Section) Key(name string) *Key { 170 | key, err := s.GetKey(name) 171 | if err != nil { 172 | // It's OK here because the only possible error is empty key name, 173 | // but if it's empty, this piece of code won't be executed. 174 | key, _ = s.NewKey(name, "") 175 | return key 176 | } 177 | return key 178 | } 179 | 180 | // Keys returns list of keys of section. 181 | func (s *Section) Keys() []*Key { 182 | keys := make([]*Key, len(s.keyList)) 183 | for i := range s.keyList { 184 | keys[i] = s.Key(s.keyList[i]) 185 | } 186 | return keys 187 | } 188 | 189 | // ParentKeys returns list of keys of parent section. 190 | func (s *Section) ParentKeys() []*Key { 191 | var parentKeys []*Key 192 | sname := s.name 193 | for { 194 | if i := strings.LastIndex(sname, "."); i > -1 { 195 | sname = sname[:i] 196 | sec, err := s.f.GetSection(sname) 197 | if err != nil { 198 | continue 199 | } 200 | parentKeys = append(parentKeys, sec.Keys()...) 201 | } else { 202 | break 203 | } 204 | 205 | } 206 | return parentKeys 207 | } 208 | 209 | // KeyStrings returns list of key names of section. 210 | func (s *Section) KeyStrings() []string { 211 | list := make([]string, len(s.keyList)) 212 | copy(list, s.keyList) 213 | return list 214 | } 215 | 216 | // KeysHash returns keys hash consisting of names and values. 217 | func (s *Section) KeysHash() map[string]string { 218 | if s.f.BlockMode { 219 | s.f.lock.RLock() 220 | defer s.f.lock.RUnlock() 221 | } 222 | 223 | hash := map[string]string{} 224 | for key, value := range s.keysHash { 225 | hash[key] = value 226 | } 227 | return hash 228 | } 229 | 230 | // DeleteKey deletes a key from section. 231 | func (s *Section) DeleteKey(name string) { 232 | if s.f.BlockMode { 233 | s.f.lock.Lock() 234 | defer s.f.lock.Unlock() 235 | } 236 | 237 | for i, k := range s.keyList { 238 | if k == name { 239 | s.keyList = append(s.keyList[:i], s.keyList[i+1:]...) 240 | delete(s.keys, name) 241 | return 242 | } 243 | } 244 | } 245 | 246 | // ChildSections returns a list of child sections of current section. 247 | // For example, "[parent.child1]" and "[parent.child12]" are child sections 248 | // of section "[parent]". 249 | func (s *Section) ChildSections() []*Section { 250 | prefix := s.name + "." 251 | children := make([]*Section, 0, 3) 252 | for _, name := range s.f.sectionList { 253 | if strings.HasPrefix(name, prefix) { 254 | children = append(children, s.f.sections[name]) 255 | } 256 | } 257 | return children 258 | } 259 | -------------------------------------------------------------------------------- /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 err != nil && 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 && int64(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 reflect.Interface, reflect.Ptr: 412 | return v.IsNil() 413 | case reflectTime: 414 | t, ok := v.Interface().(time.Time) 415 | return ok && t.IsZero() 416 | } 417 | return false 418 | } 419 | 420 | func (s *Section) reflectFrom(val reflect.Value) error { 421 | if val.Kind() == reflect.Ptr { 422 | val = val.Elem() 423 | } 424 | typ := val.Type() 425 | 426 | for i := 0; i < typ.NumField(); i++ { 427 | field := val.Field(i) 428 | tpField := typ.Field(i) 429 | 430 | tag := tpField.Tag.Get("ini") 431 | if tag == "-" { 432 | continue 433 | } 434 | 435 | opts := strings.SplitN(tag, ",", 2) 436 | if len(opts) == 2 && opts[1] == "omitempty" && isEmptyValue(field) { 437 | continue 438 | } 439 | 440 | fieldName := s.parseFieldName(tpField.Name, opts[0]) 441 | if len(fieldName) == 0 || !field.CanSet() { 442 | continue 443 | } 444 | 445 | if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) || 446 | (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { 447 | // Note: The only error here is section doesn't exist. 448 | sec, err := s.f.GetSection(fieldName) 449 | if err != nil { 450 | // Note: fieldName can never be empty here, ignore error. 451 | sec, _ = s.f.NewSection(fieldName) 452 | } 453 | 454 | // Add comment from comment tag 455 | if len(sec.Comment) == 0 { 456 | sec.Comment = tpField.Tag.Get("comment") 457 | } 458 | 459 | if err = sec.reflectFrom(field); err != nil { 460 | return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) 461 | } 462 | continue 463 | } 464 | 465 | // Note: Same reason as secion. 466 | key, err := s.GetKey(fieldName) 467 | if err != nil { 468 | key, _ = s.NewKey(fieldName, "") 469 | } 470 | 471 | // Add comment from comment tag 472 | if len(key.Comment) == 0 { 473 | key.Comment = tpField.Tag.Get("comment") 474 | } 475 | 476 | if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim"))); err != nil { 477 | return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) 478 | } 479 | 480 | } 481 | return nil 482 | } 483 | 484 | // ReflectFrom reflects secion from given struct. 485 | func (s *Section) ReflectFrom(v interface{}) error { 486 | typ := reflect.TypeOf(v) 487 | val := reflect.ValueOf(v) 488 | if typ.Kind() == reflect.Ptr { 489 | typ = typ.Elem() 490 | val = val.Elem() 491 | } else { 492 | return errors.New("cannot reflect from non-pointer struct") 493 | } 494 | 495 | return s.reflectFrom(val) 496 | } 497 | 498 | // ReflectFrom reflects file from given struct. 499 | func (f *File) ReflectFrom(v interface{}) error { 500 | return f.Section("").ReflectFrom(v) 501 | } 502 | 503 | // ReflectFrom reflects data sources from given struct with name mapper. 504 | func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error { 505 | cfg.NameMapper = mapper 506 | return cfg.ReflectFrom(v) 507 | } 508 | 509 | // ReflectFrom reflects data sources from given struct. 510 | func ReflectFrom(cfg *File, v interface{}) error { 511 | return ReflectFromWithMapper(cfg, v, nil) 512 | } 513 | --------------------------------------------------------------------------------