├── .dockerignore ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Dockerfile.test ├── LICENSE ├── README.md ├── build.sh ├── docker-compose.yml ├── go ├── cmd │ └── ccql │ │ └── main.go ├── logic │ └── ccql.go ├── sql │ ├── query.go │ └── query_test.go └── text │ ├── hosts.go │ └── hosts_test.go ├── script ├── bootstrap ├── build ├── build-deploy-tarball ├── cibuild ├── cibuild-ccql-build-deploy-tarball ├── ensure-go-installed ├── go └── test └── vendor ├── github.com ├── go-sql-driver │ └── mysql │ │ ├── .gitignore │ │ ├── .travis.yml │ │ ├── AUTHORS │ │ ├── CHANGELOG.md │ │ ├── CONTRIBUTING.md │ │ ├── LICENSE │ │ ├── README.md │ │ ├── appengine.go │ │ ├── buffer.go │ │ ├── collations.go │ │ ├── connection.go │ │ ├── const.go │ │ ├── driver.go │ │ ├── errors.go │ │ ├── infile.go │ │ ├── packets.go │ │ ├── result.go │ │ ├── rows.go │ │ ├── statement.go │ │ ├── transaction.go │ │ └── utils.go └── outbrain │ └── golib │ ├── log │ └── log.go │ └── sqlutils │ └── sqlutils.go ├── golang.org └── x │ └── crypto │ └── ssh │ └── terminal │ ├── terminal.go │ ├── terminal_test.go │ ├── util.go │ ├── util_bsd.go │ ├── util_linux.go │ ├── util_plan9.go │ └── util_windows.go └── gopkg.in └── gcfg.v1 ├── LICENSE ├── README ├── doc.go ├── example_test.go ├── go1_0.go ├── go1_2.go ├── issues_test.go ├── read.go ├── read_test.go ├── scanner ├── errors.go ├── example_test.go ├── scanner.go └── scanner_test.go ├── set.go ├── testdata ├── gcfg_test.gcfg └── gcfg_unicode_test.gcfg ├── token ├── position.go ├── position_test.go ├── serialize.go ├── serialize_test.go └── token.go └── types ├── bool.go ├── doc.go ├── enum.go ├── enum_test.go ├── int.go ├── int_test.go ├── scan.go └── scan_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | .gopath 2 | .cache 3 | Dockerfile* 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@master 12 | 13 | - name: Set up Go 1.12 14 | uses: actions/setup-go@v1 15 | with: 16 | version: 1.12 17 | id: go 18 | 19 | - name: Build 20 | run: script/cibuild 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gopath/ 2 | .vendor/ 3 | bin/ 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # http://docs.travis-ci.com/user/languages/go/ 2 | language: go 3 | 4 | go: 5 | - 1.12.x 6 | 7 | os: 8 | - linux 9 | 10 | install: true 11 | 12 | script: script/cibuild 13 | 14 | notifications: 15 | email: false 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 4 | 5 | This project adheres to the [Open Code of Conduct](http://todogroup.org/opencodeofconduct/#ccql/opensource@github.com). By participating, you are expected to uphold this code. 6 | 7 | ## Submitting a pull request 8 | 9 | 0. [Fork](https://github.com/github/ccql/fork) and clone the repository 10 | 0. Create a new branch: `git checkout -b my-branch-name` 11 | 0. Make your change, add tests, and make sure the tests still pass 12 | 0. Push to your fork and [submit a pull request](https://github.com/github/ccql/compare) 13 | 0. Pat your self on the back and wait for your pull request to be reviewed and merged. 14 | 15 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 16 | 17 | - Follow the [style guide](https://golang.org/doc/effective_go.html#formatting). 18 | - Write tests. 19 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 20 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 21 | 22 | ## Resources 23 | 24 | - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/) 25 | - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/) 26 | - [GitHub Help](https://help.github.com) 27 | -------------------------------------------------------------------------------- /Dockerfile.test: -------------------------------------------------------------------------------- 1 | FROM golang:1.12.1 2 | LABEL maintainer="github@github.com" 3 | 4 | RUN apt-get update 5 | RUN apt-get install -y lsb-release 6 | RUN rm -rf /var/lib/apt/lists/* 7 | 8 | COPY . /go/src/github.com/github/ccql 9 | WORKDIR /go/src/github.com/github/ccql 10 | 11 | CMD ["script/test"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 GitHub Inc 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ccql 2 | 3 | [![downloads](https://img.shields.io/github/downloads/github/ccql/total.svg)](https://github.com/github/ccql/releases/latest) 4 | 5 | #### Concurrent, multi server MySQL client 6 | 7 | `ccql` is a simple executable utility which executes a given set of queries on a given set of MySQL hosts 8 | in parallel. 9 | 10 | Quick example: 11 | ``` 12 | echo "my.srv1.com my.srv2.com my.srv3.com" | ccql -q "show master status; select @@server_id" -u myuser -p 123456 13 | ``` 14 | 15 | ## Usage 16 | 17 | ``` 18 | Usage of ccql: 19 | -C string 20 | Credentials file, expecting [client] scope, with 'user', 'password' fields. Overrides -u and -p 21 | -H string 22 | Hosts file, hostname[:port] comma or space or newline delimited format. If not given, hosts read from stdin 23 | -Q string 24 | Query/queries input file 25 | -d string 26 | Default schema to use (default "information_schema") 27 | -h string 28 | Comma or space delimited list of hosts in hostname[:port] format. If not given, hosts read from stdin 29 | -m uint 30 | Max concurrent connections (default 32) 31 | -p string 32 | MySQL password 33 | -q string 34 | Query/queries to execute 35 | -s string 36 | List of databases to query from; overrides -d, prints schema name to output 37 | -t float 38 | Connect timeout seconds 39 | -u string 40 | MySQL username (default OS user) 41 | ``` 42 | 43 | #### Hosts input 44 | 45 | You may provide a list of hosts in the following ways: 46 | - via `-h my.srv1.com:3307 my.srv2.com my.srv3.com` 47 | - via `-H /path/to/hosts.txt` 48 | - via _stdin_, as in `echo "my.srv1.com:3307 my.srv2.com my.srv3.com" | ccql ...` 49 | 50 | Hostnames can be separated by spaces, commas, newline characters or all the above. 51 | They may indicate a port. The default port, if unspecified, is `3306` 52 | 53 | #### Queries input 54 | 55 | You may provide a query or a list of queries in the following ways: 56 | - single query, `-q "select @@global.server_id"` 57 | - multiple queries, semicolon delimited: `-q "select @@global.server_id; set global slave_net_timeout:=10"` 58 | - single or multiple queries from text file: `-Q /path/to/queries.sql` 59 | 60 | Queries are delimited by a semicolon (`;`). The last query may, but does not have to, be terminated by a semicolon. 61 | Quotes are respected, up to a reasonable level. It is valid to include a semicolon in a quoted text, as in `select 'single;query'`. However `ccql` does not employ a full blown parser, so please don't overdo it. For example, the following may not be parsed correctly: `select '\';\''`. You get it. 62 | 63 | #### Schemas 64 | 65 | You may either provide: 66 | 67 | - An implicit, default schema via `-d schema_name` 68 | - Schema name is not visible on output. 69 | - Or explicit list of schemas via `-s "schema_1,schema_2[,schema_3...]"` (overrides `-d`) 70 | - Queries are executed per host, per schema. 71 | - Schema name printed as output column. 72 | 73 | #### Credentials input 74 | 75 | You may provide credentials in the following ways: 76 | - via `-u myusername -p mypassword` (default username is your OS user; default password is empty) 77 | - via credentials file: `-C /path/to/.my.cnf`. File must be in the following format: 78 | ``` 79 | [client] 80 | user=myuser 81 | password=mypassword 82 | ``` 83 | 84 | #### Execution 85 | 86 | Hosts are executed in parallel, with up to `128` concurrent executions (otherwise more hosts are accepted but wait in queue). 87 | For each host, the set of queries executes sequentially. Error on any query terminates execution of that host. 88 | Errors are isolated to hosts; an error while connecting or executing on host1 should not affect execution on host2. 89 | 90 | #### Output 91 | 92 | There is only output generated for queries that provide an output, typically `SELECT` queries. Queries such as 93 | `SET GLOBAL...` or `FLUSH BINARY LOGS` or `CREATE DATABASE ...` do not generate and output. 94 | 95 | Output is written to _stdout_. It is tab delimited. There is one output line per row returning from either query. 96 | The first printed token is the fully qualified `hostname:port` of the instance whose query output is printed. 97 | Remember that execution happens concurrently on multiple hosts. Output rows are therefore ordered arbitrarily 98 | in between hosts, though deterministically for any specific host. 99 | Other tokens are whatever columns were returned by the queries. 100 | 101 | ## More examples 102 | 103 | Some examples dealing with replication follow. Combining shell scripting we can have some real fun. 104 | For brevity, we assume `/tmp/hosts.txt` contains a list of servers, as follows: 105 | ``` 106 | echo "localhost:22293, localhost:22294, localhost:22295, localhost:22296" > /tmp/hosts.txt 107 | ``` 108 | (note that hosts can be separated by spaces, commas, newlines or any combination) 109 | 110 | We also assume credentials are stored in `/etc/ccql.cnf`: 111 | ``` 112 | [client] 113 | user=msandbox 114 | password=msandbox 115 | ``` 116 | 117 | Warmup: select some stuff 118 | ``` 119 | cat /tmp/hosts.txt | ccql -C /etc/ccql.cnf -q "select @@global.server_id, @@global.binlog_format, @@global.version" 120 | ``` 121 | A sample output is: 122 | ``` 123 | localhost:22296 103 STATEMENT 5.6.28 124 | localhost:22294 101 STATEMENT 5.6.28-log 125 | localhost:22293 1 STATEMENT 5.6.28-log 126 | localhost:22295 102 STATEMENT 5.6.28-log 127 | ``` 128 | The output is tab delimited. 129 | 130 | Show only servers that are configured as replicas: 131 | ``` 132 | cat /tmp/hosts.txt | ccql -C /etc/ccql.cnf -q "show slave status" | awk '{print $1}' 133 | ``` 134 | Apply `slave_net_timeout` only on replicas: 135 | ``` 136 | cat /tmp/hosts.txt | ccql -C /etc/ccql.cnf -q "show slave status;" | awk '{print $1}' | ccql -C /etc/ccql.cnf -q "set global slave_net_timeout := 10" 137 | ``` 138 | 139 | Getting tired of typing `ccql -C /etc/ccql.cnf`? Let's make a shortcut: 140 | ``` 141 | alias ccql="ccql -C /etc/ccql.cnf" 142 | ``` 143 | 144 | Which servers are acting as masters to someone? 145 | ``` 146 | cat /tmp/hosts.txt | ccql -q "show slave status;" | awk -F $'\t' '{print $3 ":" $5}' 147 | ``` 148 | 149 | Of those, which are also replicating? i.e. act as intermediate masters? 150 | ``` 151 | cat /tmp/hosts.txt | ccql -q "show slave status;" | awk -F $'\t' '{print $3 ":" $5}' | sort | uniq | ccql -q "show slave status" | awk '{print $1}' 152 | ``` 153 | 154 | Set `sync_binlog=0` on all intermediate masters: 155 | ``` 156 | cat /tmp/hosts.txt | ccql -q "show slave status;" | awk -F $'\t' '{print $3 ":" $5}' | sort | uniq | ccql -q "show slave status" | awk '{print $1}' | ccql -q "set global sync_binlog=0" 157 | ``` 158 | 159 | Multiple schemas: 160 | 161 | ```shell 162 | $ cat /tmp/hosts.txt | ccql -t 0.5 -s "test,meta" -q "select uuid() from dual" | column -t 163 | host3:3306 test d0d95311-b8ad-11e7-81e7-008cfa542442 164 | host2:3306 meta d0d95311-b8ad-11e7-a16c-a0369fb3dc94 165 | host2:3306 test d0d95fd6-b8ad-11e7-9a23-008cfa544064 166 | host1:3306 meta d0d95311-b8ad-11e7-9a15-a0369fb5fdd0 167 | host3:3306 meta d0d95311-b8ad-11e7-bd26-a0369fb5f3d8 168 | host4:3306 meta d0d95311-b8ad-11e7-a16c-a0369fb3dc94 169 | host1:3306 test d0d96924-b8ad-11e7-9bde-008cfa5440e4 170 | host4:3306 test d0d99a9d-b8ad-11e7-a680-008cfa542c9e 171 | ``` 172 | 173 | ## LICENSE 174 | 175 | See [LICENSE](LICENSE). _ccql_ imports and includes 3rd party libraries, which have their own license. These are found under [vendor](vendor). 176 | 177 | ## Binaries, downloads 178 | 179 | Find precompiled binaries for linux (amd64) and Darwin (aka OS/X, amd64) under [Releases](https://github.com/github/ccql/releases) 180 | 181 | ## Build 182 | 183 | _ccql_ is built with Go 1.6/1.7, and supports 1.5 with the [Go 1.5 vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories), which requires setting `GO15VENDOREXPERIMENT=1`. 184 | Please see the [build file](build.sh) 185 | 186 | ## What's in a name? 187 | 188 | _ccql_ is an abbreviation for _Concurrent Client for MySQL_ or something. We had a few iterations with the name 189 | but had to replace one and we were all like _yeah_ and _whoa_ and fun times. Eventually we came by this name 190 | which upset [tomkrouper](https://github.com/tomkrouper) being "too much on the left-side of the keyboard when typing" and that settled the matter. 191 | Tom uses `alias a='ccql'`. 192 | 193 | ## Notes 194 | 195 | - Credits to Domas Mituzas for creating [pmysql](http://dom.as/2010/08/12/pmysql-multi-server-mysql-client/). 196 | This project mostly reimplements `pmysql` and delivers it in an easy to redistribute format. 197 | 198 | - Pronounce "see-sequel" 199 | 200 | - This project is open to [contributions](CONTRIBUTING.md). Generally speaking it should be kept small 201 | and simple. 202 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | buildpath=/tmp/ 4 | target=ccql 5 | timestamp=$(date "+%Y%m%d%H%M%S") 6 | gobuild="go build -o $buildpath/$target go/cmd/ccql/main.go" 7 | 8 | echo "Building linux binary" 9 | echo "GO15VENDOREXPERIMENT=1 GOOS=linux GOARCH=amd64 $gobuild" | bash 10 | (cd $buildpath && tar cfz ./ccql-binary-linux-${timestamp}.tar.gz $target) 11 | 12 | echo "Building OS/X binary" 13 | echo "GO15VENDOREXPERIMENT=1 GOOS=darwin GOARCH=amd64 $gobuild" | bash 14 | (cd $buildpath && tar cfz ./ccql-binary-osx-${timestamp}.tar.gz $target) 15 | 16 | echo "Built with $(go version)" 17 | 18 | echo "Binaries found in:" 19 | ls -1 $buildpath/ccql-binary*${timestamp}.tar.gz 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.5" 2 | services: 3 | app: 4 | image: app 5 | build: 6 | context: . 7 | dockerfile: Dockerfile.test 8 | -------------------------------------------------------------------------------- /go/cmd/ccql/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/user" 9 | "syscall" 10 | 11 | "github.com/github/ccql/go/logic" 12 | "github.com/github/ccql/go/sql" 13 | "github.com/github/ccql/go/text" 14 | "golang.org/x/crypto/ssh/terminal" 15 | 16 | golib_log "github.com/outbrain/golib/log" 17 | "gopkg.in/gcfg.v1" 18 | ) 19 | 20 | var AppVersion string 21 | 22 | const ( 23 | maxAllowedConcurrentConnections uint = 128 24 | ) 25 | 26 | // main is the application's entry point. It will either spawn a CLI or HTTP interfaces. 27 | func main() { 28 | 29 | golib_log.SetLevel(golib_log.FATAL) 30 | 31 | osUser := "" 32 | // get os username as owner 33 | if usr, err := user.Current(); err == nil { 34 | osUser = usr.Username 35 | } 36 | 37 | help := flag.Bool("help", false, "Display usage") 38 | user := flag.String("u", osUser, "MySQL username") 39 | password := flag.String("p", "", "MySQL password") 40 | askPassword := flag.Bool("ask-pass", false, "prompt for MySQL password") 41 | credentialsFile := flag.String("C", "", "Credentials file, expecting [client] scope, with 'user', 'password' fields. Overrides -u and -p") 42 | defaultSchema := flag.String("d", "information_schema", "Default schema to use") 43 | schemasList := flag.String("s", "", "List of databases to query from; overrides -d, prints schema name to output") 44 | hostsList := flag.String("h", "", "Comma or space delimited list of hosts in hostname[:port] format. If not given, hosts read from stdin") 45 | hostsFile := flag.String("H", "", "Hosts file, hostname[:port] comma or space or newline delimited format. If not given, hosts read from stdin") 46 | queriesText := flag.String("q", "", "Query/queries to execute") 47 | queriesFile := flag.String("Q", "", "Query/queries input file") 48 | timeout := flag.Float64("t", 0, "Connect timeout seconds") 49 | maxConcurrency := flag.Uint("m", 32, "Max concurrent connections") 50 | 51 | flag.Parse() 52 | 53 | if AppVersion == "" { 54 | AppVersion = "local-build" 55 | } 56 | if *help { 57 | fmt.Fprintf(os.Stderr, "Usage of ccql (version: %s):\n", AppVersion) 58 | flag.PrintDefaults() 59 | return 60 | } 61 | if *queriesText == "" && *queriesFile == "" { 62 | fmt.Fprintf(os.Stderr, "You must provide a query via -q '' or via -Q \n") 63 | fmt.Fprintf(os.Stderr, "Usage of ccql:\n") 64 | flag.PrintDefaults() 65 | return 66 | } 67 | if *hostsList != "" && *hostsFile != "" { 68 | log.Fatalf("Both -q and -Q given. Please specify exactly one") 69 | } 70 | queries, err := sql.ParseQueries(*queriesText, *queriesFile) 71 | if err != nil { 72 | log.Fatal(err.Error()) 73 | } 74 | if len(queries) == 0 { 75 | log.Fatalf("No query/queries given") 76 | } 77 | 78 | if *hostsList != "" && *hostsFile != "" { 79 | log.Fatalf("Both -h and -H given. Please specify one of them, or none (in which case stdin is used)") 80 | } 81 | hosts, err := text.ParseHosts(*hostsList, *hostsFile) 82 | if err != nil { 83 | log.Fatal(err.Error()) 84 | } 85 | if len(hosts) == 0 { 86 | log.Fatalf("No hosts given") 87 | } 88 | 89 | if *maxConcurrency > maxAllowedConcurrentConnections { 90 | log.Fatalf("Max concurrent connections (-m) may not exceed %d", maxAllowedConcurrentConnections) 91 | } 92 | if *maxConcurrency < 1 { 93 | *maxConcurrency = 1 94 | } 95 | 96 | if *credentialsFile != "" { 97 | mySQLConfig := struct { 98 | Client struct { 99 | User string 100 | Password string 101 | } 102 | }{} 103 | gcfg.RelaxedParserMode = true 104 | err := gcfg.ReadFileInto(&mySQLConfig, *credentialsFile) 105 | if err != nil { 106 | log.Fatalf("Failed to parse gcfg data from file: %+v", err) 107 | } else { 108 | *user = mySQLConfig.Client.User 109 | *password = mySQLConfig.Client.Password 110 | } 111 | } 112 | 113 | if *askPassword { 114 | fmt.Print("Mysql password: ") 115 | passwd, err := terminal.ReadPassword(syscall.Stdin) 116 | if err != nil { 117 | log.Fatalf("\nError while reading password: %s", err.Error()) 118 | } 119 | *password = string(passwd) 120 | } 121 | 122 | schemas := text.SplitNonEmpty(*schemasList, ",") 123 | 124 | if err := logic.QueryHosts(hosts, *user, *password, *defaultSchema, schemas, queries, *maxConcurrency, *timeout); err != nil { 125 | os.Exit(1) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /go/logic/ccql.go: -------------------------------------------------------------------------------- 1 | package logic 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "strings" 7 | "sync" 8 | 9 | "github.com/outbrain/golib/sqlutils" 10 | ) 11 | 12 | // queryHost connects to a given host, issues the given set of queries, and outputs the results 13 | // line per row in tab delimited format 14 | func queryHost(host string, user string, password string, schema string, queries []string, timeout float64, printSchema bool) error { 15 | mysqlURI := fmt.Sprintf("%s:%s@tcp(%s)/%s?timeout=%fs", user, password, host, schema, timeout) 16 | db, _, err := sqlutils.GetDB(mysqlURI) 17 | if err != nil { 18 | return err 19 | } 20 | for _, query := range queries { 21 | resultData, err := sqlutils.QueryResultData(db, query) 22 | if err != nil { 23 | return err 24 | } 25 | for _, row := range resultData { 26 | output := []string{host} 27 | if printSchema { 28 | output = append(output, schema) 29 | } 30 | for _, rowCell := range row { 31 | output = append(output, rowCell.String) 32 | } 33 | rowOutput := strings.Join(output, "\t") 34 | fmt.Println(rowOutput) 35 | } 36 | } 37 | return nil 38 | } 39 | 40 | // QueryHosts will issue concurrent queries on given list of hosts 41 | func QueryHosts(hosts []string, user string, password string, 42 | defaultSchema string, schemas []string, queries []string, 43 | maxConcurrency uint, timeout float64, 44 | ) (anyError error) { 45 | concurrentQueries := make(chan bool, maxConcurrency) 46 | printSchema := len(schemas) > 0 47 | if len(schemas) == 0 { 48 | schemas = []string{defaultSchema} 49 | } 50 | var wg sync.WaitGroup 51 | for _, host := range hosts { 52 | // For each host, run all queries for the respective schema 53 | for _, schema := range schemas { 54 | wg.Add(1) 55 | go func(host, schema string) { 56 | concurrentQueries <- true 57 | defer func() { <-concurrentQueries }() 58 | defer wg.Done() 59 | if err := queryHost(host, user, password, schema, queries, timeout, printSchema); err != nil { 60 | anyError = err 61 | log.Printf("%s %s", host, err.Error()) 62 | } 63 | }(host, schema) 64 | } 65 | } 66 | 67 | // Barrier. Wait for all to complete 68 | wg.Wait() 69 | 70 | return anyError 71 | } 72 | -------------------------------------------------------------------------------- /go/sql/query.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "io/ioutil" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | // ParseQueries parses multiple queries given a single string. 10 | // Input may be a semicolon delimited set of queries, such as: 11 | // `select 1; show variables; select '2;nd'` 12 | // Empty queries are ignored and skipped. Result is an array of single queries. 13 | func ParseQueries(queriesText string, queriesFile string) (queries []string, err error) { 14 | if queriesFile != "" { 15 | bytes, err := ioutil.ReadFile(queriesFile) 16 | if err != nil { 17 | return queries, err 18 | } 19 | queriesText = string(bytes) 20 | } 21 | 22 | // The following regexp makes for a reasonable (yet incomplete) parses that splits 23 | // by delimiter ";" yet ignores it when it is quoted 24 | r := regexp.MustCompile(`([^;']+|'([^']*)')+[;]?`) 25 | matches := r.FindAllString(queriesText, -1) 26 | for _, match := range matches { 27 | match = strings.TrimSuffix(match, ";") 28 | match = strings.TrimSpace(match) 29 | if match != "" { 30 | queries = append(queries, match) 31 | } 32 | } 33 | return queries, nil 34 | } 35 | -------------------------------------------------------------------------------- /go/sql/query_test.go: -------------------------------------------------------------------------------- 1 | package sql 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestParseQueriesSingle(t *testing.T) { 11 | queriesText := `select 1` 12 | queries, err := ParseQueries(queriesText, "") 13 | 14 | if err != nil { 15 | t.Error(err.Error()) 16 | } 17 | if len(queries) != 1 { 18 | t.Errorf("expected 1 query; got %+v", len(queries)) 19 | } 20 | if queries[0] != `select 1` { 21 | t.Errorf("got unexpected query `%+v`", queries[0]) 22 | } 23 | } 24 | 25 | func TestParseQueriesMulti(t *testing.T) { 26 | useCases := []string{ 27 | `select 1; select 2; select 3`, 28 | `select 1; select 2; select 3 `, 29 | ` select 1; select 2 ; select 3;`, 30 | `select 1; select 2; select 3; `, 31 | `select 1; ;;select 2; select 3;`, 32 | `select 1; 33 | select 2; select 3;`, 34 | } 35 | expected := `select 1;select 2;select 3` 36 | for _, queriesText := range useCases { 37 | queries, err := ParseQueries(queriesText, "") 38 | 39 | if err != nil { 40 | t.Error(err.Error()) 41 | } 42 | if len(queries) != 3 { 43 | t.Errorf("expected 3 queries; got %+v", len(queries)) 44 | } 45 | result := strings.Join(queries, ";") 46 | if result != expected { 47 | t.Errorf("got unexpected results: `%+v`", result) 48 | } 49 | } 50 | } 51 | 52 | func TestParseQueriesEmpty(t *testing.T) { 53 | useCases := []string{ 54 | ``, 55 | ` `, 56 | `;`, 57 | ` ;;; ; ; `, 58 | } 59 | for _, queriesText := range useCases { 60 | queries, err := ParseQueries(queriesText, "") 61 | 62 | if err != nil { 63 | t.Error(err.Error()) 64 | } 65 | if len(queries) != 0 { 66 | t.Errorf("expected 0 queries; got %+v", len(queries)) 67 | } 68 | } 69 | } 70 | 71 | func TestParseQueriesQuotes(t *testing.T) { 72 | queriesText := `select 1; select '2;2' ; select 3` 73 | queries, err := ParseQueries(queriesText, "") 74 | 75 | if err != nil { 76 | t.Error(err.Error()) 77 | } 78 | if len(queries) != 3 { 79 | t.Errorf("expected 3 queries; got %+v", len(queries)) 80 | t.Errorf("%+v", strings.Join(queries, "::")) 81 | } 82 | result := strings.Join(queries, ";") 83 | if result != `select 1;select '2;2';select 3` { 84 | t.Errorf("got unexpected results: `%+v`", result) 85 | } 86 | } 87 | 88 | func TestParseQueriesFile(t *testing.T) { 89 | queriesText := `select 1; select '2;2' ; select 3` 90 | tmpFile, err := ioutil.TempFile(os.TempDir(), "querytest-") 91 | 92 | if err != nil { 93 | t.Errorf("error creating temporary file: %s", err.Error()) 94 | } 95 | 96 | defer os.Remove(tmpFile.Name()) 97 | 98 | if _, err = tmpFile.Write([]byte(queriesText)); err != nil { 99 | t.Errorf("error while trying to write to temporary file %s: %s", tmpFile.Name(), err.Error()) 100 | } 101 | 102 | queries, err := ParseQueries("", tmpFile.Name()) 103 | if err != nil { 104 | t.Error(err.Error()) 105 | } 106 | 107 | if len(queries) != 3 { 108 | t.Errorf("expected 3 queries; got %+v", len(queries)) 109 | t.Errorf("%+v", strings.Join(queries, "::")) 110 | } 111 | result := strings.Join(queries, ";") 112 | if result != `select 1;select '2;2';select 3` { 113 | t.Errorf("got unexpected results: `%+v`", result) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /go/text/hosts.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "regexp" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | defaultMySQLPort = 3306 13 | ) 14 | 15 | // ParseHosts will return list of hostnames from either given list, 16 | // or file, or stdin. 17 | func ParseHosts(hostsList string, hostsFile string) (hosts []string, err error) { 18 | if hostsFile != "" { 19 | bytes, err := ioutil.ReadFile(hostsFile) 20 | if err != nil { 21 | return hosts, err 22 | } 23 | hostsList = string(bytes) 24 | } 25 | if hostsList == "" { 26 | // Read from stdin 27 | bytes, err := ioutil.ReadAll(os.Stdin) 28 | if err != nil { 29 | return hosts, err 30 | } 31 | hostsList = string(bytes) 32 | } 33 | parsedHosts := regexp.MustCompile(`[,\s]`).Split(hostsList, -1) 34 | for _, host := range parsedHosts { 35 | host = strings.TrimSpace(host) 36 | if host != "" { 37 | if !strings.Contains(host, ":") { 38 | host = fmt.Sprintf("%s:%d", host, defaultMySQLPort) 39 | } 40 | hosts = append(hosts, host) 41 | } 42 | } 43 | 44 | return hosts, err 45 | } 46 | 47 | // SplitNonEmpty performs a strings.Split operations and filters out empty strings when returning a string slice. 48 | func SplitNonEmpty(s string, sep string) (result []string) { 49 | tokens := strings.Split(s, sep) 50 | for _, token := range tokens { 51 | if token != "" { 52 | result = append(result, strings.TrimSpace(token)) 53 | } 54 | } 55 | return result 56 | } 57 | -------------------------------------------------------------------------------- /go/text/hosts_test.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func TestParseHostsSingle(t *testing.T) { 11 | hostsList := `localhost` 12 | hosts, err := ParseHosts(hostsList, "") 13 | 14 | if err != nil { 15 | t.Error(err.Error()) 16 | } 17 | if len(hosts) != 1 { 18 | t.Errorf("expected 1 host; got %+v", len(hosts)) 19 | } 20 | if hosts[0] != `localhost:3306` { 21 | t.Errorf("got unexpected host `%+v`", hosts[0]) 22 | } 23 | } 24 | 25 | func TestParseHostsMulti(t *testing.T) { 26 | useCases := []string{ 27 | `host1 host2:4408 host3`, 28 | ` host1 host2:4408 host3 `, 29 | `host1, host2:4408, host3,`, 30 | `host1 31 | host2:4408 32 | host3`, 33 | `host1 , 34 | host2:4408 host3`, 35 | `host1, ,, host2:4408, host3, ,,`, 36 | } 37 | expected := `host1:3306,host2:4408,host3:3306` 38 | for _, hostsList := range useCases { 39 | hosts, err := ParseHosts(hostsList, "") 40 | 41 | if err != nil { 42 | t.Error(err.Error()) 43 | } 44 | if len(hosts) != 3 { 45 | t.Errorf("expected 3 hosts; got %+v", len(hosts)) 46 | } 47 | result := strings.Join(hosts, ",") 48 | if result != expected { 49 | t.Errorf("got unexpected results: `%+v`", result) 50 | } 51 | } 52 | } 53 | 54 | func TestParseHostFiles(t *testing.T) { 55 | s := `host1 host2:4408 host3` 56 | 57 | tmpFile, err := ioutil.TempFile(os.TempDir(), "querytest-") 58 | 59 | if err != nil { 60 | t.Errorf("error creating temporary file: %s", err.Error()) 61 | } 62 | 63 | defer os.Remove(tmpFile.Name()) 64 | 65 | if _, err = tmpFile.Write([]byte(s)); err != nil { 66 | t.Errorf("error while trying to write to temporary file %s: %s", tmpFile.Name(), err.Error()) 67 | } 68 | 69 | hosts, err := ParseHosts("", tmpFile.Name()) 70 | 71 | if err != nil { 72 | t.Error(err.Error()) 73 | } 74 | if len(hosts) != 3 { 75 | t.Errorf("expected 1 host; got %+v", len(hosts)) 76 | } 77 | if hosts[0] != `host1:3306` { 78 | t.Errorf("got unexpected host `%+v`", hosts[0]) 79 | } 80 | 81 | if hosts[1] != `host2:4408` { 82 | t.Errorf("got unexpected host `%+v`", hosts[1]) 83 | } 84 | 85 | if hosts[2] != `host3:3306` { 86 | t.Errorf("got unexpected host `%+v`", hosts[2]) 87 | } 88 | 89 | } 90 | 91 | func TestSplitNonEmpty(t *testing.T) { 92 | s := "the, quick,, brown,fox ,," 93 | splits := SplitNonEmpty(s, ",") 94 | 95 | if len(splits) != 4 { 96 | t.Errorf("expected 4 tokens; got %+v", len(splits)) 97 | } 98 | join := strings.Join(splits, ";") 99 | expected := "the;quick;brown;fox" 100 | if join != expected { 101 | t.Errorf("expected tokens: `%+v`. Got: `%+v`", expected, join) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Make sure we have the version of Go we want to depend on, either from the 6 | # system or one we grab ourselves. 7 | # If executing from within Dockerfile then this assumption is inherently true, since we use a `golang` docker image. 8 | . script/ensure-go-installed 9 | 10 | # Since we want to be able to build this outside of GOPATH, we set it 11 | # up so it points back to us and go is none the wiser 12 | 13 | set -x 14 | rm -rf .gopath 15 | mkdir -p .gopath/src/github.com/github 16 | ln -s "$PWD" .gopath/src/github.com/github/ccql 17 | export GOPATH=$PWD/.gopath:$GOPATH 18 | -------------------------------------------------------------------------------- /script/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . script/bootstrap 6 | 7 | mkdir -p bin 8 | bindir="$PWD"/bin 9 | scriptdir="$PWD"/script 10 | 11 | # We have a few binaries that we want to build, so let's put them into bin/ 12 | 13 | version=$(git rev-parse HEAD) 14 | describe=$(git describe --tags --always --dirty) 15 | 16 | export GOPATH="$PWD/.gopath" 17 | cd .gopath/src/github.com/github/ccql 18 | 19 | # We put the binaries directly into the bindir, because we have no need for shim wrappers 20 | go build -o "$bindir/ccql" -ldflags "-X main.AppVersion=${version} -X main.BuildDescribe=${describe}" ./go/cmd/ccql/main.go 21 | -------------------------------------------------------------------------------- /script/build-deploy-tarball: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | echo "Building" 6 | script/build 7 | 8 | cd .gopath/src/github.com/github/ccql 9 | 10 | # Get a fresh directory and make sure to delete it afterwards 11 | build_dir=tmp/build 12 | rm -rf $build_dir 13 | mkdir -p $build_dir 14 | trap "rm -rf $build_dir" EXIT 15 | 16 | commit_sha=$(git rev-parse HEAD) 17 | 18 | if [ $(uname -s) = "Darwin" ]; then 19 | build_arch="$(uname -sr | tr -d ' ' | tr '[:upper:]' '[:lower:]')-$(uname -m)" 20 | else 21 | build_arch="$(lsb_release -sc | tr -d ' ' | tr '[:upper:]' '[:lower:]')-$(uname -m)" 22 | fi 23 | 24 | echo "build_arch: $build_arch" 25 | 26 | tarball=$build_dir/${commit_sha}-${build_arch}.tar 27 | echo "tarball: $tarball" 28 | 29 | # Create the tarball 30 | tar cvf $tarball --mode="ugo=rx" bin/ 31 | 32 | # Compress it and copy it to the directory for the CI to upload it 33 | gzip $tarball 34 | mkdir -p "$BUILD_ARTIFACT_DIR"/ccql 35 | cp ${tarball}.gz "$BUILD_ARTIFACT_DIR"/ccql/ 36 | 37 | ### HACK HACK HACK ### 38 | # blame @carlosmn and @mattr- 39 | # Allow builds on stretch to also be used for jessie 40 | jessie_tarball_name=$(echo $(basename "${tarball}") | sed s/-stretch-/-jessie-/) 41 | cp ${tarball}.gz "$BUILD_ARTIFACT_DIR/ccql/${jessie_tarball_name}.gz" 42 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | script/test 4 | -------------------------------------------------------------------------------- /script/cibuild-ccql-build-deploy-tarball: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | output_fold() { 4 | # Exit early if no label provided 5 | if [ -z "$1" ]; then 6 | echo "output_fold(): requires a label argument." 7 | return 8 | fi 9 | 10 | exit_value=0 # exit_value is used to record exit status of the given command 11 | label=$1 # human-readable label describing what's being folded up 12 | shift 1 # having retrieved the output_fold()-specific arguments, strip them off $@ 13 | 14 | # Only echo the tags when in CI_MODE 15 | if [ "$CI_MODE" ]; then 16 | echo "%%%FOLD {$label}%%%" 17 | fi 18 | 19 | # run the remaining arguments. If the command exits non-0, the `||` will 20 | # prevent the `-e` flag from seeing the failure exit code, and we'll see 21 | # the second echo execute 22 | "$@" || exit_value=$? 23 | 24 | # Only echo the tags when in CI_MODE 25 | if [ "$CI_MODE" ]; then 26 | echo "%%%END FOLD%%%" 27 | fi 28 | 29 | # preserve the exit code from the subcommand. 30 | return $exit_value 31 | } 32 | 33 | function cleanup() { 34 | echo 35 | echo "%%%FOLD {Shutting down services...}%%%" 36 | docker-compose down 37 | echo "%%%END FOLD%%%" 38 | } 39 | 40 | trap cleanup EXIT 41 | 42 | export CI_MODE=true 43 | 44 | output_fold "Bootstrapping container..." docker-compose build 45 | output_fold "Running tests..." docker-compose run --rm app 46 | 47 | docker-compose run -e BUILD_ARTIFACT_DIR=$BUILD_ARTIFACT_DIR -v $BUILD_ARTIFACT_DIR:$BUILD_ARTIFACT_DIR app script/build-deploy-tarball 48 | -------------------------------------------------------------------------------- /script/ensure-go-installed: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PREFERRED_GO_VERSION=go1.12.6 4 | SUPPORTED_GO_VERSIONS='go1.1[234]' 5 | 6 | GO_PKG_DARWIN=${PREFERRED_GO_VERSION}.darwin-amd64.pkg 7 | GO_PKG_DARWIN_SHA=ea78245e43de2996fa0973033064b33f48820cfe39f4f3c6e953040925cc5815 8 | 9 | GO_PKG_LINUX=${PREFERRED_GO_VERSION}.linux-amd64.tar.gz 10 | GO_PKG_LINUX_SHA=dbcf71a3c1ea53b8d54ef1b48c85a39a6c9a935d01fc8291ff2b92028e59913c 11 | 12 | export ROOTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" 13 | cd $ROOTDIR 14 | 15 | # If Go isn't installed globally, setup environment variables for local install. 16 | if [ -z "$(which go)" ] || [ -z "$(go version | grep "$SUPPORTED_GO_VERSIONS")" ]; then 17 | GODIR="$ROOTDIR/.vendor/golocal" 18 | 19 | if [ $(uname -s) = "Darwin" ]; then 20 | export GOROOT="$GODIR/usr/local/go" 21 | else 22 | export GOROOT="$GODIR/go" 23 | fi 24 | 25 | export PATH="$GOROOT/bin:$PATH" 26 | fi 27 | 28 | # Check if local install exists, and install otherwise. 29 | if [ -z "$(which go)" ] || [ -z "$(go version | grep "$SUPPORTED_GO_VERSIONS")" ]; then 30 | [ -d "$GODIR" ] && rm -rf $GODIR 31 | mkdir -p "$GODIR" 32 | cd "$GODIR"; 33 | 34 | if [ $(uname -s) = "Darwin" ]; then 35 | curl -L -O https://dl.google.com/go/$GO_PKG_DARWIN 36 | shasum -a256 $GO_PKG_DARWIN | grep $GO_PKG_DARWIN_SHA 37 | xar -xf $GO_PKG_DARWIN 38 | cpio -i < com.googlecode.go.pkg/Payload 39 | else 40 | curl -L -O https://dl.google.com/go/$GO_PKG_LINUX 41 | shasum -a256 $GO_PKG_LINUX | grep $GO_PKG_LINUX_SHA 42 | tar xf $GO_PKG_LINUX 43 | fi 44 | 45 | # Prove we did something right 46 | echo "$(go version) installed in $GODIR: Go Binary: $(which go)" 47 | else 48 | echo "$(go version) found in $GODIR: Go Binary: $(which go)" 49 | fi 50 | 51 | cd $ROOTDIR 52 | 53 | # Configure the new go to be the first go found 54 | export GOPATH=$ROOTDIR/.vendor 55 | -------------------------------------------------------------------------------- /script/go: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . script/bootstrap 6 | 7 | mkdir -p bin 8 | bindir="$PWD"/bin 9 | 10 | cd .gopath/src/github.com/github/ccql 11 | go "$@" 12 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | . script/bootstrap 6 | 7 | echo "Verifying code is formatted via 'gofmt -s -w go/'" 8 | gofmt -s -w go/ 9 | git diff --exit-code --quiet go/ 10 | 11 | echo "Building" 12 | script/build 13 | 14 | cd .gopath/src/github.com/github/ccql 15 | 16 | echo "Running unit tests" 17 | go test ./go/... 18 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .DS_Store? 3 | ._* 4 | .Spotlight-V100 5 | .Trashes 6 | Icon? 7 | ehthumbs.db 8 | Thumbs.db 9 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: go 3 | go: 4 | - 1.2 5 | - 1.3 6 | - 1.4 7 | - tip 8 | 9 | before_script: 10 | - mysql -e 'create database gotest;' 11 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/AUTHORS: -------------------------------------------------------------------------------- 1 | # This is the official list of Go-MySQL-Driver authors for copyright purposes. 2 | 3 | # If you are submitting a patch, please add your name or the name of the 4 | # organization which holds the copyright to this list in alphabetical order. 5 | 6 | # Names should be added to this file as 7 | # Name 8 | # The email address is not required for organizations. 9 | # Please keep the list sorted. 10 | 11 | 12 | # Individual Persons 13 | 14 | Aaron Hopkins 15 | Arne Hormann 16 | Carlos Nieto 17 | Chris Moos 18 | DisposaBoy 19 | Frederick Mayle 20 | Gustavo Kristic 21 | Hanno Braun 22 | Henri Yandell 23 | Hirotaka Yamamoto 24 | INADA Naoki 25 | James Harr 26 | Jian Zhen 27 | Joshua Prunier 28 | Julien Schmidt 29 | Kamil Dziedzic 30 | Leonardo YongUk Kim 31 | Lucas Liu 32 | Luke Scott 33 | Michael Woolnough 34 | Nicola Peduzzi 35 | Runrioter Wung 36 | Soroush Pour 37 | Stan Putrya 38 | Xiaobing Jiang 39 | Xiuming Chen 40 | Julien Lefevre 41 | 42 | # Organizations 43 | 44 | Barracuda Networks, Inc. 45 | Google Inc. 46 | Stripe Inc. 47 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## HEAD 2 | 3 | Changes: 4 | 5 | - Go 1.1 is no longer supported 6 | - Use decimals field from MySQL to format time types (#249) 7 | - Buffer optimizations (#269) 8 | - TLS ServerName defaults to the host (#283) 9 | 10 | Bugfixes: 11 | 12 | - Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249) 13 | - Fixed handling of queries without columns and rows (#255) 14 | - Fixed a panic when SetKeepAlive() failed (#298) 15 | 16 | New Features: 17 | - Support for returning table alias on Columns() (#289) 18 | - Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318) 19 | 20 | 21 | ## Version 1.2 (2014-06-03) 22 | 23 | Changes: 24 | 25 | - We switched back to a "rolling release". `go get` installs the current master branch again 26 | - Version v1 of the driver will not be maintained anymore. Go 1.0 is no longer supported by this driver 27 | - Exported errors to allow easy checking from application code 28 | - Enabled TCP Keepalives on TCP connections 29 | - Optimized INFILE handling (better buffer size calculation, lazy init, ...) 30 | - The DSN parser also checks for a missing separating slash 31 | - Faster binary date / datetime to string formatting 32 | - Also exported the MySQLWarning type 33 | - mysqlConn.Close returns the first error encountered instead of ignoring all errors 34 | - writePacket() automatically writes the packet size to the header 35 | - readPacket() uses an iterative approach instead of the recursive approach to merge splitted packets 36 | 37 | New Features: 38 | 39 | - `RegisterDial` allows the usage of a custom dial function to establish the network connection 40 | - Setting the connection collation is possible with the `collation` DSN parameter. This parameter should be preferred over the `charset` parameter 41 | - Logging of critical errors is configurable with `SetLogger` 42 | - Google CloudSQL support 43 | 44 | Bugfixes: 45 | 46 | - Allow more than 32 parameters in prepared statements 47 | - Various old_password fixes 48 | - Fixed TestConcurrent test to pass Go's race detection 49 | - Fixed appendLengthEncodedInteger for large numbers 50 | - Renamed readLengthEnodedString to readLengthEncodedString and skipLengthEnodedString to skipLengthEncodedString (fixed typo) 51 | 52 | 53 | ## Version 1.1 (2013-11-02) 54 | 55 | Changes: 56 | 57 | - Go-MySQL-Driver now requires Go 1.1 58 | - Connections now use the collation `utf8_general_ci` by default. Adding `&charset=UTF8` to the DSN should not be necessary anymore 59 | - Made closing rows and connections error tolerant. This allows for example deferring rows.Close() without checking for errors 60 | - `[]byte(nil)` is now treated as a NULL value. Before, it was treated like an empty string / `[]byte("")` 61 | - DSN parameter values must now be url.QueryEscape'ed. This allows text values to contain special characters, such as '&'. 62 | - Use the IO buffer also for writing. This results in zero allocations (by the driver) for most queries 63 | - Optimized the buffer for reading 64 | - stmt.Query now caches column metadata 65 | - New Logo 66 | - Changed the copyright header to include all contributors 67 | - Improved the LOAD INFILE documentation 68 | - The driver struct is now exported to make the driver directly accessible 69 | - Refactored the driver tests 70 | - Added more benchmarks and moved all to a separate file 71 | - Other small refactoring 72 | 73 | New Features: 74 | 75 | - Added *old_passwords* support: Required in some cases, but must be enabled by adding `allowOldPasswords=true` to the DSN since it is insecure 76 | - Added a `clientFoundRows` parameter: Return the number of matching rows instead of the number of rows changed on UPDATEs 77 | - Added TLS/SSL support: Use a TLS/SSL encrypted connection to the server. Custom TLS configs can be registered and used 78 | 79 | Bugfixes: 80 | 81 | - Fixed MySQL 4.1 support: MySQL 4.1 sends packets with lengths which differ from the specification 82 | - Convert to DB timezone when inserting `time.Time` 83 | - Splitted packets (more than 16MB) are now merged correctly 84 | - Fixed false positive `io.EOF` errors when the data was fully read 85 | - Avoid panics on reuse of closed connections 86 | - Fixed empty string producing false nil values 87 | - Fixed sign byte for positive TIME fields 88 | 89 | 90 | ## Version 1.0 (2013-05-14) 91 | 92 | Initial Release 93 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Reporting Issues 4 | 5 | Before creating a new Issue, please check first if a similar Issue [already exists](https://github.com/go-sql-driver/mysql/issues?state=open) or was [recently closed](https://github.com/go-sql-driver/mysql/issues?direction=desc&page=1&sort=updated&state=closed). 6 | 7 | Please provide the following minimum information: 8 | * Your Go-MySQL-Driver version (or git SHA) 9 | * Your Go version (run `go version` in your console) 10 | * A detailed issue description 11 | * Error Log if present 12 | * If possible, a short example 13 | 14 | 15 | ## Contributing Code 16 | 17 | By contributing to this project, you share your code under the Mozilla Public License 2, as specified in the LICENSE file. 18 | Don't forget to add yourself to the AUTHORS file. 19 | 20 | ### Pull Requests Checklist 21 | 22 | Please check the following points before submitting your pull request: 23 | - [x] Code compiles correctly 24 | - [x] Created tests, if possible 25 | - [x] All tests pass 26 | - [x] Extended the README / documentation, if necessary 27 | - [x] Added yourself to the AUTHORS file 28 | 29 | ### Code Review 30 | 31 | Everyone is invited to review and comment on pull requests. 32 | If it looks fine to you, comment with "LGTM" (Looks good to me). 33 | 34 | If changes are required, notice the reviewers with "PTAL" (Please take another look) after committing the fixes. 35 | 36 | Before merging the Pull Request, at least one [team member](https://github.com/go-sql-driver?tab=members) must have commented with "LGTM". 37 | 38 | ## Development Ideas 39 | 40 | If you are looking for ideas for code contributions, please check our [Development Ideas](https://github.com/go-sql-driver/mysql/wiki/Development-Ideas) Wiki page. 41 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/appengine.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | // +build appengine 10 | 11 | package mysql 12 | 13 | import ( 14 | "appengine/cloudsql" 15 | ) 16 | 17 | func init() { 18 | RegisterDial("cloudsql", cloudsql.Dial) 19 | } 20 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/buffer.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import "io" 12 | 13 | const defaultBufSize = 4096 14 | 15 | // A buffer which is used for both reading and writing. 16 | // This is possible since communication on each connection is synchronous. 17 | // In other words, we can't write and read simultaneously on the same connection. 18 | // The buffer is similar to bufio.Reader / Writer but zero-copy-ish 19 | // Also highly optimized for this particular use case. 20 | type buffer struct { 21 | buf []byte 22 | rd io.Reader 23 | idx int 24 | length int 25 | } 26 | 27 | func newBuffer(rd io.Reader) buffer { 28 | var b [defaultBufSize]byte 29 | return buffer{ 30 | buf: b[:], 31 | rd: rd, 32 | } 33 | } 34 | 35 | // fill reads into the buffer until at least _need_ bytes are in it 36 | func (b *buffer) fill(need int) error { 37 | n := b.length 38 | 39 | // move existing data to the beginning 40 | if n > 0 && b.idx > 0 { 41 | copy(b.buf[0:n], b.buf[b.idx:]) 42 | } 43 | 44 | // grow buffer if necessary 45 | // TODO: let the buffer shrink again at some point 46 | // Maybe keep the org buf slice and swap back? 47 | if need > len(b.buf) { 48 | // Round up to the next multiple of the default size 49 | newBuf := make([]byte, ((need/defaultBufSize)+1)*defaultBufSize) 50 | copy(newBuf, b.buf) 51 | b.buf = newBuf 52 | } 53 | 54 | b.idx = 0 55 | 56 | for { 57 | nn, err := b.rd.Read(b.buf[n:]) 58 | n += nn 59 | 60 | switch err { 61 | case nil: 62 | if n < need { 63 | continue 64 | } 65 | b.length = n 66 | return nil 67 | 68 | case io.EOF: 69 | if n >= need { 70 | b.length = n 71 | return nil 72 | } 73 | return io.ErrUnexpectedEOF 74 | 75 | default: 76 | return err 77 | } 78 | } 79 | } 80 | 81 | // returns next N bytes from buffer. 82 | // The returned slice is only guaranteed to be valid until the next read 83 | func (b *buffer) readNext(need int) ([]byte, error) { 84 | if b.length < need { 85 | // refill 86 | if err := b.fill(need); err != nil { 87 | return nil, err 88 | } 89 | } 90 | 91 | offset := b.idx 92 | b.idx += need 93 | b.length -= need 94 | return b.buf[offset:b.idx], nil 95 | } 96 | 97 | // returns a buffer with the requested size. 98 | // If possible, a slice from the existing buffer is returned. 99 | // Otherwise a bigger buffer is made. 100 | // Only one buffer (total) can be used at a time. 101 | func (b *buffer) takeBuffer(length int) []byte { 102 | if b.length > 0 { 103 | return nil 104 | } 105 | 106 | // test (cheap) general case first 107 | if length <= defaultBufSize || length <= cap(b.buf) { 108 | return b.buf[:length] 109 | } 110 | 111 | if length < maxPacketSize { 112 | b.buf = make([]byte, length) 113 | return b.buf 114 | } 115 | return make([]byte, length) 116 | } 117 | 118 | // shortcut which can be used if the requested buffer is guaranteed to be 119 | // smaller than defaultBufSize 120 | // Only one buffer (total) can be used at a time. 121 | func (b *buffer) takeSmallBuffer(length int) []byte { 122 | if b.length == 0 { 123 | return b.buf[:length] 124 | } 125 | return nil 126 | } 127 | 128 | // takeCompleteBuffer returns the complete existing buffer. 129 | // This can be used if the necessary buffer size is unknown. 130 | // Only one buffer (total) can be used at a time. 131 | func (b *buffer) takeCompleteBuffer() []byte { 132 | if b.length == 0 { 133 | return b.buf 134 | } 135 | return nil 136 | } 137 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/collations.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2014 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | const defaultCollation byte = 33 // utf8_general_ci 12 | 13 | // A list of available collations mapped to the internal ID. 14 | // To update this map use the following MySQL query: 15 | // SELECT COLLATION_NAME, ID FROM information_schema.COLLATIONS 16 | var collations = map[string]byte{ 17 | "big5_chinese_ci": 1, 18 | "latin2_czech_cs": 2, 19 | "dec8_swedish_ci": 3, 20 | "cp850_general_ci": 4, 21 | "latin1_german1_ci": 5, 22 | "hp8_english_ci": 6, 23 | "koi8r_general_ci": 7, 24 | "latin1_swedish_ci": 8, 25 | "latin2_general_ci": 9, 26 | "swe7_swedish_ci": 10, 27 | "ascii_general_ci": 11, 28 | "ujis_japanese_ci": 12, 29 | "sjis_japanese_ci": 13, 30 | "cp1251_bulgarian_ci": 14, 31 | "latin1_danish_ci": 15, 32 | "hebrew_general_ci": 16, 33 | "tis620_thai_ci": 18, 34 | "euckr_korean_ci": 19, 35 | "latin7_estonian_cs": 20, 36 | "latin2_hungarian_ci": 21, 37 | "koi8u_general_ci": 22, 38 | "cp1251_ukrainian_ci": 23, 39 | "gb2312_chinese_ci": 24, 40 | "greek_general_ci": 25, 41 | "cp1250_general_ci": 26, 42 | "latin2_croatian_ci": 27, 43 | "gbk_chinese_ci": 28, 44 | "cp1257_lithuanian_ci": 29, 45 | "latin5_turkish_ci": 30, 46 | "latin1_german2_ci": 31, 47 | "armscii8_general_ci": 32, 48 | "utf8_general_ci": 33, 49 | "cp1250_czech_cs": 34, 50 | "ucs2_general_ci": 35, 51 | "cp866_general_ci": 36, 52 | "keybcs2_general_ci": 37, 53 | "macce_general_ci": 38, 54 | "macroman_general_ci": 39, 55 | "cp852_general_ci": 40, 56 | "latin7_general_ci": 41, 57 | "latin7_general_cs": 42, 58 | "macce_bin": 43, 59 | "cp1250_croatian_ci": 44, 60 | "utf8mb4_general_ci": 45, 61 | "utf8mb4_bin": 46, 62 | "latin1_bin": 47, 63 | "latin1_general_ci": 48, 64 | "latin1_general_cs": 49, 65 | "cp1251_bin": 50, 66 | "cp1251_general_ci": 51, 67 | "cp1251_general_cs": 52, 68 | "macroman_bin": 53, 69 | "utf16_general_ci": 54, 70 | "utf16_bin": 55, 71 | "utf16le_general_ci": 56, 72 | "cp1256_general_ci": 57, 73 | "cp1257_bin": 58, 74 | "cp1257_general_ci": 59, 75 | "utf32_general_ci": 60, 76 | "utf32_bin": 61, 77 | "utf16le_bin": 62, 78 | "binary": 63, 79 | "armscii8_bin": 64, 80 | "ascii_bin": 65, 81 | "cp1250_bin": 66, 82 | "cp1256_bin": 67, 83 | "cp866_bin": 68, 84 | "dec8_bin": 69, 85 | "greek_bin": 70, 86 | "hebrew_bin": 71, 87 | "hp8_bin": 72, 88 | "keybcs2_bin": 73, 89 | "koi8r_bin": 74, 90 | "koi8u_bin": 75, 91 | "latin2_bin": 77, 92 | "latin5_bin": 78, 93 | "latin7_bin": 79, 94 | "cp850_bin": 80, 95 | "cp852_bin": 81, 96 | "swe7_bin": 82, 97 | "utf8_bin": 83, 98 | "big5_bin": 84, 99 | "euckr_bin": 85, 100 | "gb2312_bin": 86, 101 | "gbk_bin": 87, 102 | "sjis_bin": 88, 103 | "tis620_bin": 89, 104 | "ucs2_bin": 90, 105 | "ujis_bin": 91, 106 | "geostd8_general_ci": 92, 107 | "geostd8_bin": 93, 108 | "latin1_spanish_ci": 94, 109 | "cp932_japanese_ci": 95, 110 | "cp932_bin": 96, 111 | "eucjpms_japanese_ci": 97, 112 | "eucjpms_bin": 98, 113 | "cp1250_polish_ci": 99, 114 | "utf16_unicode_ci": 101, 115 | "utf16_icelandic_ci": 102, 116 | "utf16_latvian_ci": 103, 117 | "utf16_romanian_ci": 104, 118 | "utf16_slovenian_ci": 105, 119 | "utf16_polish_ci": 106, 120 | "utf16_estonian_ci": 107, 121 | "utf16_spanish_ci": 108, 122 | "utf16_swedish_ci": 109, 123 | "utf16_turkish_ci": 110, 124 | "utf16_czech_ci": 111, 125 | "utf16_danish_ci": 112, 126 | "utf16_lithuanian_ci": 113, 127 | "utf16_slovak_ci": 114, 128 | "utf16_spanish2_ci": 115, 129 | "utf16_roman_ci": 116, 130 | "utf16_persian_ci": 117, 131 | "utf16_esperanto_ci": 118, 132 | "utf16_hungarian_ci": 119, 133 | "utf16_sinhala_ci": 120, 134 | "utf16_german2_ci": 121, 135 | "utf16_croatian_ci": 122, 136 | "utf16_unicode_520_ci": 123, 137 | "utf16_vietnamese_ci": 124, 138 | "ucs2_unicode_ci": 128, 139 | "ucs2_icelandic_ci": 129, 140 | "ucs2_latvian_ci": 130, 141 | "ucs2_romanian_ci": 131, 142 | "ucs2_slovenian_ci": 132, 143 | "ucs2_polish_ci": 133, 144 | "ucs2_estonian_ci": 134, 145 | "ucs2_spanish_ci": 135, 146 | "ucs2_swedish_ci": 136, 147 | "ucs2_turkish_ci": 137, 148 | "ucs2_czech_ci": 138, 149 | "ucs2_danish_ci": 139, 150 | "ucs2_lithuanian_ci": 140, 151 | "ucs2_slovak_ci": 141, 152 | "ucs2_spanish2_ci": 142, 153 | "ucs2_roman_ci": 143, 154 | "ucs2_persian_ci": 144, 155 | "ucs2_esperanto_ci": 145, 156 | "ucs2_hungarian_ci": 146, 157 | "ucs2_sinhala_ci": 147, 158 | "ucs2_german2_ci": 148, 159 | "ucs2_croatian_ci": 149, 160 | "ucs2_unicode_520_ci": 150, 161 | "ucs2_vietnamese_ci": 151, 162 | "ucs2_general_mysql500_ci": 159, 163 | "utf32_unicode_ci": 160, 164 | "utf32_icelandic_ci": 161, 165 | "utf32_latvian_ci": 162, 166 | "utf32_romanian_ci": 163, 167 | "utf32_slovenian_ci": 164, 168 | "utf32_polish_ci": 165, 169 | "utf32_estonian_ci": 166, 170 | "utf32_spanish_ci": 167, 171 | "utf32_swedish_ci": 168, 172 | "utf32_turkish_ci": 169, 173 | "utf32_czech_ci": 170, 174 | "utf32_danish_ci": 171, 175 | "utf32_lithuanian_ci": 172, 176 | "utf32_slovak_ci": 173, 177 | "utf32_spanish2_ci": 174, 178 | "utf32_roman_ci": 175, 179 | "utf32_persian_ci": 176, 180 | "utf32_esperanto_ci": 177, 181 | "utf32_hungarian_ci": 178, 182 | "utf32_sinhala_ci": 179, 183 | "utf32_german2_ci": 180, 184 | "utf32_croatian_ci": 181, 185 | "utf32_unicode_520_ci": 182, 186 | "utf32_vietnamese_ci": 183, 187 | "utf8_unicode_ci": 192, 188 | "utf8_icelandic_ci": 193, 189 | "utf8_latvian_ci": 194, 190 | "utf8_romanian_ci": 195, 191 | "utf8_slovenian_ci": 196, 192 | "utf8_polish_ci": 197, 193 | "utf8_estonian_ci": 198, 194 | "utf8_spanish_ci": 199, 195 | "utf8_swedish_ci": 200, 196 | "utf8_turkish_ci": 201, 197 | "utf8_czech_ci": 202, 198 | "utf8_danish_ci": 203, 199 | "utf8_lithuanian_ci": 204, 200 | "utf8_slovak_ci": 205, 201 | "utf8_spanish2_ci": 206, 202 | "utf8_roman_ci": 207, 203 | "utf8_persian_ci": 208, 204 | "utf8_esperanto_ci": 209, 205 | "utf8_hungarian_ci": 210, 206 | "utf8_sinhala_ci": 211, 207 | "utf8_german2_ci": 212, 208 | "utf8_croatian_ci": 213, 209 | "utf8_unicode_520_ci": 214, 210 | "utf8_vietnamese_ci": 215, 211 | "utf8_general_mysql500_ci": 223, 212 | "utf8mb4_unicode_ci": 224, 213 | "utf8mb4_icelandic_ci": 225, 214 | "utf8mb4_latvian_ci": 226, 215 | "utf8mb4_romanian_ci": 227, 216 | "utf8mb4_slovenian_ci": 228, 217 | "utf8mb4_polish_ci": 229, 218 | "utf8mb4_estonian_ci": 230, 219 | "utf8mb4_spanish_ci": 231, 220 | "utf8mb4_swedish_ci": 232, 221 | "utf8mb4_turkish_ci": 233, 222 | "utf8mb4_czech_ci": 234, 223 | "utf8mb4_danish_ci": 235, 224 | "utf8mb4_lithuanian_ci": 236, 225 | "utf8mb4_slovak_ci": 237, 226 | "utf8mb4_spanish2_ci": 238, 227 | "utf8mb4_roman_ci": 239, 228 | "utf8mb4_persian_ci": 240, 229 | "utf8mb4_esperanto_ci": 241, 230 | "utf8mb4_hungarian_ci": 242, 231 | "utf8mb4_sinhala_ci": 243, 232 | "utf8mb4_german2_ci": 244, 233 | "utf8mb4_croatian_ci": 245, 234 | "utf8mb4_unicode_520_ci": 246, 235 | "utf8mb4_vietnamese_ci": 247, 236 | } 237 | 238 | // A blacklist of collations which is unsafe to interpolate parameters. 239 | // These multibyte encodings may contains 0x5c (`\`) in their trailing bytes. 240 | var unsafeCollations = map[byte]bool{ 241 | 1: true, // big5_chinese_ci 242 | 13: true, // sjis_japanese_ci 243 | 28: true, // gbk_chinese_ci 244 | 84: true, // big5_bin 245 | 86: true, // gb2312_bin 246 | 87: true, // gbk_bin 247 | 88: true, // sjis_bin 248 | 95: true, // cp932_japanese_ci 249 | 96: true, // cp932_bin 250 | } 251 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/connection.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "crypto/tls" 13 | "database/sql/driver" 14 | "errors" 15 | "net" 16 | "strconv" 17 | "strings" 18 | "time" 19 | ) 20 | 21 | type mysqlConn struct { 22 | buf buffer 23 | netConn net.Conn 24 | affectedRows uint64 25 | insertId uint64 26 | cfg *config 27 | maxPacketAllowed int 28 | maxWriteSize int 29 | flags clientFlag 30 | status statusFlag 31 | sequence uint8 32 | parseTime bool 33 | strict bool 34 | } 35 | 36 | type config struct { 37 | user string 38 | passwd string 39 | net string 40 | addr string 41 | dbname string 42 | params map[string]string 43 | loc *time.Location 44 | tls *tls.Config 45 | timeout time.Duration 46 | collation uint8 47 | allowAllFiles bool 48 | allowOldPasswords bool 49 | allowCleartextPasswords bool 50 | clientFoundRows bool 51 | columnsWithAlias bool 52 | interpolateParams bool 53 | } 54 | 55 | // Handles parameters set in DSN after the connection is established 56 | func (mc *mysqlConn) handleParams() (err error) { 57 | for param, val := range mc.cfg.params { 58 | switch param { 59 | // Charset 60 | case "charset": 61 | charsets := strings.Split(val, ",") 62 | for i := range charsets { 63 | // ignore errors here - a charset may not exist 64 | err = mc.exec("SET NAMES " + charsets[i]) 65 | if err == nil { 66 | break 67 | } 68 | } 69 | if err != nil { 70 | return 71 | } 72 | 73 | // time.Time parsing 74 | case "parseTime": 75 | var isBool bool 76 | mc.parseTime, isBool = readBool(val) 77 | if !isBool { 78 | return errors.New("Invalid Bool value: " + val) 79 | } 80 | 81 | // Strict mode 82 | case "strict": 83 | var isBool bool 84 | mc.strict, isBool = readBool(val) 85 | if !isBool { 86 | return errors.New("Invalid Bool value: " + val) 87 | } 88 | 89 | // Compression 90 | case "compress": 91 | err = errors.New("Compression not implemented yet") 92 | return 93 | 94 | // System Vars 95 | default: 96 | err = mc.exec("SET " + param + "=" + val + "") 97 | if err != nil { 98 | return 99 | } 100 | } 101 | } 102 | 103 | return 104 | } 105 | 106 | func (mc *mysqlConn) Begin() (driver.Tx, error) { 107 | if mc.netConn == nil { 108 | errLog.Print(ErrInvalidConn) 109 | return nil, driver.ErrBadConn 110 | } 111 | err := mc.exec("START TRANSACTION") 112 | if err == nil { 113 | return &mysqlTx{mc}, err 114 | } 115 | 116 | return nil, err 117 | } 118 | 119 | func (mc *mysqlConn) Close() (err error) { 120 | // Makes Close idempotent 121 | if mc.netConn != nil { 122 | err = mc.writeCommandPacket(comQuit) 123 | if err == nil { 124 | err = mc.netConn.Close() 125 | } else { 126 | mc.netConn.Close() 127 | } 128 | mc.netConn = nil 129 | } 130 | 131 | mc.cfg = nil 132 | mc.buf.rd = nil 133 | 134 | return 135 | } 136 | 137 | func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) { 138 | if mc.netConn == nil { 139 | errLog.Print(ErrInvalidConn) 140 | return nil, driver.ErrBadConn 141 | } 142 | // Send command 143 | err := mc.writeCommandPacketStr(comStmtPrepare, query) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | stmt := &mysqlStmt{ 149 | mc: mc, 150 | } 151 | 152 | // Read Result 153 | columnCount, err := stmt.readPrepareResultPacket() 154 | if err == nil { 155 | if stmt.paramCount > 0 { 156 | if err = mc.readUntilEOF(); err != nil { 157 | return nil, err 158 | } 159 | } 160 | 161 | if columnCount > 0 { 162 | err = mc.readUntilEOF() 163 | } 164 | } 165 | 166 | return stmt, err 167 | } 168 | 169 | func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { 170 | buf := mc.buf.takeCompleteBuffer() 171 | if buf == nil { 172 | // can not take the buffer. Something must be wrong with the connection 173 | errLog.Print(ErrBusyBuffer) 174 | return "", driver.ErrBadConn 175 | } 176 | buf = buf[:0] 177 | argPos := 0 178 | 179 | for i := 0; i < len(query); i++ { 180 | q := strings.IndexByte(query[i:], '?') 181 | if q == -1 { 182 | buf = append(buf, query[i:]...) 183 | break 184 | } 185 | buf = append(buf, query[i:i+q]...) 186 | i += q 187 | 188 | arg := args[argPos] 189 | argPos++ 190 | 191 | if arg == nil { 192 | buf = append(buf, "NULL"...) 193 | continue 194 | } 195 | 196 | switch v := arg.(type) { 197 | case int64: 198 | buf = strconv.AppendInt(buf, v, 10) 199 | case float64: 200 | buf = strconv.AppendFloat(buf, v, 'g', -1, 64) 201 | case bool: 202 | if v { 203 | buf = append(buf, '1') 204 | } else { 205 | buf = append(buf, '0') 206 | } 207 | case time.Time: 208 | if v.IsZero() { 209 | buf = append(buf, "'0000-00-00'"...) 210 | } else { 211 | v := v.In(mc.cfg.loc) 212 | v = v.Add(time.Nanosecond * 500) // To round under microsecond 213 | year := v.Year() 214 | year100 := year / 100 215 | year1 := year % 100 216 | month := v.Month() 217 | day := v.Day() 218 | hour := v.Hour() 219 | minute := v.Minute() 220 | second := v.Second() 221 | micro := v.Nanosecond() / 1000 222 | 223 | buf = append(buf, []byte{ 224 | '\'', 225 | digits10[year100], digits01[year100], 226 | digits10[year1], digits01[year1], 227 | '-', 228 | digits10[month], digits01[month], 229 | '-', 230 | digits10[day], digits01[day], 231 | ' ', 232 | digits10[hour], digits01[hour], 233 | ':', 234 | digits10[minute], digits01[minute], 235 | ':', 236 | digits10[second], digits01[second], 237 | }...) 238 | 239 | if micro != 0 { 240 | micro10000 := micro / 10000 241 | micro100 := micro / 100 % 100 242 | micro1 := micro % 100 243 | buf = append(buf, []byte{ 244 | '.', 245 | digits10[micro10000], digits01[micro10000], 246 | digits10[micro100], digits01[micro100], 247 | digits10[micro1], digits01[micro1], 248 | }...) 249 | } 250 | buf = append(buf, '\'') 251 | } 252 | case []byte: 253 | if v == nil { 254 | buf = append(buf, "NULL"...) 255 | } else { 256 | buf = append(buf, "_binary'"...) 257 | if mc.status&statusNoBackslashEscapes == 0 { 258 | buf = escapeBytesBackslash(buf, v) 259 | } else { 260 | buf = escapeBytesQuotes(buf, v) 261 | } 262 | buf = append(buf, '\'') 263 | } 264 | case string: 265 | buf = append(buf, '\'') 266 | if mc.status&statusNoBackslashEscapes == 0 { 267 | buf = escapeStringBackslash(buf, v) 268 | } else { 269 | buf = escapeStringQuotes(buf, v) 270 | } 271 | buf = append(buf, '\'') 272 | default: 273 | return "", driver.ErrSkip 274 | } 275 | 276 | if len(buf)+4 > mc.maxPacketAllowed { 277 | return "", driver.ErrSkip 278 | } 279 | } 280 | if argPos != len(args) { 281 | return "", driver.ErrSkip 282 | } 283 | return string(buf), nil 284 | } 285 | 286 | func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) { 287 | if mc.netConn == nil { 288 | errLog.Print(ErrInvalidConn) 289 | return nil, driver.ErrBadConn 290 | } 291 | if len(args) != 0 { 292 | if !mc.cfg.interpolateParams { 293 | return nil, driver.ErrSkip 294 | } 295 | // try to interpolate the parameters to save extra roundtrips for preparing and closing a statement 296 | prepared, err := mc.interpolateParams(query, args) 297 | if err != nil { 298 | return nil, err 299 | } 300 | query = prepared 301 | args = nil 302 | } 303 | mc.affectedRows = 0 304 | mc.insertId = 0 305 | 306 | err := mc.exec(query) 307 | if err == nil { 308 | return &mysqlResult{ 309 | affectedRows: int64(mc.affectedRows), 310 | insertId: int64(mc.insertId), 311 | }, err 312 | } 313 | return nil, err 314 | } 315 | 316 | // Internal function to execute commands 317 | func (mc *mysqlConn) exec(query string) error { 318 | // Send command 319 | err := mc.writeCommandPacketStr(comQuery, query) 320 | if err != nil { 321 | return err 322 | } 323 | 324 | // Read Result 325 | resLen, err := mc.readResultSetHeaderPacket() 326 | if err == nil && resLen > 0 { 327 | if err = mc.readUntilEOF(); err != nil { 328 | return err 329 | } 330 | 331 | err = mc.readUntilEOF() 332 | } 333 | 334 | return err 335 | } 336 | 337 | func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) { 338 | if mc.netConn == nil { 339 | errLog.Print(ErrInvalidConn) 340 | return nil, driver.ErrBadConn 341 | } 342 | if len(args) != 0 { 343 | if !mc.cfg.interpolateParams { 344 | return nil, driver.ErrSkip 345 | } 346 | // try client-side prepare to reduce roundtrip 347 | prepared, err := mc.interpolateParams(query, args) 348 | if err != nil { 349 | return nil, err 350 | } 351 | query = prepared 352 | args = nil 353 | } 354 | // Send command 355 | err := mc.writeCommandPacketStr(comQuery, query) 356 | if err == nil { 357 | // Read Result 358 | var resLen int 359 | resLen, err = mc.readResultSetHeaderPacket() 360 | if err == nil { 361 | rows := new(textRows) 362 | rows.mc = mc 363 | 364 | if resLen == 0 { 365 | // no columns, no more data 366 | return emptyRows{}, nil 367 | } 368 | // Columns 369 | rows.columns, err = mc.readColumns(resLen) 370 | return rows, err 371 | } 372 | } 373 | return nil, err 374 | } 375 | 376 | // Gets the value of the given MySQL System Variable 377 | // The returned byte slice is only valid until the next read 378 | func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) { 379 | // Send command 380 | if err := mc.writeCommandPacketStr(comQuery, "SELECT @@"+name); err != nil { 381 | return nil, err 382 | } 383 | 384 | // Read Result 385 | resLen, err := mc.readResultSetHeaderPacket() 386 | if err == nil { 387 | rows := new(textRows) 388 | rows.mc = mc 389 | 390 | if resLen > 0 { 391 | // Columns 392 | if err := mc.readUntilEOF(); err != nil { 393 | return nil, err 394 | } 395 | } 396 | 397 | dest := make([]driver.Value, resLen) 398 | if err = rows.readRow(dest); err == nil { 399 | return dest[0].([]byte), mc.readUntilEOF() 400 | } 401 | } 402 | return nil, err 403 | } 404 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/const.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | const ( 12 | minProtocolVersion byte = 10 13 | maxPacketSize = 1<<24 - 1 14 | timeFormat = "2006-01-02 15:04:05.999999" 15 | ) 16 | 17 | // MySQL constants documentation: 18 | // http://dev.mysql.com/doc/internals/en/client-server-protocol.html 19 | 20 | const ( 21 | iOK byte = 0x00 22 | iLocalInFile byte = 0xfb 23 | iEOF byte = 0xfe 24 | iERR byte = 0xff 25 | ) 26 | 27 | // https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags 28 | type clientFlag uint32 29 | 30 | const ( 31 | clientLongPassword clientFlag = 1 << iota 32 | clientFoundRows 33 | clientLongFlag 34 | clientConnectWithDB 35 | clientNoSchema 36 | clientCompress 37 | clientODBC 38 | clientLocalFiles 39 | clientIgnoreSpace 40 | clientProtocol41 41 | clientInteractive 42 | clientSSL 43 | clientIgnoreSIGPIPE 44 | clientTransactions 45 | clientReserved 46 | clientSecureConn 47 | clientMultiStatements 48 | clientMultiResults 49 | clientPSMultiResults 50 | clientPluginAuth 51 | clientConnectAttrs 52 | clientPluginAuthLenEncClientData 53 | clientCanHandleExpiredPasswords 54 | clientSessionTrack 55 | clientDeprecateEOF 56 | ) 57 | 58 | const ( 59 | comQuit byte = iota + 1 60 | comInitDB 61 | comQuery 62 | comFieldList 63 | comCreateDB 64 | comDropDB 65 | comRefresh 66 | comShutdown 67 | comStatistics 68 | comProcessInfo 69 | comConnect 70 | comProcessKill 71 | comDebug 72 | comPing 73 | comTime 74 | comDelayedInsert 75 | comChangeUser 76 | comBinlogDump 77 | comTableDump 78 | comConnectOut 79 | comRegisterSlave 80 | comStmtPrepare 81 | comStmtExecute 82 | comStmtSendLongData 83 | comStmtClose 84 | comStmtReset 85 | comSetOption 86 | comStmtFetch 87 | ) 88 | 89 | // https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType 90 | const ( 91 | fieldTypeDecimal byte = iota 92 | fieldTypeTiny 93 | fieldTypeShort 94 | fieldTypeLong 95 | fieldTypeFloat 96 | fieldTypeDouble 97 | fieldTypeNULL 98 | fieldTypeTimestamp 99 | fieldTypeLongLong 100 | fieldTypeInt24 101 | fieldTypeDate 102 | fieldTypeTime 103 | fieldTypeDateTime 104 | fieldTypeYear 105 | fieldTypeNewDate 106 | fieldTypeVarChar 107 | fieldTypeBit 108 | ) 109 | const ( 110 | fieldTypeNewDecimal byte = iota + 0xf6 111 | fieldTypeEnum 112 | fieldTypeSet 113 | fieldTypeTinyBLOB 114 | fieldTypeMediumBLOB 115 | fieldTypeLongBLOB 116 | fieldTypeBLOB 117 | fieldTypeVarString 118 | fieldTypeString 119 | fieldTypeGeometry 120 | ) 121 | 122 | type fieldFlag uint16 123 | 124 | const ( 125 | flagNotNULL fieldFlag = 1 << iota 126 | flagPriKey 127 | flagUniqueKey 128 | flagMultipleKey 129 | flagBLOB 130 | flagUnsigned 131 | flagZeroFill 132 | flagBinary 133 | flagEnum 134 | flagAutoIncrement 135 | flagTimestamp 136 | flagSet 137 | flagUnknown1 138 | flagUnknown2 139 | flagUnknown3 140 | flagUnknown4 141 | ) 142 | 143 | // http://dev.mysql.com/doc/internals/en/status-flags.html 144 | type statusFlag uint16 145 | 146 | const ( 147 | statusInTrans statusFlag = 1 << iota 148 | statusInAutocommit 149 | statusReserved // Not in documentation 150 | statusMoreResultsExists 151 | statusNoGoodIndexUsed 152 | statusNoIndexUsed 153 | statusCursorExists 154 | statusLastRowSent 155 | statusDbDropped 156 | statusNoBackslashEscapes 157 | statusMetadataChanged 158 | statusQueryWasSlow 159 | statusPsOutParams 160 | statusInTransReadonly 161 | statusSessionStateChanged 162 | ) 163 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/driver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 2 | // 3 | // This Source Code Form is subject to the terms of the Mozilla Public 4 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 | // You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 8 | // 9 | // The driver should be used via the database/sql package: 10 | // 11 | // import "database/sql" 12 | // import _ "github.com/go-sql-driver/mysql" 13 | // 14 | // db, err := sql.Open("mysql", "user:password@/dbname") 15 | // 16 | // See https://github.com/go-sql-driver/mysql#usage for details 17 | package mysql 18 | 19 | import ( 20 | "database/sql" 21 | "database/sql/driver" 22 | "net" 23 | ) 24 | 25 | // This struct is exported to make the driver directly accessible. 26 | // In general the driver is used via the database/sql package. 27 | type MySQLDriver struct{} 28 | 29 | // DialFunc is a function which can be used to establish the network connection. 30 | // Custom dial functions must be registered with RegisterDial 31 | type DialFunc func(addr string) (net.Conn, error) 32 | 33 | var dials map[string]DialFunc 34 | 35 | // RegisterDial registers a custom dial function. It can then be used by the 36 | // network address mynet(addr), where mynet is the registered new network. 37 | // addr is passed as a parameter to the dial function. 38 | func RegisterDial(net string, dial DialFunc) { 39 | if dials == nil { 40 | dials = make(map[string]DialFunc) 41 | } 42 | dials[net] = dial 43 | } 44 | 45 | // Open new Connection. 46 | // See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how 47 | // the DSN string is formated 48 | func (d MySQLDriver) Open(dsn string) (driver.Conn, error) { 49 | var err error 50 | 51 | // New mysqlConn 52 | mc := &mysqlConn{ 53 | maxPacketAllowed: maxPacketSize, 54 | maxWriteSize: maxPacketSize - 1, 55 | } 56 | mc.cfg, err = parseDSN(dsn) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // Connect to Server 62 | if dial, ok := dials[mc.cfg.net]; ok { 63 | mc.netConn, err = dial(mc.cfg.addr) 64 | } else { 65 | nd := net.Dialer{Timeout: mc.cfg.timeout} 66 | mc.netConn, err = nd.Dial(mc.cfg.net, mc.cfg.addr) 67 | } 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | // Enable TCP Keepalives on TCP connections 73 | if tc, ok := mc.netConn.(*net.TCPConn); ok { 74 | if err := tc.SetKeepAlive(true); err != nil { 75 | // Don't send COM_QUIT before handshake. 76 | mc.netConn.Close() 77 | mc.netConn = nil 78 | return nil, err 79 | } 80 | } 81 | 82 | mc.buf = newBuffer(mc.netConn) 83 | 84 | // Reading Handshake Initialization Packet 85 | cipher, err := mc.readInitPacket() 86 | if err != nil { 87 | mc.Close() 88 | return nil, err 89 | } 90 | 91 | // Send Client Authentication Packet 92 | if err = mc.writeAuthPacket(cipher); err != nil { 93 | mc.Close() 94 | return nil, err 95 | } 96 | 97 | // Read Result Packet 98 | err = mc.readResultOK() 99 | if err != nil { 100 | // Retry with old authentication method, if allowed 101 | if mc.cfg != nil && mc.cfg.allowOldPasswords && err == ErrOldPassword { 102 | if err = mc.writeOldAuthPacket(cipher); err != nil { 103 | mc.Close() 104 | return nil, err 105 | } 106 | if err = mc.readResultOK(); err != nil { 107 | mc.Close() 108 | return nil, err 109 | } 110 | } else if mc.cfg != nil && mc.cfg.allowCleartextPasswords && err == ErrCleartextPassword { 111 | if err = mc.writeClearAuthPacket(); err != nil { 112 | mc.Close() 113 | return nil, err 114 | } 115 | if err = mc.readResultOK(); err != nil { 116 | mc.Close() 117 | return nil, err 118 | } 119 | } else { 120 | mc.Close() 121 | return nil, err 122 | } 123 | 124 | } 125 | 126 | // Get max allowed packet size 127 | maxap, err := mc.getSystemVar("max_allowed_packet") 128 | if err != nil { 129 | mc.Close() 130 | return nil, err 131 | } 132 | mc.maxPacketAllowed = stringToInt(maxap) - 1 133 | if mc.maxPacketAllowed < maxPacketSize { 134 | mc.maxWriteSize = mc.maxPacketAllowed 135 | } 136 | 137 | // Handle DSN Params 138 | err = mc.handleParams() 139 | if err != nil { 140 | mc.Close() 141 | return nil, err 142 | } 143 | 144 | return mc, nil 145 | } 146 | 147 | func init() { 148 | sql.Register("mysql", &MySQLDriver{}) 149 | } 150 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/errors.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | "errors" 14 | "fmt" 15 | "io" 16 | "log" 17 | "os" 18 | ) 19 | 20 | // Various errors the driver might return. Can change between driver versions. 21 | var ( 22 | ErrInvalidConn = errors.New("Invalid Connection") 23 | ErrMalformPkt = errors.New("Malformed Packet") 24 | ErrNoTLS = errors.New("TLS encryption requested but server does not support TLS") 25 | ErrOldPassword = errors.New("This user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords") 26 | ErrCleartextPassword = errors.New("This user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN.") 27 | ErrUnknownPlugin = errors.New("The authentication plugin is not supported.") 28 | ErrOldProtocol = errors.New("MySQL-Server does not support required Protocol 41+") 29 | ErrPktSync = errors.New("Commands out of sync. You can't run this command now") 30 | ErrPktSyncMul = errors.New("Commands out of sync. Did you run multiple statements at once?") 31 | ErrPktTooLarge = errors.New("Packet for query is too large. You can change this value on the server by adjusting the 'max_allowed_packet' variable.") 32 | ErrBusyBuffer = errors.New("Busy buffer") 33 | ) 34 | 35 | var errLog Logger = log.New(os.Stderr, "[MySQL] ", log.Ldate|log.Ltime|log.Lshortfile) 36 | 37 | // Logger is used to log critical error messages. 38 | type Logger interface { 39 | Print(v ...interface{}) 40 | } 41 | 42 | // SetLogger is used to set the logger for critical errors. 43 | // The initial logger is os.Stderr. 44 | func SetLogger(logger Logger) error { 45 | if logger == nil { 46 | return errors.New("logger is nil") 47 | } 48 | errLog = logger 49 | return nil 50 | } 51 | 52 | // MySQLError is an error type which represents a single MySQL error 53 | type MySQLError struct { 54 | Number uint16 55 | Message string 56 | } 57 | 58 | func (me *MySQLError) Error() string { 59 | return fmt.Sprintf("Error %d: %s", me.Number, me.Message) 60 | } 61 | 62 | // MySQLWarnings is an error type which represents a group of one or more MySQL 63 | // warnings 64 | type MySQLWarnings []MySQLWarning 65 | 66 | func (mws MySQLWarnings) Error() string { 67 | var msg string 68 | for i, warning := range mws { 69 | if i > 0 { 70 | msg += "\r\n" 71 | } 72 | msg += fmt.Sprintf( 73 | "%s %s: %s", 74 | warning.Level, 75 | warning.Code, 76 | warning.Message, 77 | ) 78 | } 79 | return msg 80 | } 81 | 82 | // MySQLWarning is an error type which represents a single MySQL warning. 83 | // Warnings are returned in groups only. See MySQLWarnings 84 | type MySQLWarning struct { 85 | Level string 86 | Code string 87 | Message string 88 | } 89 | 90 | func (mc *mysqlConn) getWarnings() (err error) { 91 | rows, err := mc.Query("SHOW WARNINGS", nil) 92 | if err != nil { 93 | return 94 | } 95 | 96 | var warnings = MySQLWarnings{} 97 | var values = make([]driver.Value, 3) 98 | 99 | for { 100 | err = rows.Next(values) 101 | switch err { 102 | case nil: 103 | warning := MySQLWarning{} 104 | 105 | if raw, ok := values[0].([]byte); ok { 106 | warning.Level = string(raw) 107 | } else { 108 | warning.Level = fmt.Sprintf("%s", values[0]) 109 | } 110 | if raw, ok := values[1].([]byte); ok { 111 | warning.Code = string(raw) 112 | } else { 113 | warning.Code = fmt.Sprintf("%s", values[1]) 114 | } 115 | if raw, ok := values[2].([]byte); ok { 116 | warning.Message = string(raw) 117 | } else { 118 | warning.Message = fmt.Sprintf("%s", values[0]) 119 | } 120 | 121 | warnings = append(warnings, warning) 122 | 123 | case io.EOF: 124 | return warnings 125 | 126 | default: 127 | rows.Close() 128 | return 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/infile.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2013 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "fmt" 13 | "io" 14 | "os" 15 | "strings" 16 | "sync" 17 | ) 18 | 19 | var ( 20 | fileRegister map[string]bool 21 | fileRegisterLock sync.RWMutex 22 | readerRegister map[string]func() io.Reader 23 | readerRegisterLock sync.RWMutex 24 | ) 25 | 26 | // RegisterLocalFile adds the given file to the file whitelist, 27 | // so that it can be used by "LOAD DATA LOCAL INFILE ". 28 | // Alternatively you can allow the use of all local files with 29 | // the DSN parameter 'allowAllFiles=true' 30 | // 31 | // filePath := "/home/gopher/data.csv" 32 | // mysql.RegisterLocalFile(filePath) 33 | // err := db.Exec("LOAD DATA LOCAL INFILE '" + filePath + "' INTO TABLE foo") 34 | // if err != nil { 35 | // ... 36 | // 37 | func RegisterLocalFile(filePath string) { 38 | fileRegisterLock.Lock() 39 | // lazy map init 40 | if fileRegister == nil { 41 | fileRegister = make(map[string]bool) 42 | } 43 | 44 | fileRegister[strings.Trim(filePath, `"`)] = true 45 | fileRegisterLock.Unlock() 46 | } 47 | 48 | // DeregisterLocalFile removes the given filepath from the whitelist. 49 | func DeregisterLocalFile(filePath string) { 50 | fileRegisterLock.Lock() 51 | delete(fileRegister, strings.Trim(filePath, `"`)) 52 | fileRegisterLock.Unlock() 53 | } 54 | 55 | // RegisterReaderHandler registers a handler function which is used 56 | // to receive a io.Reader. 57 | // The Reader can be used by "LOAD DATA LOCAL INFILE Reader::". 58 | // If the handler returns a io.ReadCloser Close() is called when the 59 | // request is finished. 60 | // 61 | // mysql.RegisterReaderHandler("data", func() io.Reader { 62 | // var csvReader io.Reader // Some Reader that returns CSV data 63 | // ... // Open Reader here 64 | // return csvReader 65 | // }) 66 | // err := db.Exec("LOAD DATA LOCAL INFILE 'Reader::data' INTO TABLE foo") 67 | // if err != nil { 68 | // ... 69 | // 70 | func RegisterReaderHandler(name string, handler func() io.Reader) { 71 | readerRegisterLock.Lock() 72 | // lazy map init 73 | if readerRegister == nil { 74 | readerRegister = make(map[string]func() io.Reader) 75 | } 76 | 77 | readerRegister[name] = handler 78 | readerRegisterLock.Unlock() 79 | } 80 | 81 | // DeregisterReaderHandler removes the ReaderHandler function with 82 | // the given name from the registry. 83 | func DeregisterReaderHandler(name string) { 84 | readerRegisterLock.Lock() 85 | delete(readerRegister, name) 86 | readerRegisterLock.Unlock() 87 | } 88 | 89 | func deferredClose(err *error, closer io.Closer) { 90 | closeErr := closer.Close() 91 | if *err == nil { 92 | *err = closeErr 93 | } 94 | } 95 | 96 | func (mc *mysqlConn) handleInFileRequest(name string) (err error) { 97 | var rdr io.Reader 98 | var data []byte 99 | 100 | if idx := strings.Index(name, "Reader::"); idx == 0 || (idx > 0 && name[idx-1] == '/') { // io.Reader 101 | // The server might return an an absolute path. See issue #355. 102 | name = name[idx+8:] 103 | 104 | readerRegisterLock.RLock() 105 | handler, inMap := readerRegister[name] 106 | readerRegisterLock.RUnlock() 107 | 108 | if inMap { 109 | rdr = handler() 110 | if rdr != nil { 111 | data = make([]byte, 4+mc.maxWriteSize) 112 | 113 | if cl, ok := rdr.(io.Closer); ok { 114 | defer deferredClose(&err, cl) 115 | } 116 | } else { 117 | err = fmt.Errorf("Reader '%s' is ", name) 118 | } 119 | } else { 120 | err = fmt.Errorf("Reader '%s' is not registered", name) 121 | } 122 | } else { // File 123 | name = strings.Trim(name, `"`) 124 | fileRegisterLock.RLock() 125 | fr := fileRegister[name] 126 | fileRegisterLock.RUnlock() 127 | if mc.cfg.allowAllFiles || fr { 128 | var file *os.File 129 | var fi os.FileInfo 130 | 131 | if file, err = os.Open(name); err == nil { 132 | defer deferredClose(&err, file) 133 | 134 | // get file size 135 | if fi, err = file.Stat(); err == nil { 136 | rdr = file 137 | if fileSize := int(fi.Size()); fileSize <= mc.maxWriteSize { 138 | data = make([]byte, 4+fileSize) 139 | } else if fileSize <= mc.maxPacketAllowed { 140 | data = make([]byte, 4+mc.maxWriteSize) 141 | } else { 142 | err = fmt.Errorf("Local File '%s' too large: Size: %d, Max: %d", name, fileSize, mc.maxPacketAllowed) 143 | } 144 | } 145 | } 146 | } else { 147 | err = fmt.Errorf("Local File '%s' is not registered. Use the DSN parameter 'allowAllFiles=true' to allow all files", name) 148 | } 149 | } 150 | 151 | // send content packets 152 | if err == nil { 153 | var n int 154 | for err == nil { 155 | n, err = rdr.Read(data[4:]) 156 | if n > 0 { 157 | if ioErr := mc.writePacket(data[:4+n]); ioErr != nil { 158 | return ioErr 159 | } 160 | } 161 | } 162 | if err == io.EOF { 163 | err = nil 164 | } 165 | } 166 | 167 | // send empty packet (termination) 168 | if data == nil { 169 | data = make([]byte, 4) 170 | } 171 | if ioErr := mc.writePacket(data[:4]); ioErr != nil { 172 | return ioErr 173 | } 174 | 175 | // read OK packet 176 | if err == nil { 177 | return mc.readResultOK() 178 | } else { 179 | mc.readPacket() 180 | } 181 | return err 182 | } 183 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/result.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | type mysqlResult struct { 12 | affectedRows int64 13 | insertId int64 14 | } 15 | 16 | func (res *mysqlResult) LastInsertId() (int64, error) { 17 | return res.insertId, nil 18 | } 19 | 20 | func (res *mysqlResult) RowsAffected() (int64, error) { 21 | return res.affectedRows, nil 22 | } 23 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/rows.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | "io" 14 | ) 15 | 16 | type mysqlField struct { 17 | tableName string 18 | name string 19 | flags fieldFlag 20 | fieldType byte 21 | decimals byte 22 | } 23 | 24 | type mysqlRows struct { 25 | mc *mysqlConn 26 | columns []mysqlField 27 | } 28 | 29 | type binaryRows struct { 30 | mysqlRows 31 | } 32 | 33 | type textRows struct { 34 | mysqlRows 35 | } 36 | 37 | type emptyRows struct{} 38 | 39 | func (rows *mysqlRows) Columns() []string { 40 | columns := make([]string, len(rows.columns)) 41 | if rows.mc.cfg.columnsWithAlias { 42 | for i := range columns { 43 | if tableName := rows.columns[i].tableName; len(tableName) > 0 { 44 | columns[i] = tableName + "." + rows.columns[i].name 45 | } else { 46 | columns[i] = rows.columns[i].name 47 | } 48 | } 49 | } else { 50 | for i := range columns { 51 | columns[i] = rows.columns[i].name 52 | } 53 | } 54 | return columns 55 | } 56 | 57 | func (rows *mysqlRows) Close() error { 58 | mc := rows.mc 59 | if mc == nil { 60 | return nil 61 | } 62 | if mc.netConn == nil { 63 | return ErrInvalidConn 64 | } 65 | 66 | // Remove unread packets from stream 67 | err := mc.readUntilEOF() 68 | rows.mc = nil 69 | return err 70 | } 71 | 72 | func (rows *binaryRows) Next(dest []driver.Value) error { 73 | if mc := rows.mc; mc != nil { 74 | if mc.netConn == nil { 75 | return ErrInvalidConn 76 | } 77 | 78 | // Fetch next row from stream 79 | return rows.readRow(dest) 80 | } 81 | return io.EOF 82 | } 83 | 84 | func (rows *textRows) Next(dest []driver.Value) error { 85 | if mc := rows.mc; mc != nil { 86 | if mc.netConn == nil { 87 | return ErrInvalidConn 88 | } 89 | 90 | // Fetch next row from stream 91 | return rows.readRow(dest) 92 | } 93 | return io.EOF 94 | } 95 | 96 | func (rows emptyRows) Columns() []string { 97 | return nil 98 | } 99 | 100 | func (rows emptyRows) Close() error { 101 | return nil 102 | } 103 | 104 | func (rows emptyRows) Next(dest []driver.Value) error { 105 | return io.EOF 106 | } 107 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/statement.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | import ( 12 | "database/sql/driver" 13 | "fmt" 14 | "reflect" 15 | "strconv" 16 | ) 17 | 18 | type mysqlStmt struct { 19 | mc *mysqlConn 20 | id uint32 21 | paramCount int 22 | columns []mysqlField // cached from the first query 23 | } 24 | 25 | func (stmt *mysqlStmt) Close() error { 26 | if stmt.mc == nil || stmt.mc.netConn == nil { 27 | errLog.Print(ErrInvalidConn) 28 | return driver.ErrBadConn 29 | } 30 | 31 | err := stmt.mc.writeCommandPacketUint32(comStmtClose, stmt.id) 32 | stmt.mc = nil 33 | return err 34 | } 35 | 36 | func (stmt *mysqlStmt) NumInput() int { 37 | return stmt.paramCount 38 | } 39 | 40 | func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter { 41 | return converter{} 42 | } 43 | 44 | func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) { 45 | if stmt.mc.netConn == nil { 46 | errLog.Print(ErrInvalidConn) 47 | return nil, driver.ErrBadConn 48 | } 49 | // Send command 50 | err := stmt.writeExecutePacket(args) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | mc := stmt.mc 56 | 57 | mc.affectedRows = 0 58 | mc.insertId = 0 59 | 60 | // Read Result 61 | resLen, err := mc.readResultSetHeaderPacket() 62 | if err == nil { 63 | if resLen > 0 { 64 | // Columns 65 | err = mc.readUntilEOF() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | // Rows 71 | err = mc.readUntilEOF() 72 | } 73 | if err == nil { 74 | return &mysqlResult{ 75 | affectedRows: int64(mc.affectedRows), 76 | insertId: int64(mc.insertId), 77 | }, nil 78 | } 79 | } 80 | 81 | return nil, err 82 | } 83 | 84 | func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) { 85 | if stmt.mc.netConn == nil { 86 | errLog.Print(ErrInvalidConn) 87 | return nil, driver.ErrBadConn 88 | } 89 | // Send command 90 | err := stmt.writeExecutePacket(args) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | mc := stmt.mc 96 | 97 | // Read Result 98 | resLen, err := mc.readResultSetHeaderPacket() 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | rows := new(binaryRows) 104 | rows.mc = mc 105 | 106 | if resLen > 0 { 107 | // Columns 108 | // If not cached, read them and cache them 109 | if stmt.columns == nil { 110 | rows.columns, err = mc.readColumns(resLen) 111 | stmt.columns = rows.columns 112 | } else { 113 | rows.columns = stmt.columns 114 | err = mc.readUntilEOF() 115 | } 116 | } 117 | 118 | return rows, err 119 | } 120 | 121 | type converter struct{} 122 | 123 | func (c converter) ConvertValue(v interface{}) (driver.Value, error) { 124 | if driver.IsValue(v) { 125 | return v, nil 126 | } 127 | 128 | rv := reflect.ValueOf(v) 129 | switch rv.Kind() { 130 | case reflect.Ptr: 131 | // indirect pointers 132 | if rv.IsNil() { 133 | return nil, nil 134 | } 135 | return c.ConvertValue(rv.Elem().Interface()) 136 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 137 | return rv.Int(), nil 138 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32: 139 | return int64(rv.Uint()), nil 140 | case reflect.Uint64: 141 | u64 := rv.Uint() 142 | if u64 >= 1<<63 { 143 | return strconv.FormatUint(u64, 10), nil 144 | } 145 | return int64(u64), nil 146 | case reflect.Float32, reflect.Float64: 147 | return rv.Float(), nil 148 | } 149 | return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind()) 150 | } 151 | -------------------------------------------------------------------------------- /vendor/github.com/go-sql-driver/mysql/transaction.go: -------------------------------------------------------------------------------- 1 | // Go MySQL Driver - A MySQL-Driver for Go's database/sql package 2 | // 3 | // Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. 4 | // 5 | // This Source Code Form is subject to the terms of the Mozilla Public 6 | // License, v. 2.0. If a copy of the MPL was not distributed with this file, 7 | // You can obtain one at http://mozilla.org/MPL/2.0/. 8 | 9 | package mysql 10 | 11 | type mysqlTx struct { 12 | mc *mysqlConn 13 | } 14 | 15 | func (tx *mysqlTx) Commit() (err error) { 16 | if tx.mc == nil || tx.mc.netConn == nil { 17 | return ErrInvalidConn 18 | } 19 | err = tx.mc.exec("COMMIT") 20 | tx.mc = nil 21 | return 22 | } 23 | 24 | func (tx *mysqlTx) Rollback() (err error) { 25 | if tx.mc == nil || tx.mc.netConn == nil { 26 | return ErrInvalidConn 27 | } 28 | err = tx.mc.exec("ROLLBACK") 29 | tx.mc = nil 30 | return 31 | } 32 | -------------------------------------------------------------------------------- /vendor/github.com/outbrain/golib/log/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 Outbrain Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package log 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "log/syslog" 23 | "os" 24 | "runtime/debug" 25 | "time" 26 | ) 27 | 28 | // LogLevel indicates the severity of a log entry 29 | type LogLevel int 30 | 31 | func (this LogLevel) String() string { 32 | switch this { 33 | case FATAL: 34 | return "FATAL" 35 | case CRITICAL: 36 | return "CRITICAL" 37 | case ERROR: 38 | return "ERROR" 39 | case WARNING: 40 | return "WARNING" 41 | case NOTICE: 42 | return "NOTICE" 43 | case INFO: 44 | return "INFO" 45 | case DEBUG: 46 | return "DEBUG" 47 | } 48 | return "unknown" 49 | } 50 | 51 | func LogLevelFromString(logLevelName string) (LogLevel, error) { 52 | switch logLevelName { 53 | case "FATAL": 54 | return FATAL, nil 55 | case "CRITICAL": 56 | return CRITICAL, nil 57 | case "ERROR": 58 | return ERROR, nil 59 | case "WARNING": 60 | return WARNING, nil 61 | case "NOTICE": 62 | return NOTICE, nil 63 | case "INFO": 64 | return INFO, nil 65 | case "DEBUG": 66 | return DEBUG, nil 67 | } 68 | return 0, fmt.Errorf("Unknown LogLevel name: %+v", logLevelName) 69 | } 70 | 71 | const ( 72 | FATAL LogLevel = iota 73 | CRITICAL 74 | ERROR 75 | WARNING 76 | NOTICE 77 | INFO 78 | DEBUG 79 | ) 80 | 81 | const TimeFormat = "2006-01-02 15:04:05" 82 | 83 | // globalLogLevel indicates the global level filter for all logs (only entries with level equals or higher 84 | // than this value will be logged) 85 | var globalLogLevel LogLevel = DEBUG 86 | var printStackTrace bool = false 87 | 88 | // syslogWriter is optional, and defaults to nil (disabled) 89 | var syslogLevel LogLevel = ERROR 90 | var syslogWriter *syslog.Writer 91 | 92 | // SetPrintStackTrace enables/disables dumping the stack upon error logging 93 | func SetPrintStackTrace(shouldPrintStackTrace bool) { 94 | printStackTrace = shouldPrintStackTrace 95 | } 96 | 97 | // SetLevel sets the global log level. Only entries with level equals or higher than 98 | // this value will be logged 99 | func SetLevel(logLevel LogLevel) { 100 | globalLogLevel = logLevel 101 | } 102 | 103 | // GetLevel returns current global log level 104 | func GetLevel() LogLevel { 105 | return globalLogLevel 106 | } 107 | 108 | // EnableSyslogWriter enables, if possible, writes to syslog. These will execute _in addition_ to normal logging 109 | func EnableSyslogWriter(tag string) (err error) { 110 | syslogWriter, err = syslog.New(syslog.LOG_ERR, tag) 111 | if err != nil { 112 | syslogWriter = nil 113 | } 114 | return err 115 | } 116 | 117 | // SetSyslogLevel sets the minimal syslog level. Only entries with level equals or higher than 118 | // this value will be logged. However, this is also capped by the global log level. That is, 119 | // messages with lower level than global-log-level will be discarded at any case. 120 | func SetSyslogLevel(logLevel LogLevel) { 121 | syslogLevel = logLevel 122 | } 123 | 124 | // logFormattedEntry nicely formats and emits a log entry 125 | func logFormattedEntry(logLevel LogLevel, message string, args ...interface{}) string { 126 | if logLevel > globalLogLevel { 127 | return "" 128 | } 129 | msgArgs := fmt.Sprintf(message, args...) 130 | entryString := fmt.Sprintf("%s %s %s", time.Now().Format(TimeFormat), logLevel, msgArgs) 131 | fmt.Fprintln(os.Stderr, entryString) 132 | 133 | if syslogWriter != nil { 134 | go func() error { 135 | if logLevel > syslogLevel { 136 | return nil 137 | } 138 | switch logLevel { 139 | case FATAL: 140 | return syslogWriter.Emerg(msgArgs) 141 | case CRITICAL: 142 | return syslogWriter.Crit(msgArgs) 143 | case ERROR: 144 | return syslogWriter.Err(msgArgs) 145 | case WARNING: 146 | return syslogWriter.Warning(msgArgs) 147 | case NOTICE: 148 | return syslogWriter.Notice(msgArgs) 149 | case INFO: 150 | return syslogWriter.Info(msgArgs) 151 | case DEBUG: 152 | return syslogWriter.Debug(msgArgs) 153 | } 154 | return nil 155 | }() 156 | } 157 | return entryString 158 | } 159 | 160 | // logEntry emits a formatted log entry 161 | func logEntry(logLevel LogLevel, message string, args ...interface{}) string { 162 | entryString := message 163 | for _, s := range args { 164 | entryString += fmt.Sprintf(" %s", s) 165 | } 166 | return logFormattedEntry(logLevel, entryString) 167 | } 168 | 169 | // logErrorEntry emits a log entry based on given error object 170 | func logErrorEntry(logLevel LogLevel, err error) error { 171 | if err == nil { 172 | // No error 173 | return nil 174 | } 175 | entryString := fmt.Sprintf("%+v", err) 176 | logEntry(logLevel, entryString) 177 | if printStackTrace { 178 | debug.PrintStack() 179 | } 180 | return err 181 | } 182 | 183 | func Debug(message string, args ...interface{}) string { 184 | return logEntry(DEBUG, message, args...) 185 | } 186 | 187 | func Debugf(message string, args ...interface{}) string { 188 | return logFormattedEntry(DEBUG, message, args...) 189 | } 190 | 191 | func Info(message string, args ...interface{}) string { 192 | return logEntry(INFO, message, args...) 193 | } 194 | 195 | func Infof(message string, args ...interface{}) string { 196 | return logFormattedEntry(INFO, message, args...) 197 | } 198 | 199 | func Notice(message string, args ...interface{}) string { 200 | return logEntry(NOTICE, message, args...) 201 | } 202 | 203 | func Noticef(message string, args ...interface{}) string { 204 | return logFormattedEntry(NOTICE, message, args...) 205 | } 206 | 207 | func Warning(message string, args ...interface{}) error { 208 | return errors.New(logEntry(WARNING, message, args...)) 209 | } 210 | 211 | func Warningf(message string, args ...interface{}) error { 212 | return errors.New(logFormattedEntry(WARNING, message, args...)) 213 | } 214 | 215 | func Error(message string, args ...interface{}) error { 216 | return errors.New(logEntry(ERROR, message, args...)) 217 | } 218 | 219 | func Errorf(message string, args ...interface{}) error { 220 | return errors.New(logFormattedEntry(ERROR, message, args...)) 221 | } 222 | 223 | func Errore(err error) error { 224 | return logErrorEntry(ERROR, err) 225 | } 226 | 227 | func Critical(message string, args ...interface{}) error { 228 | return errors.New(logEntry(CRITICAL, message, args...)) 229 | } 230 | 231 | func Criticalf(message string, args ...interface{}) error { 232 | return errors.New(logFormattedEntry(CRITICAL, message, args...)) 233 | } 234 | 235 | func Criticale(err error) error { 236 | return logErrorEntry(CRITICAL, err) 237 | } 238 | 239 | // Fatal emits a FATAL level entry and exists the program 240 | func Fatal(message string, args ...interface{}) error { 241 | logEntry(FATAL, message, args...) 242 | os.Exit(1) 243 | return errors.New(logEntry(CRITICAL, message, args...)) 244 | } 245 | 246 | // Fatalf emits a FATAL level entry and exists the program 247 | func Fatalf(message string, args ...interface{}) error { 248 | logFormattedEntry(FATAL, message, args...) 249 | os.Exit(1) 250 | return errors.New(logFormattedEntry(CRITICAL, message, args...)) 251 | } 252 | 253 | // Fatale emits a FATAL level entry and exists the program 254 | func Fatale(err error) error { 255 | logErrorEntry(FATAL, err) 256 | os.Exit(1) 257 | return err 258 | } 259 | -------------------------------------------------------------------------------- /vendor/github.com/outbrain/golib/sqlutils/sqlutils.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2014 Outbrain Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sqlutils 18 | 19 | import ( 20 | "database/sql" 21 | "encoding/json" 22 | "errors" 23 | "fmt" 24 | _ "github.com/go-sql-driver/mysql" 25 | "github.com/outbrain/golib/log" 26 | "strconv" 27 | "strings" 28 | "sync" 29 | ) 30 | 31 | // RowMap represents one row in a result set. Its objective is to allow 32 | // for easy, typed getters by column name. 33 | type RowMap map[string]CellData 34 | 35 | // Cell data is the result of a single (atomic) column in a single row 36 | type CellData sql.NullString 37 | 38 | func (this *CellData) MarshalJSON() ([]byte, error) { 39 | if this.Valid { 40 | return json.Marshal(this.String) 41 | } else { 42 | return json.Marshal(nil) 43 | } 44 | } 45 | 46 | func (this *CellData) NullString() *sql.NullString { 47 | return (*sql.NullString)(this) 48 | } 49 | 50 | // RowData is the result of a single row, in positioned array format 51 | type RowData []CellData 52 | 53 | // MarshalJSON will marshal this map as JSON 54 | func (this *RowData) MarshalJSON() ([]byte, error) { 55 | cells := make([](*CellData), len(*this), len(*this)) 56 | for i, val := range *this { 57 | d := CellData(val) 58 | cells[i] = &d 59 | } 60 | return json.Marshal(cells) 61 | } 62 | 63 | // ResultData is an ordered row set of RowData 64 | type ResultData []RowData 65 | 66 | var EmptyResultData = ResultData{} 67 | 68 | func (this *RowMap) GetString(key string) string { 69 | return (*this)[key].String 70 | } 71 | 72 | // GetStringD returns a string from the map, or a default value if the key does not exist 73 | func (this *RowMap) GetStringD(key string, def string) string { 74 | if cell, ok := (*this)[key]; ok { 75 | return cell.String 76 | } 77 | return def 78 | } 79 | 80 | func (this *RowMap) GetInt64(key string) int64 { 81 | res, _ := strconv.ParseInt(this.GetString(key), 10, 0) 82 | return res 83 | } 84 | 85 | func (this *RowMap) GetNullInt64(key string) sql.NullInt64 { 86 | i, err := strconv.ParseInt(this.GetString(key), 10, 0) 87 | if err == nil { 88 | return sql.NullInt64{Int64: i, Valid: true} 89 | } else { 90 | return sql.NullInt64{Valid: false} 91 | } 92 | } 93 | 94 | func (this *RowMap) GetInt(key string) int { 95 | res, _ := strconv.Atoi(this.GetString(key)) 96 | return res 97 | } 98 | 99 | func (this *RowMap) GetIntD(key string, def int) int { 100 | res, err := strconv.Atoi(this.GetString(key)) 101 | if err != nil { 102 | return def 103 | } 104 | return res 105 | } 106 | 107 | func (this *RowMap) GetUint(key string) uint { 108 | res, _ := strconv.Atoi(this.GetString(key)) 109 | return uint(res) 110 | } 111 | 112 | func (this *RowMap) GetUintD(key string, def uint) uint { 113 | res, err := strconv.Atoi(this.GetString(key)) 114 | if err != nil { 115 | return def 116 | } 117 | return uint(res) 118 | } 119 | 120 | func (this *RowMap) GetBool(key string) bool { 121 | return this.GetInt(key) != 0 122 | } 123 | 124 | // knownDBs is a DB cache by uri 125 | var knownDBs map[string]*sql.DB = make(map[string]*sql.DB) 126 | var knownDBsMutex = &sync.Mutex{} 127 | 128 | // GetDB returns a DB instance based on uri. 129 | // bool result indicates whether the DB was returned from cache; err 130 | func GetDB(mysql_uri string) (*sql.DB, bool, error) { 131 | knownDBsMutex.Lock() 132 | defer func() { 133 | knownDBsMutex.Unlock() 134 | }() 135 | 136 | var exists bool 137 | if _, exists = knownDBs[mysql_uri]; !exists { 138 | if db, err := sql.Open("mysql", mysql_uri); err == nil { 139 | knownDBs[mysql_uri] = db 140 | } else { 141 | return db, exists, err 142 | } 143 | } 144 | return knownDBs[mysql_uri], exists, nil 145 | } 146 | 147 | // RowToArray is a convenience function, typically not called directly, which maps a 148 | // single read database row into a NullString 149 | func RowToArray(rows *sql.Rows, columns []string) []CellData { 150 | buff := make([]interface{}, len(columns)) 151 | data := make([]CellData, len(columns)) 152 | for i, _ := range buff { 153 | buff[i] = data[i].NullString() 154 | } 155 | rows.Scan(buff...) 156 | return data 157 | } 158 | 159 | // ScanRowsToArrays is a convenience function, typically not called directly, which maps rows 160 | // already read from the databse into arrays of NullString 161 | func ScanRowsToArrays(rows *sql.Rows, on_row func([]CellData) error) error { 162 | columns, _ := rows.Columns() 163 | for rows.Next() { 164 | arr := RowToArray(rows, columns) 165 | err := on_row(arr) 166 | if err != nil { 167 | return err 168 | } 169 | } 170 | return nil 171 | } 172 | 173 | func rowToMap(row []CellData, columns []string) map[string]CellData { 174 | m := make(map[string]CellData) 175 | for k, data_col := range row { 176 | m[columns[k]] = data_col 177 | } 178 | return m 179 | } 180 | 181 | // ScanRowsToMaps is a convenience function, typically not called directly, which maps rows 182 | // already read from the databse into RowMap entries. 183 | func ScanRowsToMaps(rows *sql.Rows, on_row func(RowMap) error) error { 184 | columns, _ := rows.Columns() 185 | err := ScanRowsToArrays(rows, func(arr []CellData) error { 186 | m := rowToMap(arr, columns) 187 | err := on_row(m) 188 | if err != nil { 189 | return err 190 | } 191 | return nil 192 | }) 193 | return err 194 | } 195 | 196 | // QueryRowsMap is a convenience function allowing querying a result set while poviding a callback 197 | // function activated per read row. 198 | func QueryRowsMap(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error { 199 | var err error 200 | defer func() { 201 | if derr := recover(); derr != nil { 202 | err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr)) 203 | } 204 | }() 205 | 206 | rows, err := db.Query(query, args...) 207 | defer rows.Close() 208 | if err != nil && err != sql.ErrNoRows { 209 | return log.Errore(err) 210 | } 211 | err = ScanRowsToMaps(rows, on_row) 212 | return err 213 | } 214 | 215 | // queryResultData returns a raw array of rows for a given query, optionally reading and returning column names 216 | func queryResultData(db *sql.DB, query string, retrieveColumns bool, args ...interface{}) (ResultData, []string, error) { 217 | var err error 218 | defer func() { 219 | if derr := recover(); derr != nil { 220 | err = errors.New(fmt.Sprintf("QueryRowsMap unexpected error: %+v", derr)) 221 | } 222 | }() 223 | 224 | columns := []string{} 225 | rows, err := db.Query(query, args...) 226 | defer rows.Close() 227 | if err != nil && err != sql.ErrNoRows { 228 | return EmptyResultData, columns, log.Errore(err) 229 | } 230 | if retrieveColumns { 231 | // Don't pay if you don't want to 232 | columns, _ = rows.Columns() 233 | } 234 | resultData := ResultData{} 235 | err = ScanRowsToArrays(rows, func(rowData []CellData) error { 236 | resultData = append(resultData, rowData) 237 | return nil 238 | }) 239 | return resultData, columns, err 240 | } 241 | 242 | // QueryResultData returns a raw array of rows 243 | func QueryResultData(db *sql.DB, query string, args ...interface{}) (ResultData, error) { 244 | resultData, _, err := queryResultData(db, query, false, args...) 245 | return resultData, err 246 | } 247 | 248 | // QueryResultDataNamed returns a raw array of rows, with column names 249 | func QueryResultDataNamed(db *sql.DB, query string, args ...interface{}) (ResultData, []string, error) { 250 | return queryResultData(db, query, true, args...) 251 | } 252 | 253 | // QueryRowsMapBuffered reads data from the database into a buffer, and only then applies the given function per row. 254 | // This allows the application to take its time with processing the data, albeit consuming as much memory as required by 255 | // the result set. 256 | func QueryRowsMapBuffered(db *sql.DB, query string, on_row func(RowMap) error, args ...interface{}) error { 257 | resultData, columns, err := queryResultData(db, query, true, args...) 258 | if err != nil { 259 | // Already logged 260 | return err 261 | } 262 | for _, row := range resultData { 263 | err = on_row(rowToMap(row, columns)) 264 | if err != nil { 265 | return err 266 | } 267 | } 268 | return nil 269 | } 270 | 271 | // ExecNoPrepare executes given query using given args on given DB, without using prepared statements. 272 | func ExecNoPrepare(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { 273 | var err error 274 | defer func() { 275 | if derr := recover(); derr != nil { 276 | err = errors.New(fmt.Sprintf("ExecNoPrepare unexpected error: %+v", derr)) 277 | } 278 | }() 279 | 280 | var res sql.Result 281 | res, err = db.Exec(query, args...) 282 | if err != nil { 283 | log.Errore(err) 284 | } 285 | return res, err 286 | } 287 | 288 | // ExecQuery executes given query using given args on given DB. It will safele prepare, execute and close 289 | // the statement. 290 | func execInternal(silent bool, db *sql.DB, query string, args ...interface{}) (sql.Result, error) { 291 | var err error 292 | defer func() { 293 | if derr := recover(); derr != nil { 294 | err = errors.New(fmt.Sprintf("execInternal unexpected error: %+v", derr)) 295 | } 296 | }() 297 | 298 | stmt, err := db.Prepare(query) 299 | if err != nil { 300 | return nil, err 301 | } 302 | defer stmt.Close() 303 | var res sql.Result 304 | res, err = stmt.Exec(args...) 305 | if err != nil && !silent { 306 | log.Errore(err) 307 | } 308 | return res, err 309 | } 310 | 311 | // Exec executes given query using given args on given DB. It will safele prepare, execute and close 312 | // the statement. 313 | func Exec(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { 314 | return execInternal(false, db, query, args...) 315 | } 316 | 317 | // ExecSilently acts like Exec but does not report any error 318 | func ExecSilently(db *sql.DB, query string, args ...interface{}) (sql.Result, error) { 319 | return execInternal(true, db, query, args...) 320 | } 321 | 322 | func InClauseStringValues(terms []string) string { 323 | quoted := []string{} 324 | for _, s := range terms { 325 | quoted = append(quoted, fmt.Sprintf("'%s'", strings.Replace(s, ",", "''", -1))) 326 | } 327 | return strings.Join(quoted, ", ") 328 | } 329 | 330 | // Convert variable length arguments into arguments array 331 | func Args(args ...interface{}) []interface{} { 332 | return args 333 | } 334 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/terminal_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package terminal 6 | 7 | import ( 8 | "io" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | type MockTerminal struct { 14 | toSend []byte 15 | bytesPerRead int 16 | received []byte 17 | } 18 | 19 | func (c *MockTerminal) Read(data []byte) (n int, err error) { 20 | n = len(data) 21 | if n == 0 { 22 | return 23 | } 24 | if n > len(c.toSend) { 25 | n = len(c.toSend) 26 | } 27 | if n == 0 { 28 | return 0, io.EOF 29 | } 30 | if c.bytesPerRead > 0 && n > c.bytesPerRead { 31 | n = c.bytesPerRead 32 | } 33 | copy(data, c.toSend[:n]) 34 | c.toSend = c.toSend[n:] 35 | return 36 | } 37 | 38 | func (c *MockTerminal) Write(data []byte) (n int, err error) { 39 | c.received = append(c.received, data...) 40 | return len(data), nil 41 | } 42 | 43 | func TestClose(t *testing.T) { 44 | c := &MockTerminal{} 45 | ss := NewTerminal(c, "> ") 46 | line, err := ss.ReadLine() 47 | if line != "" { 48 | t.Errorf("Expected empty line but got: %s", line) 49 | } 50 | if err != io.EOF { 51 | t.Errorf("Error should have been EOF but got: %s", err) 52 | } 53 | } 54 | 55 | var keyPressTests = []struct { 56 | in string 57 | line string 58 | err error 59 | throwAwayLines int 60 | }{ 61 | { 62 | err: io.EOF, 63 | }, 64 | { 65 | in: "\r", 66 | line: "", 67 | }, 68 | { 69 | in: "foo\r", 70 | line: "foo", 71 | }, 72 | { 73 | in: "a\x1b[Cb\r", // right 74 | line: "ab", 75 | }, 76 | { 77 | in: "a\x1b[Db\r", // left 78 | line: "ba", 79 | }, 80 | { 81 | in: "a\177b\r", // backspace 82 | line: "b", 83 | }, 84 | { 85 | in: "\x1b[A\r", // up 86 | }, 87 | { 88 | in: "\x1b[B\r", // down 89 | }, 90 | { 91 | in: "line\x1b[A\x1b[B\r", // up then down 92 | line: "line", 93 | }, 94 | { 95 | in: "line1\rline2\x1b[A\r", // recall previous line. 96 | line: "line1", 97 | throwAwayLines: 1, 98 | }, 99 | { 100 | // recall two previous lines and append. 101 | in: "line1\rline2\rline3\x1b[A\x1b[Axxx\r", 102 | line: "line1xxx", 103 | throwAwayLines: 2, 104 | }, 105 | { 106 | // Ctrl-A to move to beginning of line followed by ^K to kill 107 | // line. 108 | in: "a b \001\013\r", 109 | line: "", 110 | }, 111 | { 112 | // Ctrl-A to move to beginning of line, Ctrl-E to move to end, 113 | // finally ^K to kill nothing. 114 | in: "a b \001\005\013\r", 115 | line: "a b ", 116 | }, 117 | { 118 | in: "\027\r", 119 | line: "", 120 | }, 121 | { 122 | in: "a\027\r", 123 | line: "", 124 | }, 125 | { 126 | in: "a \027\r", 127 | line: "", 128 | }, 129 | { 130 | in: "a b\027\r", 131 | line: "a ", 132 | }, 133 | { 134 | in: "a b \027\r", 135 | line: "a ", 136 | }, 137 | { 138 | in: "one two thr\x1b[D\027\r", 139 | line: "one two r", 140 | }, 141 | { 142 | in: "\013\r", 143 | line: "", 144 | }, 145 | { 146 | in: "a\013\r", 147 | line: "a", 148 | }, 149 | { 150 | in: "ab\x1b[D\013\r", 151 | line: "a", 152 | }, 153 | { 154 | in: "Ξεσκεπάζω\r", 155 | line: "Ξεσκεπάζω", 156 | }, 157 | { 158 | in: "£\r\x1b[A\177\r", // non-ASCII char, enter, up, backspace. 159 | line: "", 160 | throwAwayLines: 1, 161 | }, 162 | { 163 | in: "£\r££\x1b[A\x1b[B\177\r", // non-ASCII char, enter, 2x non-ASCII, up, down, backspace, enter. 164 | line: "£", 165 | throwAwayLines: 1, 166 | }, 167 | { 168 | // Ctrl-D at the end of the line should be ignored. 169 | in: "a\004\r", 170 | line: "a", 171 | }, 172 | { 173 | // a, b, left, Ctrl-D should erase the b. 174 | in: "ab\x1b[D\004\r", 175 | line: "a", 176 | }, 177 | { 178 | // a, b, c, d, left, left, ^U should erase to the beginning of 179 | // the line. 180 | in: "abcd\x1b[D\x1b[D\025\r", 181 | line: "cd", 182 | }, 183 | { 184 | // Bracketed paste mode: control sequences should be returned 185 | // verbatim in paste mode. 186 | in: "abc\x1b[200~de\177f\x1b[201~\177\r", 187 | line: "abcde\177", 188 | }, 189 | { 190 | // Enter in bracketed paste mode should still work. 191 | in: "abc\x1b[200~d\refg\x1b[201~h\r", 192 | line: "efgh", 193 | throwAwayLines: 1, 194 | }, 195 | { 196 | // Lines consisting entirely of pasted data should be indicated as such. 197 | in: "\x1b[200~a\r", 198 | line: "a", 199 | err: ErrPasteIndicator, 200 | }, 201 | } 202 | 203 | func TestKeyPresses(t *testing.T) { 204 | for i, test := range keyPressTests { 205 | for j := 1; j < len(test.in); j++ { 206 | c := &MockTerminal{ 207 | toSend: []byte(test.in), 208 | bytesPerRead: j, 209 | } 210 | ss := NewTerminal(c, "> ") 211 | for k := 0; k < test.throwAwayLines; k++ { 212 | _, err := ss.ReadLine() 213 | if err != nil { 214 | t.Errorf("Throwaway line %d from test %d resulted in error: %s", k, i, err) 215 | } 216 | } 217 | line, err := ss.ReadLine() 218 | if line != test.line { 219 | t.Errorf("Line resulting from test %d (%d bytes per read) was '%s', expected '%s'", i, j, line, test.line) 220 | break 221 | } 222 | if err != test.err { 223 | t.Errorf("Error resulting from test %d (%d bytes per read) was '%v', expected '%v'", i, j, err, test.err) 224 | break 225 | } 226 | } 227 | } 228 | } 229 | 230 | func TestPasswordNotSaved(t *testing.T) { 231 | c := &MockTerminal{ 232 | toSend: []byte("password\r\x1b[A\r"), 233 | bytesPerRead: 1, 234 | } 235 | ss := NewTerminal(c, "> ") 236 | pw, _ := ss.ReadPassword("> ") 237 | if pw != "password" { 238 | t.Fatalf("failed to read password, got %s", pw) 239 | } 240 | line, _ := ss.ReadLine() 241 | if len(line) > 0 { 242 | t.Fatalf("password was saved in history") 243 | } 244 | } 245 | 246 | var setSizeTests = []struct { 247 | width, height int 248 | }{ 249 | {40, 13}, 250 | {80, 24}, 251 | {132, 43}, 252 | } 253 | 254 | func TestTerminalSetSize(t *testing.T) { 255 | for _, setSize := range setSizeTests { 256 | c := &MockTerminal{ 257 | toSend: []byte("password\r\x1b[A\r"), 258 | bytesPerRead: 1, 259 | } 260 | ss := NewTerminal(c, "> ") 261 | ss.SetSize(setSize.width, setSize.height) 262 | pw, _ := ss.ReadPassword("Password: ") 263 | if pw != "password" { 264 | t.Fatalf("failed to read password, got %s", pw) 265 | } 266 | if string(c.received) != "Password: \r\n" { 267 | t.Errorf("failed to set the temporary prompt expected %q, got %q", "Password: ", c.received) 268 | } 269 | } 270 | } 271 | 272 | func TestMakeRawState(t *testing.T) { 273 | fd := int(os.Stdout.Fd()) 274 | if !IsTerminal(fd) { 275 | t.Skip("stdout is not a terminal; skipping test") 276 | } 277 | 278 | st, err := GetState(fd) 279 | if err != nil { 280 | t.Fatalf("failed to get terminal state from GetState: %s", err) 281 | } 282 | defer Restore(fd, st) 283 | raw, err := MakeRaw(fd) 284 | if err != nil { 285 | t.Fatalf("failed to get terminal state from MakeRaw: %s", err) 286 | } 287 | 288 | if *st != *raw { 289 | t.Errorf("states do not match; was %v, expected %v", raw, st) 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin dragonfly freebsd linux,!appengine netbsd openbsd 6 | 7 | // Package terminal provides support functions for dealing with terminals, as 8 | // commonly found on UNIX systems. 9 | // 10 | // Putting a terminal into raw mode is the most common requirement: 11 | // 12 | // oldState, err := terminal.MakeRaw(0) 13 | // if err != nil { 14 | // panic(err) 15 | // } 16 | // defer terminal.Restore(0, oldState) 17 | package terminal // import "golang.org/x/crypto/ssh/terminal" 18 | 19 | import ( 20 | "io" 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | // State contains the state of a terminal. 26 | type State struct { 27 | termios syscall.Termios 28 | } 29 | 30 | // IsTerminal returns true if the given file descriptor is a terminal. 31 | func IsTerminal(fd int) bool { 32 | var termios syscall.Termios 33 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) 34 | return err == 0 35 | } 36 | 37 | // MakeRaw put the terminal connected to the given file descriptor into raw 38 | // mode and returns the previous state of the terminal so that it can be 39 | // restored. 40 | func MakeRaw(fd int) (*State, error) { 41 | var oldState State 42 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 43 | return nil, err 44 | } 45 | 46 | newState := oldState.termios 47 | newState.Iflag &^= syscall.ISTRIP | syscall.INLCR | syscall.ICRNL | syscall.IGNCR | syscall.IXON | syscall.IXOFF 48 | newState.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.ISIG 49 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 50 | return nil, err 51 | } 52 | 53 | return &oldState, nil 54 | } 55 | 56 | // GetState returns the current state of a terminal which may be useful to 57 | // restore the terminal after a signal. 58 | func GetState(fd int) (*State, error) { 59 | var oldState State 60 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState.termios)), 0, 0, 0); err != 0 { 61 | return nil, err 62 | } 63 | 64 | return &oldState, nil 65 | } 66 | 67 | // Restore restores the terminal connected to the given file descriptor to a 68 | // previous state. 69 | func Restore(fd int, state *State) error { 70 | _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&state.termios)), 0, 0, 0) 71 | return err 72 | } 73 | 74 | // GetSize returns the dimensions of the given terminal. 75 | func GetSize(fd int) (width, height int, err error) { 76 | var dimensions [4]uint16 77 | 78 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), uintptr(syscall.TIOCGWINSZ), uintptr(unsafe.Pointer(&dimensions)), 0, 0, 0); err != 0 { 79 | return -1, -1, err 80 | } 81 | return int(dimensions[1]), int(dimensions[0]), nil 82 | } 83 | 84 | // ReadPassword reads a line of input from a terminal without local echo. This 85 | // is commonly used for inputting passwords and other sensitive data. The slice 86 | // returned does not include the \n. 87 | func ReadPassword(fd int) ([]byte, error) { 88 | var oldState syscall.Termios 89 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); err != 0 { 90 | return nil, err 91 | } 92 | 93 | newState := oldState 94 | newState.Lflag &^= syscall.ECHO 95 | newState.Lflag |= syscall.ICANON | syscall.ISIG 96 | newState.Iflag |= syscall.ICRNL 97 | if _, _, err := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); err != 0 { 98 | return nil, err 99 | } 100 | 101 | defer func() { 102 | syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0) 103 | }() 104 | 105 | var buf [16]byte 106 | var ret []byte 107 | for { 108 | n, err := syscall.Read(fd, buf[:]) 109 | if err != nil { 110 | return nil, err 111 | } 112 | if n == 0 { 113 | if len(ret) == 0 { 114 | return nil, io.EOF 115 | } 116 | break 117 | } 118 | if buf[n-1] == '\n' { 119 | n-- 120 | } 121 | ret = append(ret, buf[:n]...) 122 | if n < len(buf) { 123 | break 124 | } 125 | } 126 | 127 | return ret, nil 128 | } 129 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/util_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin dragonfly freebsd netbsd openbsd 6 | 7 | package terminal 8 | 9 | import "syscall" 10 | 11 | const ioctlReadTermios = syscall.TIOCGETA 12 | const ioctlWriteTermios = syscall.TIOCSETA 13 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/util_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package terminal 6 | 7 | // These constants are declared here, rather than importing 8 | // them from the syscall package as some syscall packages, even 9 | // on linux, for example gccgo, do not declare them. 10 | const ioctlReadTermios = 0x5401 // syscall.TCGETS 11 | const ioctlWriteTermios = 0x5402 // syscall.TCSETS 12 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/util_plan9.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package terminal provides support functions for dealing with terminals, as 6 | // commonly found on UNIX systems. 7 | // 8 | // Putting a terminal into raw mode is the most common requirement: 9 | // 10 | // oldState, err := terminal.MakeRaw(0) 11 | // if err != nil { 12 | // panic(err) 13 | // } 14 | // defer terminal.Restore(0, oldState) 15 | package terminal 16 | 17 | import ( 18 | "fmt" 19 | "runtime" 20 | ) 21 | 22 | type State struct{} 23 | 24 | // IsTerminal returns true if the given file descriptor is a terminal. 25 | func IsTerminal(fd int) bool { 26 | return false 27 | } 28 | 29 | // MakeRaw put the terminal connected to the given file descriptor into raw 30 | // mode and returns the previous state of the terminal so that it can be 31 | // restored. 32 | func MakeRaw(fd int) (*State, error) { 33 | return nil, fmt.Errorf("terminal: MakeRaw not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 34 | } 35 | 36 | // GetState returns the current state of a terminal which may be useful to 37 | // restore the terminal after a signal. 38 | func GetState(fd int) (*State, error) { 39 | return nil, fmt.Errorf("terminal: GetState not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 40 | } 41 | 42 | // Restore restores the terminal connected to the given file descriptor to a 43 | // previous state. 44 | func Restore(fd int, state *State) error { 45 | return fmt.Errorf("terminal: Restore not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 46 | } 47 | 48 | // GetSize returns the dimensions of the given terminal. 49 | func GetSize(fd int) (width, height int, err error) { 50 | return 0, 0, fmt.Errorf("terminal: GetSize not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 51 | } 52 | 53 | // ReadPassword reads a line of input from a terminal without local echo. This 54 | // is commonly used for inputting passwords and other sensitive data. The slice 55 | // returned does not include the \n. 56 | func ReadPassword(fd int) ([]byte, error) { 57 | return nil, fmt.Errorf("terminal: ReadPassword not implemented on %s/%s", runtime.GOOS, runtime.GOARCH) 58 | } 59 | -------------------------------------------------------------------------------- /vendor/golang.org/x/crypto/ssh/terminal/util_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | // Package terminal provides support functions for dealing with terminals, as 8 | // commonly found on UNIX systems. 9 | // 10 | // Putting a terminal into raw mode is the most common requirement: 11 | // 12 | // oldState, err := terminal.MakeRaw(0) 13 | // if err != nil { 14 | // panic(err) 15 | // } 16 | // defer terminal.Restore(0, oldState) 17 | package terminal 18 | 19 | import ( 20 | "io" 21 | "syscall" 22 | "unsafe" 23 | ) 24 | 25 | const ( 26 | enableLineInput = 2 27 | enableEchoInput = 4 28 | enableProcessedInput = 1 29 | enableWindowInput = 8 30 | enableMouseInput = 16 31 | enableInsertMode = 32 32 | enableQuickEditMode = 64 33 | enableExtendedFlags = 128 34 | enableAutoPosition = 256 35 | enableProcessedOutput = 1 36 | enableWrapAtEolOutput = 2 37 | ) 38 | 39 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 40 | 41 | var ( 42 | procGetConsoleMode = kernel32.NewProc("GetConsoleMode") 43 | procSetConsoleMode = kernel32.NewProc("SetConsoleMode") 44 | procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") 45 | ) 46 | 47 | type ( 48 | short int16 49 | word uint16 50 | 51 | coord struct { 52 | x short 53 | y short 54 | } 55 | smallRect struct { 56 | left short 57 | top short 58 | right short 59 | bottom short 60 | } 61 | consoleScreenBufferInfo struct { 62 | size coord 63 | cursorPosition coord 64 | attributes word 65 | window smallRect 66 | maximumWindowSize coord 67 | } 68 | ) 69 | 70 | type State struct { 71 | mode uint32 72 | } 73 | 74 | // IsTerminal returns true if the given file descriptor is a terminal. 75 | func IsTerminal(fd int) bool { 76 | var st uint32 77 | r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 78 | return r != 0 && e == 0 79 | } 80 | 81 | // MakeRaw put the terminal connected to the given file descriptor into raw 82 | // mode and returns the previous state of the terminal so that it can be 83 | // restored. 84 | func MakeRaw(fd int) (*State, error) { 85 | var st uint32 86 | _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 87 | if e != 0 { 88 | return nil, error(e) 89 | } 90 | raw := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) 91 | _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(raw), 0) 92 | if e != 0 { 93 | return nil, error(e) 94 | } 95 | return &State{st}, nil 96 | } 97 | 98 | // GetState returns the current state of a terminal which may be useful to 99 | // restore the terminal after a signal. 100 | func GetState(fd int) (*State, error) { 101 | var st uint32 102 | _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 103 | if e != 0 { 104 | return nil, error(e) 105 | } 106 | return &State{st}, nil 107 | } 108 | 109 | // Restore restores the terminal connected to the given file descriptor to a 110 | // previous state. 111 | func Restore(fd int, state *State) error { 112 | _, _, err := syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(state.mode), 0) 113 | return err 114 | } 115 | 116 | // GetSize returns the dimensions of the given terminal. 117 | func GetSize(fd int) (width, height int, err error) { 118 | var info consoleScreenBufferInfo 119 | _, _, e := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&info)), 0) 120 | if e != 0 { 121 | return 0, 0, error(e) 122 | } 123 | return int(info.size.x), int(info.size.y), nil 124 | } 125 | 126 | // ReadPassword reads a line of input from a terminal without local echo. This 127 | // is commonly used for inputting passwords and other sensitive data. The slice 128 | // returned does not include the \n. 129 | func ReadPassword(fd int) ([]byte, error) { 130 | var st uint32 131 | _, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) 132 | if e != 0 { 133 | return nil, error(e) 134 | } 135 | old := st 136 | 137 | st &^= (enableEchoInput) 138 | st |= (enableProcessedInput | enableLineInput | enableProcessedOutput) 139 | _, _, e = syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(st), 0) 140 | if e != 0 { 141 | return nil, error(e) 142 | } 143 | 144 | defer func() { 145 | syscall.Syscall(procSetConsoleMode.Addr(), 2, uintptr(fd), uintptr(old), 0) 146 | }() 147 | 148 | var buf [16]byte 149 | var ret []byte 150 | for { 151 | n, err := syscall.Read(syscall.Handle(fd), buf[:]) 152 | if err != nil { 153 | return nil, err 154 | } 155 | if n == 0 { 156 | if len(ret) == 0 { 157 | return nil, io.EOF 158 | } 159 | break 160 | } 161 | if buf[n-1] == '\n' { 162 | n-- 163 | } 164 | if n > 0 && buf[n-1] == '\r' { 165 | n-- 166 | } 167 | ret = append(ret, buf[:n]...) 168 | if n < len(buf) { 169 | break 170 | } 171 | } 172 | 173 | return ret, nil 174 | } 175 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go 2 | Authors. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following disclaimer 12 | in the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Google Inc. nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/README: -------------------------------------------------------------------------------- 1 | Gcfg reads INI-style configuration files into Go structs; 2 | supports user-defined types and subsections. 3 | 4 | Package docs: https://godoc.org/gopkg.in/gcfg.v1 5 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/doc.go: -------------------------------------------------------------------------------- 1 | // Package gcfg reads "INI-style" text-based configuration files with 2 | // "name=value" pairs grouped into sections (gcfg files). 3 | // 4 | // This package is still a work in progress; see the sections below for planned 5 | // changes. 6 | // 7 | // Syntax 8 | // 9 | // The syntax is based on that used by git config: 10 | // http://git-scm.com/docs/git-config#_syntax . 11 | // There are some (planned) differences compared to the git config format: 12 | // - improve data portability: 13 | // - must be encoded in UTF-8 (for now) and must not contain the 0 byte 14 | // - include and "path" type is not supported 15 | // (path type may be implementable as a user-defined type) 16 | // - internationalization 17 | // - section and variable names can contain unicode letters, unicode digits 18 | // (as defined in http://golang.org/ref/spec#Characters ) and hyphens 19 | // (U+002D), starting with a unicode letter 20 | // - disallow potentially ambiguous or misleading definitions: 21 | // - `[sec.sub]` format is not allowed (deprecated in gitconfig) 22 | // - `[sec ""]` is not allowed 23 | // - use `[sec]` for section name "sec" and empty subsection name 24 | // - (planned) within a single file, definitions must be contiguous for each: 25 | // - section: '[secA]' -> '[secB]' -> '[secA]' is an error 26 | // - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error 27 | // - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error 28 | // 29 | // Data structure 30 | // 31 | // The functions in this package read values into a user-defined struct. 32 | // Each section corresponds to a struct field in the config struct, and each 33 | // variable in a section corresponds to a data field in the section struct. 34 | // The mapping of each section or variable name to fields is done either based 35 | // on the "gcfg" struct tag or by matching the name of the section or variable, 36 | // ignoring case. In the latter case, hyphens '-' in section and variable names 37 | // correspond to underscores '_' in field names. 38 | // Fields must be exported; to use a section or variable name starting with a 39 | // letter that is neither upper- or lower-case, prefix the field name with 'X'. 40 | // (See https://code.google.com/p/go/issues/detail?id=5763#c4 .) 41 | // 42 | // For sections with subsections, the corresponding field in config must be a 43 | // map, rather than a struct, with string keys and pointer-to-struct values. 44 | // Values for subsection variables are stored in the map with the subsection 45 | // name used as the map key. 46 | // (Note that unlike section and variable names, subsection names are case 47 | // sensitive.) 48 | // When using a map, and there is a section with the same section name but 49 | // without a subsection name, its values are stored with the empty string used 50 | // as the key. 51 | // 52 | // The functions in this package panic if config is not a pointer to a struct, 53 | // or when a field is not of a suitable type (either a struct or a map with 54 | // string keys and pointer-to-struct values). 55 | // 56 | // Parsing of values 57 | // 58 | // The section structs in the config struct may contain single-valued or 59 | // multi-valued variables. Variables of unnamed slice type (that is, a type 60 | // starting with `[]`) are treated as multi-value; all others (including named 61 | // slice types) are treated as single-valued variables. 62 | // 63 | // Single-valued variables are handled based on the type as follows. 64 | // Unnamed pointer types (that is, types starting with `*`) are dereferenced, 65 | // and if necessary, a new instance is allocated. 66 | // 67 | // For types implementing the encoding.TextUnmarshaler interface, the 68 | // UnmarshalText method is used to set the value. Implementing this method is 69 | // the recommended way for parsing user-defined types. 70 | // 71 | // For fields of string kind, the value string is assigned to the field, after 72 | // unquoting and unescaping as needed. 73 | // For fields of bool kind, the field is set to true if the value is "true", 74 | // "yes", "on" or "1", and set to false if the value is "false", "no", "off" or 75 | // "0", ignoring case. In addition, single-valued bool fields can be specified 76 | // with a "blank" value (variable name without equals sign and value); in such 77 | // case the value is set to true. 78 | // 79 | // Predefined integer types [u]int(|8|16|32|64) and big.Int are parsed as 80 | // decimal or hexadecimal (if having '0x' prefix). (This is to prevent 81 | // unintuitively handling zero-padded numbers as octal.) Other types having 82 | // [u]int* as the underlying type, such as os.FileMode and uintptr allow 83 | // decimal, hexadecimal, or octal values. 84 | // Parsing mode for integer types can be overridden using the struct tag option 85 | // ",int=mode" where mode is a combination of the 'd', 'h', and 'o' characters 86 | // (each standing for decimal, hexadecimal, and octal, respectively.) 87 | // 88 | // All other types are parsed using fmt.Sscanf with the "%v" verb. 89 | // 90 | // For multi-valued variables, each individual value is parsed as above and 91 | // appended to the slice. If the first value is specified as a "blank" value 92 | // (variable name without equals sign and value), a new slice is allocated; 93 | // that is any values previously set in the slice will be ignored. 94 | // 95 | // The types subpackage for provides helpers for parsing "enum-like" and integer 96 | // types. 97 | // 98 | // TODO 99 | // 100 | // The following is a list of changes under consideration: 101 | // - documentation 102 | // - self-contained syntax documentation 103 | // - more practical examples 104 | // - move TODOs to issue tracker (eventually) 105 | // - syntax 106 | // - reconsider valid escape sequences 107 | // (gitconfig doesn't support \r in value, \t in subsection name, etc.) 108 | // - reading / parsing gcfg files 109 | // - define internal representation structure 110 | // - support multiple inputs (readers, strings, files) 111 | // - support declaring encoding (?) 112 | // - support varying fields sets for subsections (?) 113 | // - writing gcfg files 114 | // - error handling 115 | // - make error context accessible programmatically? 116 | // - limit input size? 117 | // 118 | package gcfg // import "gopkg.in/gcfg.v1" 119 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/example_test.go: -------------------------------------------------------------------------------- 1 | package gcfg_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | import "gopkg.in/gcfg.v1" 9 | 10 | func ExampleReadStringInto() { 11 | cfgStr := `; Comment line 12 | [section] 13 | name=value # comment` 14 | cfg := struct { 15 | Section struct { 16 | Name string 17 | } 18 | }{} 19 | err := gcfg.ReadStringInto(&cfg, cfgStr) 20 | if err != nil { 21 | log.Fatalf("Failed to parse gcfg data: %s", err) 22 | } 23 | fmt.Println(cfg.Section.Name) 24 | // Output: value 25 | } 26 | 27 | func ExampleReadStringInto_bool() { 28 | cfgStr := `; Comment line 29 | [section] 30 | switch=on` 31 | cfg := struct { 32 | Section struct { 33 | Switch bool 34 | } 35 | }{} 36 | err := gcfg.ReadStringInto(&cfg, cfgStr) 37 | if err != nil { 38 | log.Fatalf("Failed to parse gcfg data: %s", err) 39 | } 40 | fmt.Println(cfg.Section.Switch) 41 | // Output: true 42 | } 43 | 44 | func ExampleReadStringInto_hyphens() { 45 | cfgStr := `; Comment line 46 | [section-name] 47 | variable-name=value # comment` 48 | cfg := struct { 49 | Section_Name struct { 50 | Variable_Name string 51 | } 52 | }{} 53 | err := gcfg.ReadStringInto(&cfg, cfgStr) 54 | if err != nil { 55 | log.Fatalf("Failed to parse gcfg data: %s", err) 56 | } 57 | fmt.Println(cfg.Section_Name.Variable_Name) 58 | // Output: value 59 | } 60 | 61 | func ExampleReadStringInto_tags() { 62 | cfgStr := `; Comment line 63 | [section] 64 | var-name=value # comment` 65 | cfg := struct { 66 | Section struct { 67 | FieldName string `gcfg:"var-name"` 68 | } 69 | }{} 70 | err := gcfg.ReadStringInto(&cfg, cfgStr) 71 | if err != nil { 72 | log.Fatalf("Failed to parse gcfg data: %s", err) 73 | } 74 | fmt.Println(cfg.Section.FieldName) 75 | // Output: value 76 | } 77 | 78 | func ExampleReadStringInto_subsections() { 79 | cfgStr := `; Comment line 80 | [profile "A"] 81 | color = white 82 | 83 | [profile "B"] 84 | color = black 85 | ` 86 | cfg := struct { 87 | Profile map[string]*struct { 88 | Color string 89 | } 90 | }{} 91 | err := gcfg.ReadStringInto(&cfg, cfgStr) 92 | if err != nil { 93 | log.Fatalf("Failed to parse gcfg data: %s", err) 94 | } 95 | fmt.Printf("%s %s\n", cfg.Profile["A"].Color, cfg.Profile["B"].Color) 96 | // Output: white black 97 | } 98 | 99 | func ExampleReadStringInto_multivalue() { 100 | cfgStr := `; Comment line 101 | [section] 102 | multi=value1 103 | multi=value2` 104 | cfg := struct { 105 | Section struct { 106 | Multi []string 107 | } 108 | }{} 109 | err := gcfg.ReadStringInto(&cfg, cfgStr) 110 | if err != nil { 111 | log.Fatalf("Failed to parse gcfg data: %s", err) 112 | } 113 | fmt.Println(cfg.Section.Multi) 114 | // Output: [value1 value2] 115 | } 116 | 117 | func ExampleReadStringInto_unicode() { 118 | cfgStr := `; Comment line 119 | [甲] 120 | 乙=丙 # comment` 121 | cfg := struct { 122 | X甲 struct { 123 | X乙 string 124 | } 125 | }{} 126 | err := gcfg.ReadStringInto(&cfg, cfgStr) 127 | if err != nil { 128 | log.Fatalf("Failed to parse gcfg data: %s", err) 129 | } 130 | fmt.Println(cfg.X甲.X乙) 131 | // Output: 丙 132 | } 133 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/go1_0.go: -------------------------------------------------------------------------------- 1 | // +build !go1.2 2 | 3 | package gcfg 4 | 5 | type textUnmarshaler interface { 6 | UnmarshalText(text []byte) error 7 | } 8 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/go1_2.go: -------------------------------------------------------------------------------- 1 | // +build go1.2 2 | 3 | package gcfg 4 | 5 | import ( 6 | "encoding" 7 | ) 8 | 9 | type textUnmarshaler encoding.TextUnmarshaler 10 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/issues_test.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | type Config1 struct { 11 | Section struct { 12 | Int int 13 | BigInt big.Int 14 | } 15 | } 16 | 17 | var testsIssue1 = []struct { 18 | cfg string 19 | typename string 20 | }{ 21 | {"[section]\nint=X", "int"}, 22 | {"[section]\nint=", "int"}, 23 | {"[section]\nint=1A", "int"}, 24 | {"[section]\nbigint=X", "big.Int"}, 25 | {"[section]\nbigint=", "big.Int"}, 26 | {"[section]\nbigint=1A", "big.Int"}, 27 | } 28 | 29 | // Value parse error should: 30 | // - include plain type name 31 | // - not include reflect internals 32 | func TestIssue1(t *testing.T) { 33 | for i, tt := range testsIssue1 { 34 | var c Config1 35 | err := ReadStringInto(&c, tt.cfg) 36 | switch { 37 | case err == nil: 38 | t.Errorf("%d fail: got ok; wanted error", i) 39 | case !strings.Contains(err.Error(), tt.typename): 40 | t.Errorf("%d fail: error message doesn't contain type name %q: %v", 41 | i, tt.typename, err) 42 | case strings.Contains(err.Error(), "reflect"): 43 | t.Errorf("%d fail: error message includes reflect internals: %v", 44 | i, err) 45 | default: 46 | t.Logf("%d pass: %v", i, err) 47 | } 48 | } 49 | } 50 | 51 | type confIssue2 struct{ Main struct{ Foo string } } 52 | 53 | var testsIssue2 = []readtest{ 54 | {"[main]\n;\nfoo = bar\n", &confIssue2{struct{ Foo string }{"bar"}}, true}, 55 | {"[main]\r\n;\r\nfoo = bar\r\n", &confIssue2{struct{ Foo string }{"bar"}}, true}, 56 | } 57 | 58 | func TestIssue2(t *testing.T) { 59 | for i, tt := range testsIssue2 { 60 | id := fmt.Sprintf("issue2:%d", i) 61 | testRead(t, id, tt) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/read.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "io/ioutil" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | import ( 12 | "gopkg.in/gcfg.v1/scanner" 13 | "gopkg.in/gcfg.v1/token" 14 | ) 15 | 16 | var unescape = map[rune]rune{'\\': '\\', '"': '"', 'n': '\n', 't': '\t'} 17 | 18 | // no error: invalid literals should be caught by scanner 19 | func unquote(s string) string { 20 | u, q, esc := make([]rune, 0, len(s)), false, false 21 | for _, c := range s { 22 | if esc { 23 | uc, ok := unescape[c] 24 | switch { 25 | case ok: 26 | u = append(u, uc) 27 | fallthrough 28 | case !q && c == '\n': 29 | esc = false 30 | continue 31 | } 32 | panic("invalid escape sequence") 33 | } 34 | switch c { 35 | case '"': 36 | q = !q 37 | case '\\': 38 | esc = true 39 | default: 40 | u = append(u, c) 41 | } 42 | } 43 | if q { 44 | panic("missing end quote") 45 | } 46 | if esc { 47 | panic("invalid escape sequence") 48 | } 49 | return string(u) 50 | } 51 | 52 | func readInto(config interface{}, fset *token.FileSet, file *token.File, src []byte) error { 53 | var s scanner.Scanner 54 | var errs scanner.ErrorList 55 | s.Init(file, src, func(p token.Position, m string) { errs.Add(p, m) }, 0) 56 | sect, sectsub := "", "" 57 | pos, tok, lit := s.Scan() 58 | errfn := func(msg string) error { 59 | return fmt.Errorf("%s: %s", fset.Position(pos), msg) 60 | } 61 | for { 62 | if errs.Len() > 0 { 63 | return errs.Err() 64 | } 65 | switch tok { 66 | case token.EOF: 67 | return nil 68 | case token.EOL, token.COMMENT: 69 | pos, tok, lit = s.Scan() 70 | case token.LBRACK: 71 | pos, tok, lit = s.Scan() 72 | if errs.Len() > 0 { 73 | return errs.Err() 74 | } 75 | if tok != token.IDENT { 76 | return errfn("expected section name") 77 | } 78 | sect, sectsub = lit, "" 79 | pos, tok, lit = s.Scan() 80 | if errs.Len() > 0 { 81 | return errs.Err() 82 | } 83 | if tok == token.STRING { 84 | sectsub = unquote(lit) 85 | if sectsub == "" { 86 | return errfn("empty subsection name") 87 | } 88 | pos, tok, lit = s.Scan() 89 | if errs.Len() > 0 { 90 | return errs.Err() 91 | } 92 | } 93 | if tok != token.RBRACK { 94 | if sectsub == "" { 95 | return errfn("expected subsection name or right bracket") 96 | } 97 | return errfn("expected right bracket") 98 | } 99 | pos, tok, lit = s.Scan() 100 | if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { 101 | return errfn("expected EOL, EOF, or comment") 102 | } 103 | // If a section/subsection header was found, ensure a 104 | // container object is created, even if there are no 105 | // variables further down. 106 | err := set(config, sect, sectsub, "", true, "") 107 | if err != nil { 108 | return err 109 | } 110 | case token.IDENT: 111 | if sect == "" { 112 | return errfn("expected section header") 113 | } 114 | n := lit 115 | pos, tok, lit = s.Scan() 116 | if errs.Len() > 0 { 117 | return errs.Err() 118 | } 119 | blank, v := tok == token.EOF || tok == token.EOL || tok == token.COMMENT, "" 120 | if !blank { 121 | if tok != token.ASSIGN { 122 | return errfn("expected '='") 123 | } 124 | pos, tok, lit = s.Scan() 125 | if errs.Len() > 0 { 126 | return errs.Err() 127 | } 128 | if tok != token.STRING { 129 | return errfn("expected value") 130 | } 131 | v = unquote(lit) 132 | pos, tok, lit = s.Scan() 133 | if errs.Len() > 0 { 134 | return errs.Err() 135 | } 136 | if tok != token.EOL && tok != token.EOF && tok != token.COMMENT { 137 | return errfn("expected EOL, EOF, or comment") 138 | } 139 | } 140 | err := set(config, sect, sectsub, n, blank, v) 141 | if err != nil { 142 | return err 143 | } 144 | default: 145 | if sect == "" { 146 | return errfn("expected section header") 147 | } 148 | return errfn("expected section header or variable declaration") 149 | } 150 | } 151 | panic("never reached") 152 | } 153 | 154 | // ReadInto reads gcfg formatted data from reader and sets the values into the 155 | // corresponding fields in config. 156 | func ReadInto(config interface{}, reader io.Reader) error { 157 | src, err := ioutil.ReadAll(reader) 158 | if err != nil { 159 | return err 160 | } 161 | fset := token.NewFileSet() 162 | file := fset.AddFile("", fset.Base(), len(src)) 163 | return readInto(config, fset, file, src) 164 | } 165 | 166 | // ReadStringInto reads gcfg formatted data from str and sets the values into 167 | // the corresponding fields in config. 168 | func ReadStringInto(config interface{}, str string) error { 169 | r := strings.NewReader(str) 170 | return ReadInto(config, r) 171 | } 172 | 173 | // ReadFileInto reads gcfg formatted data from the file filename and sets the 174 | // values into the corresponding fields in config. 175 | func ReadFileInto(config interface{}, filename string) error { 176 | f, err := os.Open(filename) 177 | if err != nil { 178 | return err 179 | } 180 | defer f.Close() 181 | src, err := ioutil.ReadAll(f) 182 | if err != nil { 183 | return err 184 | } 185 | fset := token.NewFileSet() 186 | file := fset.AddFile(filename, fset.Base(), len(src)) 187 | return readInto(config, fset, file, src) 188 | } 189 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/scanner/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package scanner 6 | 7 | import ( 8 | "fmt" 9 | "io" 10 | "sort" 11 | ) 12 | 13 | import ( 14 | "gopkg.in/gcfg.v1/token" 15 | ) 16 | 17 | // In an ErrorList, an error is represented by an *Error. 18 | // The position Pos, if valid, points to the beginning of 19 | // the offending token, and the error condition is described 20 | // by Msg. 21 | // 22 | type Error struct { 23 | Pos token.Position 24 | Msg string 25 | } 26 | 27 | // Error implements the error interface. 28 | func (e Error) Error() string { 29 | if e.Pos.Filename != "" || e.Pos.IsValid() { 30 | // don't print "" 31 | // TODO(gri) reconsider the semantics of Position.IsValid 32 | return e.Pos.String() + ": " + e.Msg 33 | } 34 | return e.Msg 35 | } 36 | 37 | // ErrorList is a list of *Errors. 38 | // The zero value for an ErrorList is an empty ErrorList ready to use. 39 | // 40 | type ErrorList []*Error 41 | 42 | // Add adds an Error with given position and error message to an ErrorList. 43 | func (p *ErrorList) Add(pos token.Position, msg string) { 44 | *p = append(*p, &Error{pos, msg}) 45 | } 46 | 47 | // Reset resets an ErrorList to no errors. 48 | func (p *ErrorList) Reset() { *p = (*p)[0:0] } 49 | 50 | // ErrorList implements the sort Interface. 51 | func (p ErrorList) Len() int { return len(p) } 52 | func (p ErrorList) Swap(i, j int) { p[i], p[j] = p[j], p[i] } 53 | 54 | func (p ErrorList) Less(i, j int) bool { 55 | e := &p[i].Pos 56 | f := &p[j].Pos 57 | if e.Filename < f.Filename { 58 | return true 59 | } 60 | if e.Filename == f.Filename { 61 | return e.Offset < f.Offset 62 | } 63 | return false 64 | } 65 | 66 | // Sort sorts an ErrorList. *Error entries are sorted by position, 67 | // other errors are sorted by error message, and before any *Error 68 | // entry. 69 | // 70 | func (p ErrorList) Sort() { 71 | sort.Sort(p) 72 | } 73 | 74 | // RemoveMultiples sorts an ErrorList and removes all but the first error per line. 75 | func (p *ErrorList) RemoveMultiples() { 76 | sort.Sort(p) 77 | var last token.Position // initial last.Line is != any legal error line 78 | i := 0 79 | for _, e := range *p { 80 | if e.Pos.Filename != last.Filename || e.Pos.Line != last.Line { 81 | last = e.Pos 82 | (*p)[i] = e 83 | i++ 84 | } 85 | } 86 | (*p) = (*p)[0:i] 87 | } 88 | 89 | // An ErrorList implements the error interface. 90 | func (p ErrorList) Error() string { 91 | switch len(p) { 92 | case 0: 93 | return "no errors" 94 | case 1: 95 | return p[0].Error() 96 | } 97 | return fmt.Sprintf("%s (and %d more errors)", p[0], len(p)-1) 98 | } 99 | 100 | // Err returns an error equivalent to this error list. 101 | // If the list is empty, Err returns nil. 102 | func (p ErrorList) Err() error { 103 | if len(p) == 0 { 104 | return nil 105 | } 106 | return p 107 | } 108 | 109 | // PrintError is a utility function that prints a list of errors to w, 110 | // one error per line, if the err parameter is an ErrorList. Otherwise 111 | // it prints the err string. 112 | // 113 | func PrintError(w io.Writer, err error) { 114 | if list, ok := err.(ErrorList); ok { 115 | for _, e := range list { 116 | fmt.Fprintf(w, "%s\n", e) 117 | } 118 | } else if err != nil { 119 | fmt.Fprintf(w, "%s\n", err) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/scanner/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package scanner_test 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | import ( 12 | "gopkg.in/gcfg.v1/scanner" 13 | "gopkg.in/gcfg.v1/token" 14 | ) 15 | 16 | func ExampleScanner_Scan() { 17 | // src is the input that we want to tokenize. 18 | src := []byte(`[profile "A"] 19 | color = blue ; Comment`) 20 | 21 | // Initialize the scanner. 22 | var s scanner.Scanner 23 | fset := token.NewFileSet() // positions are relative to fset 24 | file := fset.AddFile("", fset.Base(), len(src)) // register input "file" 25 | s.Init(file, src, nil /* no error handler */, scanner.ScanComments) 26 | 27 | // Repeated calls to Scan yield the token sequence found in the input. 28 | for { 29 | pos, tok, lit := s.Scan() 30 | if tok == token.EOF { 31 | break 32 | } 33 | fmt.Printf("%s\t%q\t%q\n", fset.Position(pos), tok, lit) 34 | } 35 | 36 | // output: 37 | // 1:1 "[" "" 38 | // 1:2 "IDENT" "profile" 39 | // 1:10 "STRING" "\"A\"" 40 | // 1:13 "]" "" 41 | // 1:14 "\n" "" 42 | // 2:1 "IDENT" "color" 43 | // 2:7 "=" "" 44 | // 2:9 "STRING" "blue" 45 | // 2:14 "COMMENT" "; Comment" 46 | } 47 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/scanner/scanner.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package scanner implements a scanner for gcfg configuration text. 6 | // It takes a []byte as source which can then be tokenized 7 | // through repeated calls to the Scan method. 8 | // 9 | // Note that the API for the scanner package may change to accommodate new 10 | // features or implementation changes in gcfg. 11 | // 12 | package scanner 13 | 14 | import ( 15 | "fmt" 16 | "path/filepath" 17 | "unicode" 18 | "unicode/utf8" 19 | ) 20 | 21 | import ( 22 | "gopkg.in/gcfg.v1/token" 23 | ) 24 | 25 | // An ErrorHandler may be provided to Scanner.Init. If a syntax error is 26 | // encountered and a handler was installed, the handler is called with a 27 | // position and an error message. The position points to the beginning of 28 | // the offending token. 29 | // 30 | type ErrorHandler func(pos token.Position, msg string) 31 | 32 | // A Scanner holds the scanner's internal state while processing 33 | // a given text. It can be allocated as part of another data 34 | // structure but must be initialized via Init before use. 35 | // 36 | type Scanner struct { 37 | // immutable state 38 | file *token.File // source file handle 39 | dir string // directory portion of file.Name() 40 | src []byte // source 41 | err ErrorHandler // error reporting; or nil 42 | mode Mode // scanning mode 43 | 44 | // scanning state 45 | ch rune // current character 46 | offset int // character offset 47 | rdOffset int // reading offset (position after current character) 48 | lineOffset int // current line offset 49 | nextVal bool // next token is expected to be a value 50 | 51 | // public state - ok to modify 52 | ErrorCount int // number of errors encountered 53 | } 54 | 55 | // Read the next Unicode char into s.ch. 56 | // s.ch < 0 means end-of-file. 57 | // 58 | func (s *Scanner) next() { 59 | if s.rdOffset < len(s.src) { 60 | s.offset = s.rdOffset 61 | if s.ch == '\n' { 62 | s.lineOffset = s.offset 63 | s.file.AddLine(s.offset) 64 | } 65 | r, w := rune(s.src[s.rdOffset]), 1 66 | switch { 67 | case r == 0: 68 | s.error(s.offset, "illegal character NUL") 69 | case r >= 0x80: 70 | // not ASCII 71 | r, w = utf8.DecodeRune(s.src[s.rdOffset:]) 72 | if r == utf8.RuneError && w == 1 { 73 | s.error(s.offset, "illegal UTF-8 encoding") 74 | } 75 | } 76 | s.rdOffset += w 77 | s.ch = r 78 | } else { 79 | s.offset = len(s.src) 80 | if s.ch == '\n' { 81 | s.lineOffset = s.offset 82 | s.file.AddLine(s.offset) 83 | } 84 | s.ch = -1 // eof 85 | } 86 | } 87 | 88 | // A mode value is a set of flags (or 0). 89 | // They control scanner behavior. 90 | // 91 | type Mode uint 92 | 93 | const ( 94 | ScanComments Mode = 1 << iota // return comments as COMMENT tokens 95 | ) 96 | 97 | // Init prepares the scanner s to tokenize the text src by setting the 98 | // scanner at the beginning of src. The scanner uses the file set file 99 | // for position information and it adds line information for each line. 100 | // It is ok to re-use the same file when re-scanning the same file as 101 | // line information which is already present is ignored. Init causes a 102 | // panic if the file size does not match the src size. 103 | // 104 | // Calls to Scan will invoke the error handler err if they encounter a 105 | // syntax error and err is not nil. Also, for each error encountered, 106 | // the Scanner field ErrorCount is incremented by one. The mode parameter 107 | // determines how comments are handled. 108 | // 109 | // Note that Init may call err if there is an error in the first character 110 | // of the file. 111 | // 112 | func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) { 113 | // Explicitly initialize all fields since a scanner may be reused. 114 | if file.Size() != len(src) { 115 | panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src))) 116 | } 117 | s.file = file 118 | s.dir, _ = filepath.Split(file.Name()) 119 | s.src = src 120 | s.err = err 121 | s.mode = mode 122 | 123 | s.ch = ' ' 124 | s.offset = 0 125 | s.rdOffset = 0 126 | s.lineOffset = 0 127 | s.ErrorCount = 0 128 | s.nextVal = false 129 | 130 | s.next() 131 | } 132 | 133 | func (s *Scanner) error(offs int, msg string) { 134 | if s.err != nil { 135 | s.err(s.file.Position(s.file.Pos(offs)), msg) 136 | } 137 | s.ErrorCount++ 138 | } 139 | 140 | func (s *Scanner) scanComment() string { 141 | // initial [;#] already consumed 142 | offs := s.offset - 1 // position of initial [;#] 143 | 144 | for s.ch != '\n' && s.ch >= 0 { 145 | s.next() 146 | } 147 | return string(s.src[offs:s.offset]) 148 | } 149 | 150 | func isLetter(ch rune) bool { 151 | return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch >= 0x80 && unicode.IsLetter(ch) 152 | } 153 | 154 | func isDigit(ch rune) bool { 155 | return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch) 156 | } 157 | 158 | func (s *Scanner) scanIdentifier() string { 159 | offs := s.offset 160 | for isLetter(s.ch) || isDigit(s.ch) || s.ch == '-' { 161 | s.next() 162 | } 163 | return string(s.src[offs:s.offset]) 164 | } 165 | 166 | func (s *Scanner) scanEscape(val bool) { 167 | offs := s.offset 168 | ch := s.ch 169 | s.next() // always make progress 170 | switch ch { 171 | case '\\', '"': 172 | // ok 173 | case 'n', 't': 174 | if val { 175 | break // ok 176 | } 177 | fallthrough 178 | default: 179 | s.error(offs, "unknown escape sequence") 180 | } 181 | } 182 | 183 | func (s *Scanner) scanString() string { 184 | // '"' opening already consumed 185 | offs := s.offset - 1 186 | 187 | for s.ch != '"' { 188 | ch := s.ch 189 | s.next() 190 | if ch == '\n' || ch < 0 { 191 | s.error(offs, "string not terminated") 192 | break 193 | } 194 | if ch == '\\' { 195 | s.scanEscape(false) 196 | } 197 | } 198 | 199 | s.next() 200 | 201 | return string(s.src[offs:s.offset]) 202 | } 203 | 204 | func stripCR(b []byte) []byte { 205 | c := make([]byte, len(b)) 206 | i := 0 207 | for _, ch := range b { 208 | if ch != '\r' { 209 | c[i] = ch 210 | i++ 211 | } 212 | } 213 | return c[:i] 214 | } 215 | 216 | func (s *Scanner) scanValString() string { 217 | offs := s.offset 218 | 219 | hasCR := false 220 | end := offs 221 | inQuote := false 222 | loop: 223 | for inQuote || s.ch >= 0 && s.ch != '\n' && s.ch != ';' && s.ch != '#' { 224 | ch := s.ch 225 | s.next() 226 | switch { 227 | case inQuote && ch == '\\': 228 | s.scanEscape(true) 229 | case !inQuote && ch == '\\': 230 | if s.ch == '\r' { 231 | hasCR = true 232 | s.next() 233 | } 234 | if s.ch != '\n' { 235 | s.error(offs, "unquoted '\\' must be followed by new line") 236 | break loop 237 | } 238 | s.next() 239 | case ch == '"': 240 | inQuote = !inQuote 241 | case ch == '\r': 242 | hasCR = true 243 | case ch < 0 || inQuote && ch == '\n': 244 | s.error(offs, "string not terminated") 245 | break loop 246 | } 247 | if inQuote || !isWhiteSpace(ch) { 248 | end = s.offset 249 | } 250 | } 251 | 252 | lit := s.src[offs:end] 253 | if hasCR { 254 | lit = stripCR(lit) 255 | } 256 | 257 | return string(lit) 258 | } 259 | 260 | func isWhiteSpace(ch rune) bool { 261 | return ch == ' ' || ch == '\t' || ch == '\r' 262 | } 263 | 264 | func (s *Scanner) skipWhitespace() { 265 | for isWhiteSpace(s.ch) { 266 | s.next() 267 | } 268 | } 269 | 270 | // Scan scans the next token and returns the token position, the token, 271 | // and its literal string if applicable. The source end is indicated by 272 | // token.EOF. 273 | // 274 | // If the returned token is a literal (token.IDENT, token.STRING) or 275 | // token.COMMENT, the literal string has the corresponding value. 276 | // 277 | // If the returned token is token.ILLEGAL, the literal string is the 278 | // offending character. 279 | // 280 | // In all other cases, Scan returns an empty literal string. 281 | // 282 | // For more tolerant parsing, Scan will return a valid token if 283 | // possible even if a syntax error was encountered. Thus, even 284 | // if the resulting token sequence contains no illegal tokens, 285 | // a client may not assume that no error occurred. Instead it 286 | // must check the scanner's ErrorCount or the number of calls 287 | // of the error handler, if there was one installed. 288 | // 289 | // Scan adds line information to the file added to the file 290 | // set with Init. Token positions are relative to that file 291 | // and thus relative to the file set. 292 | // 293 | func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) { 294 | scanAgain: 295 | s.skipWhitespace() 296 | 297 | // current token start 298 | pos = s.file.Pos(s.offset) 299 | 300 | // determine token value 301 | switch ch := s.ch; { 302 | case s.nextVal: 303 | lit = s.scanValString() 304 | tok = token.STRING 305 | s.nextVal = false 306 | case isLetter(ch): 307 | lit = s.scanIdentifier() 308 | tok = token.IDENT 309 | default: 310 | s.next() // always make progress 311 | switch ch { 312 | case -1: 313 | tok = token.EOF 314 | case '\n': 315 | tok = token.EOL 316 | case '"': 317 | tok = token.STRING 318 | lit = s.scanString() 319 | case '[': 320 | tok = token.LBRACK 321 | case ']': 322 | tok = token.RBRACK 323 | case ';', '#': 324 | // comment 325 | lit = s.scanComment() 326 | if s.mode&ScanComments == 0 { 327 | // skip comment 328 | goto scanAgain 329 | } 330 | tok = token.COMMENT 331 | case '=': 332 | tok = token.ASSIGN 333 | s.nextVal = true 334 | default: 335 | s.error(s.file.Offset(pos), fmt.Sprintf("illegal character %#U", ch)) 336 | tok = token.ILLEGAL 337 | lit = string(ch) 338 | } 339 | } 340 | 341 | return 342 | } 343 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/scanner/scanner_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package scanner 6 | 7 | import ( 8 | "os" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | import ( 14 | "gopkg.in/gcfg.v1/token" 15 | ) 16 | 17 | var fset = token.NewFileSet() 18 | 19 | const /* class */ ( 20 | special = iota 21 | literal 22 | operator 23 | ) 24 | 25 | func tokenclass(tok token.Token) int { 26 | switch { 27 | case tok.IsLiteral(): 28 | return literal 29 | case tok.IsOperator(): 30 | return operator 31 | } 32 | return special 33 | } 34 | 35 | type elt struct { 36 | tok token.Token 37 | lit string 38 | class int 39 | pre string 40 | suf string 41 | } 42 | 43 | var tokens = [...]elt{ 44 | // Special tokens 45 | {token.COMMENT, "; a comment", special, "", "\n"}, 46 | {token.COMMENT, "# a comment", special, "", "\n"}, 47 | 48 | // Operators and delimiters 49 | {token.ASSIGN, "=", operator, "", "value"}, 50 | {token.LBRACK, "[", operator, "", ""}, 51 | {token.RBRACK, "]", operator, "", ""}, 52 | {token.EOL, "\n", operator, "", ""}, 53 | 54 | // Identifiers 55 | {token.IDENT, "foobar", literal, "", ""}, 56 | {token.IDENT, "a۰۱۸", literal, "", ""}, 57 | {token.IDENT, "foo६४", literal, "", ""}, 58 | {token.IDENT, "bar9876", literal, "", ""}, 59 | {token.IDENT, "foo-bar", literal, "", ""}, 60 | {token.IDENT, "foo", literal, ";\n", ""}, 61 | // String literals (subsection names) 62 | {token.STRING, `"foobar"`, literal, "", ""}, 63 | {token.STRING, `"\""`, literal, "", ""}, 64 | // String literals (values) 65 | {token.STRING, `"\n"`, literal, "=", ""}, 66 | {token.STRING, `"foobar"`, literal, "=", ""}, 67 | {token.STRING, `"foo\nbar"`, literal, "=", ""}, 68 | {token.STRING, `"foo\"bar"`, literal, "=", ""}, 69 | {token.STRING, `"foo\\bar"`, literal, "=", ""}, 70 | {token.STRING, `"foobar"`, literal, "=", ""}, 71 | {token.STRING, `"foobar"`, literal, "= ", ""}, 72 | {token.STRING, `"foobar"`, literal, "=", "\n"}, 73 | {token.STRING, `"foobar"`, literal, "=", ";"}, 74 | {token.STRING, `"foobar"`, literal, "=", " ;"}, 75 | {token.STRING, `"foobar"`, literal, "=", "#"}, 76 | {token.STRING, `"foobar"`, literal, "=", " #"}, 77 | {token.STRING, "foobar", literal, "=", ""}, 78 | {token.STRING, "foobar", literal, "= ", ""}, 79 | {token.STRING, "foobar", literal, "=", " "}, 80 | {token.STRING, `"foo" "bar"`, literal, "=", " "}, 81 | {token.STRING, "foo\\\nbar", literal, "=", ""}, 82 | {token.STRING, "foo\\\r\nbar", literal, "=", ""}, 83 | } 84 | 85 | const whitespace = " \t \n\n\n" // to separate tokens 86 | 87 | var source = func() []byte { 88 | var src []byte 89 | for _, t := range tokens { 90 | src = append(src, t.pre...) 91 | src = append(src, t.lit...) 92 | src = append(src, t.suf...) 93 | src = append(src, whitespace...) 94 | } 95 | return src 96 | }() 97 | 98 | func newlineCount(s string) int { 99 | n := 0 100 | for i := 0; i < len(s); i++ { 101 | if s[i] == '\n' { 102 | n++ 103 | } 104 | } 105 | return n 106 | } 107 | 108 | func checkPos(t *testing.T, lit string, p token.Pos, expected token.Position) { 109 | pos := fset.Position(p) 110 | if pos.Filename != expected.Filename { 111 | t.Errorf("bad filename for %q: got %s, expected %s", lit, pos.Filename, expected.Filename) 112 | } 113 | if pos.Offset != expected.Offset { 114 | t.Errorf("bad position for %q: got %d, expected %d", lit, pos.Offset, expected.Offset) 115 | } 116 | if pos.Line != expected.Line { 117 | t.Errorf("bad line for %q: got %d, expected %d", lit, pos.Line, expected.Line) 118 | } 119 | if pos.Column != expected.Column { 120 | t.Errorf("bad column for %q: got %d, expected %d", lit, pos.Column, expected.Column) 121 | } 122 | } 123 | 124 | // Verify that calling Scan() provides the correct results. 125 | func TestScan(t *testing.T) { 126 | // make source 127 | src_linecount := newlineCount(string(source)) 128 | whitespace_linecount := newlineCount(whitespace) 129 | 130 | index := 0 131 | 132 | // error handler 133 | eh := func(_ token.Position, msg string) { 134 | t.Errorf("%d: error handler called (msg = %s)", index, msg) 135 | } 136 | 137 | // verify scan 138 | var s Scanner 139 | s.Init(fset.AddFile("", fset.Base(), len(source)), source, eh, ScanComments) 140 | // epos is the expected position 141 | epos := token.Position{ 142 | Filename: "", 143 | Offset: 0, 144 | Line: 1, 145 | Column: 1, 146 | } 147 | for { 148 | pos, tok, lit := s.Scan() 149 | if lit == "" { 150 | // no literal value for non-literal tokens 151 | lit = tok.String() 152 | } 153 | e := elt{token.EOF, "", special, "", ""} 154 | if index < len(tokens) { 155 | e = tokens[index] 156 | } 157 | if tok == token.EOF { 158 | lit = "" 159 | epos.Line = src_linecount 160 | epos.Column = 2 161 | } 162 | if e.pre != "" && strings.ContainsRune("=;#", rune(e.pre[0])) { 163 | epos.Column = 1 164 | checkPos(t, lit, pos, epos) 165 | var etok token.Token 166 | if e.pre[0] == '=' { 167 | etok = token.ASSIGN 168 | } else { 169 | etok = token.COMMENT 170 | } 171 | if tok != etok { 172 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, etok) 173 | } 174 | pos, tok, lit = s.Scan() 175 | } 176 | epos.Offset += len(e.pre) 177 | if tok != token.EOF { 178 | epos.Column = 1 + len(e.pre) 179 | } 180 | if e.pre != "" && e.pre[len(e.pre)-1] == '\n' { 181 | epos.Offset-- 182 | epos.Column-- 183 | checkPos(t, lit, pos, epos) 184 | if tok != token.EOL { 185 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL) 186 | } 187 | epos.Line++ 188 | epos.Offset++ 189 | epos.Column = 1 190 | pos, tok, lit = s.Scan() 191 | } 192 | checkPos(t, lit, pos, epos) 193 | if tok != e.tok { 194 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, e.tok) 195 | } 196 | if e.tok.IsLiteral() { 197 | // no CRs in value string literals 198 | elit := e.lit 199 | if strings.ContainsRune(e.pre, '=') { 200 | elit = string(stripCR([]byte(elit))) 201 | epos.Offset += len(e.lit) - len(lit) // correct position 202 | } 203 | if lit != elit { 204 | t.Errorf("bad literal for %q: got %q, expected %q", lit, lit, elit) 205 | } 206 | } 207 | if tokenclass(tok) != e.class { 208 | t.Errorf("bad class for %q: got %d, expected %d", lit, tokenclass(tok), e.class) 209 | } 210 | epos.Offset += len(lit) + len(e.suf) + len(whitespace) 211 | epos.Line += newlineCount(lit) + newlineCount(e.suf) + whitespace_linecount 212 | index++ 213 | if tok == token.EOF { 214 | break 215 | } 216 | if e.suf == "value" { 217 | pos, tok, lit = s.Scan() 218 | if tok != token.STRING { 219 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.STRING) 220 | } 221 | } else if strings.ContainsRune(e.suf, ';') || strings.ContainsRune(e.suf, '#') { 222 | pos, tok, lit = s.Scan() 223 | if tok != token.COMMENT { 224 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.COMMENT) 225 | } 226 | } 227 | // skip EOLs 228 | for i := 0; i < whitespace_linecount+newlineCount(e.suf); i++ { 229 | pos, tok, lit = s.Scan() 230 | if tok != token.EOL { 231 | t.Errorf("bad token for %q: got %q, expected %q", lit, tok, token.EOL) 232 | } 233 | } 234 | } 235 | if s.ErrorCount != 0 { 236 | t.Errorf("found %d errors", s.ErrorCount) 237 | } 238 | } 239 | 240 | func TestScanValStringEOF(t *testing.T) { 241 | var s Scanner 242 | src := "= value" 243 | f := fset.AddFile("src", fset.Base(), len(src)) 244 | s.Init(f, []byte(src), nil, 0) 245 | s.Scan() // = 246 | s.Scan() // value 247 | _, tok, _ := s.Scan() // EOF 248 | if tok != token.EOF { 249 | t.Errorf("bad token: got %s, expected %s", tok, token.EOF) 250 | } 251 | if s.ErrorCount > 0 { 252 | t.Error("scanning error") 253 | } 254 | } 255 | 256 | // Verify that initializing the same scanner more then once works correctly. 257 | func TestInit(t *testing.T) { 258 | var s Scanner 259 | 260 | // 1st init 261 | src1 := "\nname = value" 262 | f1 := fset.AddFile("src1", fset.Base(), len(src1)) 263 | s.Init(f1, []byte(src1), nil, 0) 264 | if f1.Size() != len(src1) { 265 | t.Errorf("bad file size: got %d, expected %d", f1.Size(), len(src1)) 266 | } 267 | s.Scan() // \n 268 | s.Scan() // name 269 | _, tok, _ := s.Scan() // = 270 | if tok != token.ASSIGN { 271 | t.Errorf("bad token: got %s, expected %s", tok, token.ASSIGN) 272 | } 273 | 274 | // 2nd init 275 | src2 := "[section]" 276 | f2 := fset.AddFile("src2", fset.Base(), len(src2)) 277 | s.Init(f2, []byte(src2), nil, 0) 278 | if f2.Size() != len(src2) { 279 | t.Errorf("bad file size: got %d, expected %d", f2.Size(), len(src2)) 280 | } 281 | _, tok, _ = s.Scan() // [ 282 | if tok != token.LBRACK { 283 | t.Errorf("bad token: got %s, expected %s", tok, token.LBRACK) 284 | } 285 | 286 | if s.ErrorCount != 0 { 287 | t.Errorf("found %d errors", s.ErrorCount) 288 | } 289 | } 290 | 291 | func TestStdErrorHandler(t *testing.T) { 292 | const src = "@\n" + // illegal character, cause an error 293 | "@ @\n" // two errors on the same line 294 | 295 | var list ErrorList 296 | eh := func(pos token.Position, msg string) { list.Add(pos, msg) } 297 | 298 | var s Scanner 299 | s.Init(fset.AddFile("File1", fset.Base(), len(src)), []byte(src), eh, 0) 300 | for { 301 | if _, tok, _ := s.Scan(); tok == token.EOF { 302 | break 303 | } 304 | } 305 | 306 | if len(list) != s.ErrorCount { 307 | t.Errorf("found %d errors, expected %d", len(list), s.ErrorCount) 308 | } 309 | 310 | if len(list) != 3 { 311 | t.Errorf("found %d raw errors, expected 3", len(list)) 312 | PrintError(os.Stderr, list) 313 | } 314 | 315 | list.Sort() 316 | if len(list) != 3 { 317 | t.Errorf("found %d sorted errors, expected 3", len(list)) 318 | PrintError(os.Stderr, list) 319 | } 320 | 321 | list.RemoveMultiples() 322 | if len(list) != 2 { 323 | t.Errorf("found %d one-per-line errors, expected 2", len(list)) 324 | PrintError(os.Stderr, list) 325 | } 326 | } 327 | 328 | type errorCollector struct { 329 | cnt int // number of errors encountered 330 | msg string // last error message encountered 331 | pos token.Position // last error position encountered 332 | } 333 | 334 | func checkError(t *testing.T, src string, tok token.Token, pos int, err string) { 335 | var s Scanner 336 | var h errorCollector 337 | eh := func(pos token.Position, msg string) { 338 | h.cnt++ 339 | h.msg = msg 340 | h.pos = pos 341 | } 342 | s.Init(fset.AddFile("", fset.Base(), len(src)), []byte(src), eh, ScanComments) 343 | if src[0] == '=' { 344 | _, _, _ = s.Scan() 345 | } 346 | _, tok0, _ := s.Scan() 347 | _, tok1, _ := s.Scan() 348 | if tok0 != tok { 349 | t.Errorf("%q: got %s, expected %s", src, tok0, tok) 350 | } 351 | if tok1 != token.EOF { 352 | t.Errorf("%q: got %s, expected EOF", src, tok1) 353 | } 354 | cnt := 0 355 | if err != "" { 356 | cnt = 1 357 | } 358 | if h.cnt != cnt { 359 | t.Errorf("%q: got cnt %d, expected %d", src, h.cnt, cnt) 360 | } 361 | if h.msg != err { 362 | t.Errorf("%q: got msg %q, expected %q", src, h.msg, err) 363 | } 364 | if h.pos.Offset != pos { 365 | t.Errorf("%q: got offset %d, expected %d", src, h.pos.Offset, pos) 366 | } 367 | } 368 | 369 | var errors = []struct { 370 | src string 371 | tok token.Token 372 | pos int 373 | err string 374 | }{ 375 | {"\a", token.ILLEGAL, 0, "illegal character U+0007"}, 376 | {"/", token.ILLEGAL, 0, "illegal character U+002F '/'"}, 377 | {"_", token.ILLEGAL, 0, "illegal character U+005F '_'"}, 378 | {`…`, token.ILLEGAL, 0, "illegal character U+2026 '…'"}, 379 | {`""`, token.STRING, 0, ""}, 380 | {`"`, token.STRING, 0, "string not terminated"}, 381 | {"\"\n", token.STRING, 0, "string not terminated"}, 382 | {`="`, token.STRING, 1, "string not terminated"}, 383 | {"=\"\n", token.STRING, 1, "string not terminated"}, 384 | {"=\\", token.STRING, 1, "unquoted '\\' must be followed by new line"}, 385 | {"=\\\r", token.STRING, 1, "unquoted '\\' must be followed by new line"}, 386 | {`"\z"`, token.STRING, 2, "unknown escape sequence"}, 387 | {`"\a"`, token.STRING, 2, "unknown escape sequence"}, 388 | {`"\b"`, token.STRING, 2, "unknown escape sequence"}, 389 | {`"\f"`, token.STRING, 2, "unknown escape sequence"}, 390 | {`"\r"`, token.STRING, 2, "unknown escape sequence"}, 391 | {`"\t"`, token.STRING, 2, "unknown escape sequence"}, 392 | {`"\v"`, token.STRING, 2, "unknown escape sequence"}, 393 | {`"\0"`, token.STRING, 2, "unknown escape sequence"}, 394 | } 395 | 396 | func TestScanErrors(t *testing.T) { 397 | for _, e := range errors { 398 | checkError(t, e.src, e.tok, e.pos, e.err) 399 | } 400 | } 401 | 402 | func BenchmarkScan(b *testing.B) { 403 | b.StopTimer() 404 | fset := token.NewFileSet() 405 | file := fset.AddFile("", fset.Base(), len(source)) 406 | var s Scanner 407 | b.StartTimer() 408 | for i := b.N - 1; i >= 0; i-- { 409 | s.Init(file, source, nil, ScanComments) 410 | for { 411 | _, tok, _ := s.Scan() 412 | if tok == token.EOF { 413 | break 414 | } 415 | } 416 | } 417 | } 418 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/set.go: -------------------------------------------------------------------------------- 1 | package gcfg 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "reflect" 7 | "strings" 8 | "unicode" 9 | "unicode/utf8" 10 | 11 | "gopkg.in/gcfg.v1/types" 12 | ) 13 | 14 | type tag struct { 15 | ident string 16 | intMode string 17 | } 18 | 19 | var RelaxedParserMode = false 20 | 21 | func newTag(ts string) tag { 22 | t := tag{} 23 | s := strings.Split(ts, ",") 24 | t.ident = s[0] 25 | for _, tse := range s[1:] { 26 | if strings.HasPrefix(tse, "int=") { 27 | t.intMode = tse[len("int="):] 28 | } 29 | } 30 | return t 31 | } 32 | 33 | func fieldFold(v reflect.Value, name string) (reflect.Value, tag) { 34 | var n string 35 | r0, _ := utf8.DecodeRuneInString(name) 36 | if unicode.IsLetter(r0) && !unicode.IsLower(r0) && !unicode.IsUpper(r0) { 37 | n = "X" 38 | } 39 | n += strings.Replace(name, "-", "_", -1) 40 | f, ok := v.Type().FieldByNameFunc(func(fieldName string) bool { 41 | if !v.FieldByName(fieldName).CanSet() { 42 | return false 43 | } 44 | f, _ := v.Type().FieldByName(fieldName) 45 | t := newTag(f.Tag.Get("gcfg")) 46 | if t.ident != "" { 47 | return strings.EqualFold(t.ident, name) 48 | } 49 | return strings.EqualFold(n, fieldName) 50 | }) 51 | if !ok { 52 | return reflect.Value{}, tag{} 53 | } 54 | return v.FieldByName(f.Name), newTag(f.Tag.Get("gcfg")) 55 | } 56 | 57 | type setter func(destp interface{}, blank bool, val string, t tag) error 58 | 59 | var errUnsupportedType = fmt.Errorf("unsupported type") 60 | var errBlankUnsupported = fmt.Errorf("blank value not supported for type") 61 | 62 | var setters = []setter{ 63 | typeSetter, textUnmarshalerSetter, kindSetter, scanSetter, 64 | } 65 | 66 | func textUnmarshalerSetter(d interface{}, blank bool, val string, t tag) error { 67 | dtu, ok := d.(textUnmarshaler) 68 | if !ok { 69 | return errUnsupportedType 70 | } 71 | if blank { 72 | return errBlankUnsupported 73 | } 74 | return dtu.UnmarshalText([]byte(val)) 75 | } 76 | 77 | func boolSetter(d interface{}, blank bool, val string, t tag) error { 78 | if blank { 79 | reflect.ValueOf(d).Elem().Set(reflect.ValueOf(true)) 80 | return nil 81 | } 82 | b, err := types.ParseBool(val) 83 | if err == nil { 84 | reflect.ValueOf(d).Elem().Set(reflect.ValueOf(b)) 85 | } 86 | return err 87 | } 88 | 89 | func intMode(mode string) types.IntMode { 90 | var m types.IntMode 91 | if strings.ContainsAny(mode, "dD") { 92 | m |= types.Dec 93 | } 94 | if strings.ContainsAny(mode, "hH") { 95 | m |= types.Hex 96 | } 97 | if strings.ContainsAny(mode, "oO") { 98 | m |= types.Oct 99 | } 100 | return m 101 | } 102 | 103 | var typeModes = map[reflect.Type]types.IntMode{ 104 | reflect.TypeOf(int(0)): types.Dec | types.Hex, 105 | reflect.TypeOf(int8(0)): types.Dec | types.Hex, 106 | reflect.TypeOf(int16(0)): types.Dec | types.Hex, 107 | reflect.TypeOf(int32(0)): types.Dec | types.Hex, 108 | reflect.TypeOf(int64(0)): types.Dec | types.Hex, 109 | reflect.TypeOf(uint(0)): types.Dec | types.Hex, 110 | reflect.TypeOf(uint8(0)): types.Dec | types.Hex, 111 | reflect.TypeOf(uint16(0)): types.Dec | types.Hex, 112 | reflect.TypeOf(uint32(0)): types.Dec | types.Hex, 113 | reflect.TypeOf(uint64(0)): types.Dec | types.Hex, 114 | // use default mode (allow dec/hex/oct) for uintptr type 115 | reflect.TypeOf(big.Int{}): types.Dec | types.Hex, 116 | } 117 | 118 | func intModeDefault(t reflect.Type) types.IntMode { 119 | m, ok := typeModes[t] 120 | if !ok { 121 | m = types.Dec | types.Hex | types.Oct 122 | } 123 | return m 124 | } 125 | 126 | func intSetter(d interface{}, blank bool, val string, t tag) error { 127 | if blank { 128 | return errBlankUnsupported 129 | } 130 | mode := intMode(t.intMode) 131 | if mode == 0 { 132 | mode = intModeDefault(reflect.TypeOf(d).Elem()) 133 | } 134 | return types.ParseInt(d, val, mode) 135 | } 136 | 137 | func stringSetter(d interface{}, blank bool, val string, t tag) error { 138 | if blank { 139 | return errBlankUnsupported 140 | } 141 | dsp, ok := d.(*string) 142 | if !ok { 143 | return errUnsupportedType 144 | } 145 | *dsp = val 146 | return nil 147 | } 148 | 149 | var kindSetters = map[reflect.Kind]setter{ 150 | reflect.String: stringSetter, 151 | reflect.Bool: boolSetter, 152 | reflect.Int: intSetter, 153 | reflect.Int8: intSetter, 154 | reflect.Int16: intSetter, 155 | reflect.Int32: intSetter, 156 | reflect.Int64: intSetter, 157 | reflect.Uint: intSetter, 158 | reflect.Uint8: intSetter, 159 | reflect.Uint16: intSetter, 160 | reflect.Uint32: intSetter, 161 | reflect.Uint64: intSetter, 162 | reflect.Uintptr: intSetter, 163 | } 164 | 165 | var typeSetters = map[reflect.Type]setter{ 166 | reflect.TypeOf(big.Int{}): intSetter, 167 | } 168 | 169 | func typeSetter(d interface{}, blank bool, val string, tt tag) error { 170 | t := reflect.ValueOf(d).Type().Elem() 171 | setter, ok := typeSetters[t] 172 | if !ok { 173 | return errUnsupportedType 174 | } 175 | return setter(d, blank, val, tt) 176 | } 177 | 178 | func kindSetter(d interface{}, blank bool, val string, tt tag) error { 179 | k := reflect.ValueOf(d).Type().Elem().Kind() 180 | setter, ok := kindSetters[k] 181 | if !ok { 182 | return errUnsupportedType 183 | } 184 | return setter(d, blank, val, tt) 185 | } 186 | 187 | func scanSetter(d interface{}, blank bool, val string, tt tag) error { 188 | if blank { 189 | return errBlankUnsupported 190 | } 191 | return types.ScanFully(d, val, 'v') 192 | } 193 | 194 | func set(cfg interface{}, sect, sub, name string, blank bool, value string) error { 195 | vPCfg := reflect.ValueOf(cfg) 196 | if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { 197 | panic(fmt.Errorf("config must be a pointer to a struct")) 198 | } 199 | vCfg := vPCfg.Elem() 200 | vSect, _ := fieldFold(vCfg, sect) 201 | if !vSect.IsValid() { 202 | if RelaxedParserMode { 203 | return nil 204 | } 205 | return fmt.Errorf("invalid section: section %q", sect) 206 | } 207 | if vSect.Kind() == reflect.Map { 208 | vst := vSect.Type() 209 | if vst.Key().Kind() != reflect.String || 210 | vst.Elem().Kind() != reflect.Ptr || 211 | vst.Elem().Elem().Kind() != reflect.Struct { 212 | panic(fmt.Errorf("map field for section must have string keys and "+ 213 | " pointer-to-struct values: section %q", sect)) 214 | } 215 | if vSect.IsNil() { 216 | vSect.Set(reflect.MakeMap(vst)) 217 | } 218 | k := reflect.ValueOf(sub) 219 | pv := vSect.MapIndex(k) 220 | if !pv.IsValid() { 221 | vType := vSect.Type().Elem().Elem() 222 | pv = reflect.New(vType) 223 | vSect.SetMapIndex(k, pv) 224 | } 225 | vSect = pv.Elem() 226 | } else if vSect.Kind() != reflect.Struct { 227 | panic(fmt.Errorf("field for section must be a map or a struct: "+ 228 | "section %q", sect)) 229 | } else if sub != "" { 230 | return fmt.Errorf("invalid subsection: "+ 231 | "section %q subsection %q", sect, sub) 232 | } 233 | // Empty name is a special value, meaning that only the 234 | // section/subsection object is to be created, with no values set. 235 | if name == "" { 236 | return nil 237 | } 238 | vVar, t := fieldFold(vSect, name) 239 | if !vVar.IsValid() { 240 | if RelaxedParserMode { 241 | return nil 242 | } 243 | return fmt.Errorf("invalid variable: "+ 244 | "section %q subsection %q variable %q", sect, sub, name) 245 | } 246 | // vVal is either single-valued var, or newly allocated value within multi-valued var 247 | var vVal reflect.Value 248 | // multi-value if unnamed slice type 249 | isMulti := vVar.Type().Name() == "" && vVar.Kind() == reflect.Slice || 250 | vVar.Type().Name() == "" && vVar.Kind() == reflect.Ptr && vVar.Type().Elem().Name() == "" && vVar.Type().Elem().Kind() == reflect.Slice 251 | if isMulti && vVar.Kind() == reflect.Ptr { 252 | if vVar.IsNil() { 253 | vVar.Set(reflect.New(vVar.Type().Elem())) 254 | } 255 | vVar = vVar.Elem() 256 | } 257 | if isMulti && blank { 258 | vVar.Set(reflect.Zero(vVar.Type())) 259 | return nil 260 | } 261 | if isMulti { 262 | vVal = reflect.New(vVar.Type().Elem()).Elem() 263 | } else { 264 | vVal = vVar 265 | } 266 | isDeref := vVal.Type().Name() == "" && vVal.Type().Kind() == reflect.Ptr 267 | isNew := isDeref && vVal.IsNil() 268 | // vAddr is address of value to set (dereferenced & allocated as needed) 269 | var vAddr reflect.Value 270 | switch { 271 | case isNew: 272 | vAddr = reflect.New(vVal.Type().Elem()) 273 | case isDeref && !isNew: 274 | vAddr = vVal 275 | default: 276 | vAddr = vVal.Addr() 277 | } 278 | vAddrI := vAddr.Interface() 279 | err, ok := error(nil), false 280 | for _, s := range setters { 281 | err = s(vAddrI, blank, value, t) 282 | if err == nil { 283 | ok = true 284 | break 285 | } 286 | if err != errUnsupportedType { 287 | return err 288 | } 289 | } 290 | if !ok { 291 | // in case all setters returned errUnsupportedType 292 | return err 293 | } 294 | if isNew { // set reference if it was dereferenced and newly allocated 295 | vVal.Set(vAddr) 296 | } 297 | if isMulti { // append if multi-valued 298 | vVar.Set(reflect.Append(vVar, vVal)) 299 | } 300 | return nil 301 | } 302 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/testdata/gcfg_test.gcfg: -------------------------------------------------------------------------------- 1 | ; Comment line 2 | [section] 3 | name=value # comment 4 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/testdata/gcfg_unicode_test.gcfg: -------------------------------------------------------------------------------- 1 | ; Comment line 2 | [甲] 3 | 乙=丙 # comment 4 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/token/position.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // TODO(gri) consider making this a separate package outside the go directory. 6 | 7 | package token 8 | 9 | import ( 10 | "fmt" 11 | "sort" 12 | "sync" 13 | ) 14 | 15 | // ----------------------------------------------------------------------------- 16 | // Positions 17 | 18 | // Position describes an arbitrary source position 19 | // including the file, line, and column location. 20 | // A Position is valid if the line number is > 0. 21 | // 22 | type Position struct { 23 | Filename string // filename, if any 24 | Offset int // offset, starting at 0 25 | Line int // line number, starting at 1 26 | Column int // column number, starting at 1 (character count) 27 | } 28 | 29 | // IsValid returns true if the position is valid. 30 | func (pos *Position) IsValid() bool { return pos.Line > 0 } 31 | 32 | // String returns a string in one of several forms: 33 | // 34 | // file:line:column valid position with file name 35 | // line:column valid position without file name 36 | // file invalid position with file name 37 | // - invalid position without file name 38 | // 39 | func (pos Position) String() string { 40 | s := pos.Filename 41 | if pos.IsValid() { 42 | if s != "" { 43 | s += ":" 44 | } 45 | s += fmt.Sprintf("%d:%d", pos.Line, pos.Column) 46 | } 47 | if s == "" { 48 | s = "-" 49 | } 50 | return s 51 | } 52 | 53 | // Pos is a compact encoding of a source position within a file set. 54 | // It can be converted into a Position for a more convenient, but much 55 | // larger, representation. 56 | // 57 | // The Pos value for a given file is a number in the range [base, base+size], 58 | // where base and size are specified when adding the file to the file set via 59 | // AddFile. 60 | // 61 | // To create the Pos value for a specific source offset, first add 62 | // the respective file to the current file set (via FileSet.AddFile) 63 | // and then call File.Pos(offset) for that file. Given a Pos value p 64 | // for a specific file set fset, the corresponding Position value is 65 | // obtained by calling fset.Position(p). 66 | // 67 | // Pos values can be compared directly with the usual comparison operators: 68 | // If two Pos values p and q are in the same file, comparing p and q is 69 | // equivalent to comparing the respective source file offsets. If p and q 70 | // are in different files, p < q is true if the file implied by p was added 71 | // to the respective file set before the file implied by q. 72 | // 73 | type Pos int 74 | 75 | // The zero value for Pos is NoPos; there is no file and line information 76 | // associated with it, and NoPos().IsValid() is false. NoPos is always 77 | // smaller than any other Pos value. The corresponding Position value 78 | // for NoPos is the zero value for Position. 79 | // 80 | const NoPos Pos = 0 81 | 82 | // IsValid returns true if the position is valid. 83 | func (p Pos) IsValid() bool { 84 | return p != NoPos 85 | } 86 | 87 | // ----------------------------------------------------------------------------- 88 | // File 89 | 90 | // A File is a handle for a file belonging to a FileSet. 91 | // A File has a name, size, and line offset table. 92 | // 93 | type File struct { 94 | set *FileSet 95 | name string // file name as provided to AddFile 96 | base int // Pos value range for this file is [base...base+size] 97 | size int // file size as provided to AddFile 98 | 99 | // lines and infos are protected by set.mutex 100 | lines []int 101 | infos []lineInfo 102 | } 103 | 104 | // Name returns the file name of file f as registered with AddFile. 105 | func (f *File) Name() string { 106 | return f.name 107 | } 108 | 109 | // Base returns the base offset of file f as registered with AddFile. 110 | func (f *File) Base() int { 111 | return f.base 112 | } 113 | 114 | // Size returns the size of file f as registered with AddFile. 115 | func (f *File) Size() int { 116 | return f.size 117 | } 118 | 119 | // LineCount returns the number of lines in file f. 120 | func (f *File) LineCount() int { 121 | f.set.mutex.RLock() 122 | n := len(f.lines) 123 | f.set.mutex.RUnlock() 124 | return n 125 | } 126 | 127 | // AddLine adds the line offset for a new line. 128 | // The line offset must be larger than the offset for the previous line 129 | // and smaller than the file size; otherwise the line offset is ignored. 130 | // 131 | func (f *File) AddLine(offset int) { 132 | f.set.mutex.Lock() 133 | if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { 134 | f.lines = append(f.lines, offset) 135 | } 136 | f.set.mutex.Unlock() 137 | } 138 | 139 | // SetLines sets the line offsets for a file and returns true if successful. 140 | // The line offsets are the offsets of the first character of each line; 141 | // for instance for the content "ab\nc\n" the line offsets are {0, 3}. 142 | // An empty file has an empty line offset table. 143 | // Each line offset must be larger than the offset for the previous line 144 | // and smaller than the file size; otherwise SetLines fails and returns 145 | // false. 146 | // 147 | func (f *File) SetLines(lines []int) bool { 148 | // verify validity of lines table 149 | size := f.size 150 | for i, offset := range lines { 151 | if i > 0 && offset <= lines[i-1] || size <= offset { 152 | return false 153 | } 154 | } 155 | 156 | // set lines table 157 | f.set.mutex.Lock() 158 | f.lines = lines 159 | f.set.mutex.Unlock() 160 | return true 161 | } 162 | 163 | // SetLinesForContent sets the line offsets for the given file content. 164 | func (f *File) SetLinesForContent(content []byte) { 165 | var lines []int 166 | line := 0 167 | for offset, b := range content { 168 | if line >= 0 { 169 | lines = append(lines, line) 170 | } 171 | line = -1 172 | if b == '\n' { 173 | line = offset + 1 174 | } 175 | } 176 | 177 | // set lines table 178 | f.set.mutex.Lock() 179 | f.lines = lines 180 | f.set.mutex.Unlock() 181 | } 182 | 183 | // A lineInfo object describes alternative file and line number 184 | // information (such as provided via a //line comment in a .go 185 | // file) for a given file offset. 186 | type lineInfo struct { 187 | // fields are exported to make them accessible to gob 188 | Offset int 189 | Filename string 190 | Line int 191 | } 192 | 193 | // AddLineInfo adds alternative file and line number information for 194 | // a given file offset. The offset must be larger than the offset for 195 | // the previously added alternative line info and smaller than the 196 | // file size; otherwise the information is ignored. 197 | // 198 | // AddLineInfo is typically used to register alternative position 199 | // information for //line filename:line comments in source files. 200 | // 201 | func (f *File) AddLineInfo(offset int, filename string, line int) { 202 | f.set.mutex.Lock() 203 | if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { 204 | f.infos = append(f.infos, lineInfo{offset, filename, line}) 205 | } 206 | f.set.mutex.Unlock() 207 | } 208 | 209 | // Pos returns the Pos value for the given file offset; 210 | // the offset must be <= f.Size(). 211 | // f.Pos(f.Offset(p)) == p. 212 | // 213 | func (f *File) Pos(offset int) Pos { 214 | if offset > f.size { 215 | panic("illegal file offset") 216 | } 217 | return Pos(f.base + offset) 218 | } 219 | 220 | // Offset returns the offset for the given file position p; 221 | // p must be a valid Pos value in that file. 222 | // f.Offset(f.Pos(offset)) == offset. 223 | // 224 | func (f *File) Offset(p Pos) int { 225 | if int(p) < f.base || int(p) > f.base+f.size { 226 | panic("illegal Pos value") 227 | } 228 | return int(p) - f.base 229 | } 230 | 231 | // Line returns the line number for the given file position p; 232 | // p must be a Pos value in that file or NoPos. 233 | // 234 | func (f *File) Line(p Pos) int { 235 | // TODO(gri) this can be implemented much more efficiently 236 | return f.Position(p).Line 237 | } 238 | 239 | func searchLineInfos(a []lineInfo, x int) int { 240 | return sort.Search(len(a), func(i int) bool { return a[i].Offset > x }) - 1 241 | } 242 | 243 | // info returns the file name, line, and column number for a file offset. 244 | func (f *File) info(offset int) (filename string, line, column int) { 245 | filename = f.name 246 | if i := searchInts(f.lines, offset); i >= 0 { 247 | line, column = i+1, offset-f.lines[i]+1 248 | } 249 | if len(f.infos) > 0 { 250 | // almost no files have extra line infos 251 | if i := searchLineInfos(f.infos, offset); i >= 0 { 252 | alt := &f.infos[i] 253 | filename = alt.Filename 254 | if i := searchInts(f.lines, alt.Offset); i >= 0 { 255 | line += alt.Line - i - 1 256 | } 257 | } 258 | } 259 | return 260 | } 261 | 262 | func (f *File) position(p Pos) (pos Position) { 263 | offset := int(p) - f.base 264 | pos.Offset = offset 265 | pos.Filename, pos.Line, pos.Column = f.info(offset) 266 | return 267 | } 268 | 269 | // Position returns the Position value for the given file position p; 270 | // p must be a Pos value in that file or NoPos. 271 | // 272 | func (f *File) Position(p Pos) (pos Position) { 273 | if p != NoPos { 274 | if int(p) < f.base || int(p) > f.base+f.size { 275 | panic("illegal Pos value") 276 | } 277 | pos = f.position(p) 278 | } 279 | return 280 | } 281 | 282 | // ----------------------------------------------------------------------------- 283 | // FileSet 284 | 285 | // A FileSet represents a set of source files. 286 | // Methods of file sets are synchronized; multiple goroutines 287 | // may invoke them concurrently. 288 | // 289 | type FileSet struct { 290 | mutex sync.RWMutex // protects the file set 291 | base int // base offset for the next file 292 | files []*File // list of files in the order added to the set 293 | last *File // cache of last file looked up 294 | } 295 | 296 | // NewFileSet creates a new file set. 297 | func NewFileSet() *FileSet { 298 | s := new(FileSet) 299 | s.base = 1 // 0 == NoPos 300 | return s 301 | } 302 | 303 | // Base returns the minimum base offset that must be provided to 304 | // AddFile when adding the next file. 305 | // 306 | func (s *FileSet) Base() int { 307 | s.mutex.RLock() 308 | b := s.base 309 | s.mutex.RUnlock() 310 | return b 311 | 312 | } 313 | 314 | // AddFile adds a new file with a given filename, base offset, and file size 315 | // to the file set s and returns the file. Multiple files may have the same 316 | // name. The base offset must not be smaller than the FileSet's Base(), and 317 | // size must not be negative. 318 | // 319 | // Adding the file will set the file set's Base() value to base + size + 1 320 | // as the minimum base value for the next file. The following relationship 321 | // exists between a Pos value p for a given file offset offs: 322 | // 323 | // int(p) = base + offs 324 | // 325 | // with offs in the range [0, size] and thus p in the range [base, base+size]. 326 | // For convenience, File.Pos may be used to create file-specific position 327 | // values from a file offset. 328 | // 329 | func (s *FileSet) AddFile(filename string, base, size int) *File { 330 | s.mutex.Lock() 331 | defer s.mutex.Unlock() 332 | if base < s.base || size < 0 { 333 | panic("illegal base or size") 334 | } 335 | // base >= s.base && size >= 0 336 | f := &File{s, filename, base, size, []int{0}, nil} 337 | base += size + 1 // +1 because EOF also has a position 338 | if base < 0 { 339 | panic("token.Pos offset overflow (> 2G of source code in file set)") 340 | } 341 | // add the file to the file set 342 | s.base = base 343 | s.files = append(s.files, f) 344 | s.last = f 345 | return f 346 | } 347 | 348 | // Iterate calls f for the files in the file set in the order they were added 349 | // until f returns false. 350 | // 351 | func (s *FileSet) Iterate(f func(*File) bool) { 352 | for i := 0; ; i++ { 353 | var file *File 354 | s.mutex.RLock() 355 | if i < len(s.files) { 356 | file = s.files[i] 357 | } 358 | s.mutex.RUnlock() 359 | if file == nil || !f(file) { 360 | break 361 | } 362 | } 363 | } 364 | 365 | func searchFiles(a []*File, x int) int { 366 | return sort.Search(len(a), func(i int) bool { return a[i].base > x }) - 1 367 | } 368 | 369 | func (s *FileSet) file(p Pos) *File { 370 | // common case: p is in last file 371 | if f := s.last; f != nil && f.base <= int(p) && int(p) <= f.base+f.size { 372 | return f 373 | } 374 | // p is not in last file - search all files 375 | if i := searchFiles(s.files, int(p)); i >= 0 { 376 | f := s.files[i] 377 | // f.base <= int(p) by definition of searchFiles 378 | if int(p) <= f.base+f.size { 379 | s.last = f 380 | return f 381 | } 382 | } 383 | return nil 384 | } 385 | 386 | // File returns the file that contains the position p. 387 | // If no such file is found (for instance for p == NoPos), 388 | // the result is nil. 389 | // 390 | func (s *FileSet) File(p Pos) (f *File) { 391 | if p != NoPos { 392 | s.mutex.RLock() 393 | f = s.file(p) 394 | s.mutex.RUnlock() 395 | } 396 | return 397 | } 398 | 399 | // Position converts a Pos in the fileset into a general Position. 400 | func (s *FileSet) Position(p Pos) (pos Position) { 401 | if p != NoPos { 402 | s.mutex.RLock() 403 | if f := s.file(p); f != nil { 404 | pos = f.position(p) 405 | } 406 | s.mutex.RUnlock() 407 | } 408 | return 409 | } 410 | 411 | // ----------------------------------------------------------------------------- 412 | // Helper functions 413 | 414 | func searchInts(a []int, x int) int { 415 | // This function body is a manually inlined version of: 416 | // 417 | // return sort.Search(len(a), func(i int) bool { return a[i] > x }) - 1 418 | // 419 | // With better compiler optimizations, this may not be needed in the 420 | // future, but at the moment this change improves the go/printer 421 | // benchmark performance by ~30%. This has a direct impact on the 422 | // speed of gofmt and thus seems worthwhile (2011-04-29). 423 | // TODO(gri): Remove this when compilers have caught up. 424 | i, j := 0, len(a) 425 | for i < j { 426 | h := i + (j-i)/2 // avoid overflow when computing h 427 | // i ≤ h < j 428 | if a[h] <= x { 429 | i = h + 1 430 | } else { 431 | j = h 432 | } 433 | } 434 | return i - 1 435 | } 436 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/token/position_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | import ( 8 | "fmt" 9 | "testing" 10 | ) 11 | 12 | func checkPos(t *testing.T, msg string, p, q Position) { 13 | if p.Filename != q.Filename { 14 | t.Errorf("%s: expected filename = %q; got %q", msg, q.Filename, p.Filename) 15 | } 16 | if p.Offset != q.Offset { 17 | t.Errorf("%s: expected offset = %d; got %d", msg, q.Offset, p.Offset) 18 | } 19 | if p.Line != q.Line { 20 | t.Errorf("%s: expected line = %d; got %d", msg, q.Line, p.Line) 21 | } 22 | if p.Column != q.Column { 23 | t.Errorf("%s: expected column = %d; got %d", msg, q.Column, p.Column) 24 | } 25 | } 26 | 27 | func TestNoPos(t *testing.T) { 28 | if NoPos.IsValid() { 29 | t.Errorf("NoPos should not be valid") 30 | } 31 | var fset *FileSet 32 | checkPos(t, "nil NoPos", fset.Position(NoPos), Position{}) 33 | fset = NewFileSet() 34 | checkPos(t, "fset NoPos", fset.Position(NoPos), Position{}) 35 | } 36 | 37 | var tests = []struct { 38 | filename string 39 | source []byte // may be nil 40 | size int 41 | lines []int 42 | }{ 43 | {"a", []byte{}, 0, []int{}}, 44 | {"b", []byte("01234"), 5, []int{0}}, 45 | {"c", []byte("\n\n\n\n\n\n\n\n\n"), 9, []int{0, 1, 2, 3, 4, 5, 6, 7, 8}}, 46 | {"d", nil, 100, []int{0, 5, 10, 20, 30, 70, 71, 72, 80, 85, 90, 99}}, 47 | {"e", nil, 777, []int{0, 80, 100, 120, 130, 180, 267, 455, 500, 567, 620}}, 48 | {"f", []byte("package p\n\nimport \"fmt\""), 23, []int{0, 10, 11}}, 49 | {"g", []byte("package p\n\nimport \"fmt\"\n"), 24, []int{0, 10, 11}}, 50 | {"h", []byte("package p\n\nimport \"fmt\"\n "), 25, []int{0, 10, 11, 24}}, 51 | } 52 | 53 | func linecol(lines []int, offs int) (int, int) { 54 | prevLineOffs := 0 55 | for line, lineOffs := range lines { 56 | if offs < lineOffs { 57 | return line, offs - prevLineOffs + 1 58 | } 59 | prevLineOffs = lineOffs 60 | } 61 | return len(lines), offs - prevLineOffs + 1 62 | } 63 | 64 | func verifyPositions(t *testing.T, fset *FileSet, f *File, lines []int) { 65 | for offs := 0; offs < f.Size(); offs++ { 66 | p := f.Pos(offs) 67 | offs2 := f.Offset(p) 68 | if offs2 != offs { 69 | t.Errorf("%s, Offset: expected offset %d; got %d", f.Name(), offs, offs2) 70 | } 71 | line, col := linecol(lines, offs) 72 | msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 73 | checkPos(t, msg, f.Position(f.Pos(offs)), Position{f.Name(), offs, line, col}) 74 | checkPos(t, msg, fset.Position(p), Position{f.Name(), offs, line, col}) 75 | } 76 | } 77 | 78 | func makeTestSource(size int, lines []int) []byte { 79 | src := make([]byte, size) 80 | for _, offs := range lines { 81 | if offs > 0 { 82 | src[offs-1] = '\n' 83 | } 84 | } 85 | return src 86 | } 87 | 88 | func TestPositions(t *testing.T) { 89 | const delta = 7 // a non-zero base offset increment 90 | fset := NewFileSet() 91 | for _, test := range tests { 92 | // verify consistency of test case 93 | if test.source != nil && len(test.source) != test.size { 94 | t.Errorf("%s: inconsistent test case: expected file size %d; got %d", test.filename, test.size, len(test.source)) 95 | } 96 | 97 | // add file and verify name and size 98 | f := fset.AddFile(test.filename, fset.Base()+delta, test.size) 99 | if f.Name() != test.filename { 100 | t.Errorf("expected filename %q; got %q", test.filename, f.Name()) 101 | } 102 | if f.Size() != test.size { 103 | t.Errorf("%s: expected file size %d; got %d", f.Name(), test.size, f.Size()) 104 | } 105 | if fset.File(f.Pos(0)) != f { 106 | t.Errorf("%s: f.Pos(0) was not found in f", f.Name()) 107 | } 108 | 109 | // add lines individually and verify all positions 110 | for i, offset := range test.lines { 111 | f.AddLine(offset) 112 | if f.LineCount() != i+1 { 113 | t.Errorf("%s, AddLine: expected line count %d; got %d", f.Name(), i+1, f.LineCount()) 114 | } 115 | // adding the same offset again should be ignored 116 | f.AddLine(offset) 117 | if f.LineCount() != i+1 { 118 | t.Errorf("%s, AddLine: expected unchanged line count %d; got %d", f.Name(), i+1, f.LineCount()) 119 | } 120 | verifyPositions(t, fset, f, test.lines[0:i+1]) 121 | } 122 | 123 | // add lines with SetLines and verify all positions 124 | if ok := f.SetLines(test.lines); !ok { 125 | t.Errorf("%s: SetLines failed", f.Name()) 126 | } 127 | if f.LineCount() != len(test.lines) { 128 | t.Errorf("%s, SetLines: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount()) 129 | } 130 | verifyPositions(t, fset, f, test.lines) 131 | 132 | // add lines with SetLinesForContent and verify all positions 133 | src := test.source 134 | if src == nil { 135 | // no test source available - create one from scratch 136 | src = makeTestSource(test.size, test.lines) 137 | } 138 | f.SetLinesForContent(src) 139 | if f.LineCount() != len(test.lines) { 140 | t.Errorf("%s, SetLinesForContent: expected line count %d; got %d", f.Name(), len(test.lines), f.LineCount()) 141 | } 142 | verifyPositions(t, fset, f, test.lines) 143 | } 144 | } 145 | 146 | func TestLineInfo(t *testing.T) { 147 | fset := NewFileSet() 148 | f := fset.AddFile("foo", fset.Base(), 500) 149 | lines := []int{0, 42, 77, 100, 210, 220, 277, 300, 333, 401} 150 | // add lines individually and provide alternative line information 151 | for _, offs := range lines { 152 | f.AddLine(offs) 153 | f.AddLineInfo(offs, "bar", 42) 154 | } 155 | // verify positions for all offsets 156 | for offs := 0; offs <= f.Size(); offs++ { 157 | p := f.Pos(offs) 158 | _, col := linecol(lines, offs) 159 | msg := fmt.Sprintf("%s (offs = %d, p = %d)", f.Name(), offs, p) 160 | checkPos(t, msg, f.Position(f.Pos(offs)), Position{"bar", offs, 42, col}) 161 | checkPos(t, msg, fset.Position(p), Position{"bar", offs, 42, col}) 162 | } 163 | } 164 | 165 | func TestFiles(t *testing.T) { 166 | fset := NewFileSet() 167 | for i, test := range tests { 168 | fset.AddFile(test.filename, fset.Base(), test.size) 169 | j := 0 170 | fset.Iterate(func(f *File) bool { 171 | if f.Name() != tests[j].filename { 172 | t.Errorf("expected filename = %s; got %s", tests[j].filename, f.Name()) 173 | } 174 | j++ 175 | return true 176 | }) 177 | if j != i+1 { 178 | t.Errorf("expected %d files; got %d", i+1, j) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/token/serialize.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | type serializedFile struct { 8 | // fields correspond 1:1 to fields with same (lower-case) name in File 9 | Name string 10 | Base int 11 | Size int 12 | Lines []int 13 | Infos []lineInfo 14 | } 15 | 16 | type serializedFileSet struct { 17 | Base int 18 | Files []serializedFile 19 | } 20 | 21 | // Read calls decode to deserialize a file set into s; s must not be nil. 22 | func (s *FileSet) Read(decode func(interface{}) error) error { 23 | var ss serializedFileSet 24 | if err := decode(&ss); err != nil { 25 | return err 26 | } 27 | 28 | s.mutex.Lock() 29 | s.base = ss.Base 30 | files := make([]*File, len(ss.Files)) 31 | for i := 0; i < len(ss.Files); i++ { 32 | f := &ss.Files[i] 33 | files[i] = &File{s, f.Name, f.Base, f.Size, f.Lines, f.Infos} 34 | } 35 | s.files = files 36 | s.last = nil 37 | s.mutex.Unlock() 38 | 39 | return nil 40 | } 41 | 42 | // Write calls encode to serialize the file set s. 43 | func (s *FileSet) Write(encode func(interface{}) error) error { 44 | var ss serializedFileSet 45 | 46 | s.mutex.Lock() 47 | ss.Base = s.base 48 | files := make([]serializedFile, len(s.files)) 49 | for i, f := range s.files { 50 | files[i] = serializedFile{f.name, f.base, f.size, f.lines, f.infos} 51 | } 52 | ss.Files = files 53 | s.mutex.Unlock() 54 | 55 | return encode(ss) 56 | } 57 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/token/serialize_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package token 6 | 7 | import ( 8 | "bytes" 9 | "encoding/gob" 10 | "fmt" 11 | "testing" 12 | ) 13 | 14 | // equal returns nil if p and q describe the same file set; 15 | // otherwise it returns an error describing the discrepancy. 16 | func equal(p, q *FileSet) error { 17 | if p == q { 18 | // avoid deadlock if p == q 19 | return nil 20 | } 21 | 22 | // not strictly needed for the test 23 | p.mutex.Lock() 24 | q.mutex.Lock() 25 | defer q.mutex.Unlock() 26 | defer p.mutex.Unlock() 27 | 28 | if p.base != q.base { 29 | return fmt.Errorf("different bases: %d != %d", p.base, q.base) 30 | } 31 | 32 | if len(p.files) != len(q.files) { 33 | return fmt.Errorf("different number of files: %d != %d", len(p.files), len(q.files)) 34 | } 35 | 36 | for i, f := range p.files { 37 | g := q.files[i] 38 | if f.set != p { 39 | return fmt.Errorf("wrong fileset for %q", f.name) 40 | } 41 | if g.set != q { 42 | return fmt.Errorf("wrong fileset for %q", g.name) 43 | } 44 | if f.name != g.name { 45 | return fmt.Errorf("different filenames: %q != %q", f.name, g.name) 46 | } 47 | if f.base != g.base { 48 | return fmt.Errorf("different base for %q: %d != %d", f.name, f.base, g.base) 49 | } 50 | if f.size != g.size { 51 | return fmt.Errorf("different size for %q: %d != %d", f.name, f.size, g.size) 52 | } 53 | for j, l := range f.lines { 54 | m := g.lines[j] 55 | if l != m { 56 | return fmt.Errorf("different offsets for %q", f.name) 57 | } 58 | } 59 | for j, l := range f.infos { 60 | m := g.infos[j] 61 | if l.Offset != m.Offset || l.Filename != m.Filename || l.Line != m.Line { 62 | return fmt.Errorf("different infos for %q", f.name) 63 | } 64 | } 65 | } 66 | 67 | // we don't care about .last - it's just a cache 68 | return nil 69 | } 70 | 71 | func checkSerialize(t *testing.T, p *FileSet) { 72 | var buf bytes.Buffer 73 | encode := func(x interface{}) error { 74 | return gob.NewEncoder(&buf).Encode(x) 75 | } 76 | if err := p.Write(encode); err != nil { 77 | t.Errorf("writing fileset failed: %s", err) 78 | return 79 | } 80 | q := NewFileSet() 81 | decode := func(x interface{}) error { 82 | return gob.NewDecoder(&buf).Decode(x) 83 | } 84 | if err := q.Read(decode); err != nil { 85 | t.Errorf("reading fileset failed: %s", err) 86 | return 87 | } 88 | if err := equal(p, q); err != nil { 89 | t.Errorf("filesets not identical: %s", err) 90 | } 91 | } 92 | 93 | func TestSerialization(t *testing.T) { 94 | p := NewFileSet() 95 | checkSerialize(t, p) 96 | // add some files 97 | for i := 0; i < 10; i++ { 98 | f := p.AddFile(fmt.Sprintf("file%d", i), p.Base()+i, i*100) 99 | checkSerialize(t, p) 100 | // add some lines and alternative file infos 101 | line := 1000 102 | for offs := 0; offs < f.Size(); offs += 40 + i { 103 | f.AddLine(offs) 104 | if offs%7 == 0 { 105 | f.AddLineInfo(offs, fmt.Sprintf("file%d", offs), line) 106 | line += 33 107 | } 108 | } 109 | checkSerialize(t, p) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/token/token.go: -------------------------------------------------------------------------------- 1 | // Copyright 2009 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package token defines constants representing the lexical tokens of the gcfg 6 | // configuration syntax and basic operations on tokens (printing, predicates). 7 | // 8 | // Note that the API for the token package may change to accommodate new 9 | // features or implementation changes in gcfg. 10 | // 11 | package token 12 | 13 | import "strconv" 14 | 15 | // Token is the set of lexical tokens of the gcfg configuration syntax. 16 | type Token int 17 | 18 | // The list of tokens. 19 | const ( 20 | // Special tokens 21 | ILLEGAL Token = iota 22 | EOF 23 | COMMENT 24 | 25 | literal_beg 26 | // Identifiers and basic type literals 27 | // (these tokens stand for classes of literals) 28 | IDENT // section-name, variable-name 29 | STRING // "subsection-name", variable value 30 | literal_end 31 | 32 | operator_beg 33 | // Operators and delimiters 34 | ASSIGN // = 35 | LBRACK // [ 36 | RBRACK // ] 37 | EOL // \n 38 | operator_end 39 | ) 40 | 41 | var tokens = [...]string{ 42 | ILLEGAL: "ILLEGAL", 43 | 44 | EOF: "EOF", 45 | COMMENT: "COMMENT", 46 | 47 | IDENT: "IDENT", 48 | STRING: "STRING", 49 | 50 | ASSIGN: "=", 51 | LBRACK: "[", 52 | RBRACK: "]", 53 | EOL: "\n", 54 | } 55 | 56 | // String returns the string corresponding to the token tok. 57 | // For operators and delimiters, the string is the actual token character 58 | // sequence (e.g., for the token ASSIGN, the string is "="). For all other 59 | // tokens the string corresponds to the token constant name (e.g. for the 60 | // token IDENT, the string is "IDENT"). 61 | // 62 | func (tok Token) String() string { 63 | s := "" 64 | if 0 <= tok && tok < Token(len(tokens)) { 65 | s = tokens[tok] 66 | } 67 | if s == "" { 68 | s = "token(" + strconv.Itoa(int(tok)) + ")" 69 | } 70 | return s 71 | } 72 | 73 | // Predicates 74 | 75 | // IsLiteral returns true for tokens corresponding to identifiers 76 | // and basic type literals; it returns false otherwise. 77 | // 78 | func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } 79 | 80 | // IsOperator returns true for tokens corresponding to operators and 81 | // delimiters; it returns false otherwise. 82 | // 83 | func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } 84 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/bool.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | // BoolValues defines the name and value mappings for ParseBool. 4 | var BoolValues = map[string]interface{}{ 5 | "true": true, "yes": true, "on": true, "1": true, 6 | "false": false, "no": false, "off": false, "0": false, 7 | } 8 | 9 | var boolParser = func() *EnumParser { 10 | ep := &EnumParser{} 11 | ep.AddVals(BoolValues) 12 | return ep 13 | }() 14 | 15 | // ParseBool parses bool values according to the definitions in BoolValues. 16 | // Parsing is case-insensitive. 17 | func ParseBool(s string) (bool, error) { 18 | v, err := boolParser.Parse(s) 19 | if err != nil { 20 | return false, err 21 | } 22 | return v.(bool), nil 23 | } 24 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/doc.go: -------------------------------------------------------------------------------- 1 | // Package types defines helpers for type conversions. 2 | // 3 | // The API for this package is not finalized yet. 4 | package types 5 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/enum.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // EnumParser parses "enum" values; i.e. a predefined set of strings to 10 | // predefined values. 11 | type EnumParser struct { 12 | Type string // type name; if not set, use type of first value added 13 | CaseMatch bool // if true, matching of strings is case-sensitive 14 | // PrefixMatch bool 15 | vals map[string]interface{} 16 | } 17 | 18 | // AddVals adds strings and values to an EnumParser. 19 | func (ep *EnumParser) AddVals(vals map[string]interface{}) { 20 | if ep.vals == nil { 21 | ep.vals = make(map[string]interface{}) 22 | } 23 | for k, v := range vals { 24 | if ep.Type == "" { 25 | ep.Type = reflect.TypeOf(v).Name() 26 | } 27 | if !ep.CaseMatch { 28 | k = strings.ToLower(k) 29 | } 30 | ep.vals[k] = v 31 | } 32 | } 33 | 34 | // Parse parses the string and returns the value or an error. 35 | func (ep EnumParser) Parse(s string) (interface{}, error) { 36 | if !ep.CaseMatch { 37 | s = strings.ToLower(s) 38 | } 39 | v, ok := ep.vals[s] 40 | if !ok { 41 | return false, fmt.Errorf("failed to parse %s %#q", ep.Type, s) 42 | } 43 | return v, nil 44 | } 45 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/enum_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestEnumParserBool(t *testing.T) { 8 | for _, tt := range []struct { 9 | val string 10 | res bool 11 | ok bool 12 | }{ 13 | {val: "tRuE", res: true, ok: true}, 14 | {val: "False", res: false, ok: true}, 15 | {val: "t", ok: false}, 16 | } { 17 | b, err := ParseBool(tt.val) 18 | switch { 19 | case tt.ok && err != nil: 20 | t.Errorf("%q: got error %v, want %v", tt.val, err, tt.res) 21 | case !tt.ok && err == nil: 22 | t.Errorf("%q: got %v, want error", tt.val, b) 23 | case tt.ok && b != tt.res: 24 | t.Errorf("%q: got %v, want %v", tt.val, b, tt.res) 25 | default: 26 | t.Logf("%q: got %v, %v", tt.val, b, err) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/int.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // An IntMode is a mode for parsing integer values, representing a set of 9 | // accepted bases. 10 | type IntMode uint8 11 | 12 | // IntMode values for ParseInt; can be combined using binary or. 13 | const ( 14 | Dec IntMode = 1 << iota 15 | Hex 16 | Oct 17 | ) 18 | 19 | // String returns a string representation of IntMode; e.g. `IntMode(Dec|Hex)`. 20 | func (m IntMode) String() string { 21 | var modes []string 22 | if m&Dec != 0 { 23 | modes = append(modes, "Dec") 24 | } 25 | if m&Hex != 0 { 26 | modes = append(modes, "Hex") 27 | } 28 | if m&Oct != 0 { 29 | modes = append(modes, "Oct") 30 | } 31 | return "IntMode(" + strings.Join(modes, "|") + ")" 32 | } 33 | 34 | var errIntAmbig = fmt.Errorf("ambiguous integer value; must include '0' prefix") 35 | 36 | func prefix0(val string) bool { 37 | return strings.HasPrefix(val, "0") || strings.HasPrefix(val, "-0") 38 | } 39 | 40 | func prefix0x(val string) bool { 41 | return strings.HasPrefix(val, "0x") || strings.HasPrefix(val, "-0x") 42 | } 43 | 44 | // ParseInt parses val using mode into intptr, which must be a pointer to an 45 | // integer kind type. Non-decimal value require prefix `0` or `0x` in the cases 46 | // when mode permits ambiguity of base; otherwise the prefix can be omitted. 47 | func ParseInt(intptr interface{}, val string, mode IntMode) error { 48 | val = strings.TrimSpace(val) 49 | verb := byte(0) 50 | switch mode { 51 | case Dec: 52 | verb = 'd' 53 | case Dec + Hex: 54 | if prefix0x(val) { 55 | verb = 'v' 56 | } else { 57 | verb = 'd' 58 | } 59 | case Dec + Oct: 60 | if prefix0(val) && !prefix0x(val) { 61 | verb = 'v' 62 | } else { 63 | verb = 'd' 64 | } 65 | case Dec + Hex + Oct: 66 | verb = 'v' 67 | case Hex: 68 | if prefix0x(val) { 69 | verb = 'v' 70 | } else { 71 | verb = 'x' 72 | } 73 | case Oct: 74 | verb = 'o' 75 | case Hex + Oct: 76 | if prefix0(val) { 77 | verb = 'v' 78 | } else { 79 | return errIntAmbig 80 | } 81 | } 82 | if verb == 0 { 83 | panic("unsupported mode") 84 | } 85 | return ScanFully(intptr, val, verb) 86 | } 87 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/int_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func elem(p interface{}) interface{} { 9 | return reflect.ValueOf(p).Elem().Interface() 10 | } 11 | 12 | func TestParseInt(t *testing.T) { 13 | for _, tt := range []struct { 14 | val string 15 | mode IntMode 16 | exp interface{} 17 | ok bool 18 | }{ 19 | {"0", Dec, int(0), true}, 20 | {"10", Dec, int(10), true}, 21 | {"-10", Dec, int(-10), true}, 22 | {"x", Dec, int(0), false}, 23 | {"0xa", Hex, int(0xa), true}, 24 | {"a", Hex, int(0xa), true}, 25 | {"10", Hex, int(0x10), true}, 26 | {"-0xa", Hex, int(-0xa), true}, 27 | {"0x", Hex, int(0x0), true}, // Scanf doesn't require digit behind 0x 28 | {"-0x", Hex, int(0x0), true}, // Scanf doesn't require digit behind 0x 29 | {"-a", Hex, int(-0xa), true}, 30 | {"-10", Hex, int(-0x10), true}, 31 | {"x", Hex, int(0), false}, 32 | {"10", Oct, int(010), true}, 33 | {"010", Oct, int(010), true}, 34 | {"-10", Oct, int(-010), true}, 35 | {"-010", Oct, int(-010), true}, 36 | {"10", Dec | Hex, int(10), true}, 37 | {"010", Dec | Hex, int(10), true}, 38 | {"0x10", Dec | Hex, int(0x10), true}, 39 | {"10", Dec | Oct, int(10), true}, 40 | {"010", Dec | Oct, int(010), true}, 41 | {"0x10", Dec | Oct, int(0), false}, 42 | {"10", Hex | Oct, int(0), false}, // need prefix to distinguish Hex/Oct 43 | {"010", Hex | Oct, int(010), true}, 44 | {"0x10", Hex | Oct, int(0x10), true}, 45 | {"10", Dec | Hex | Oct, int(10), true}, 46 | {"010", Dec | Hex | Oct, int(010), true}, 47 | {"0x10", Dec | Hex | Oct, int(0x10), true}, 48 | } { 49 | typ := reflect.TypeOf(tt.exp) 50 | res := reflect.New(typ).Interface() 51 | err := ParseInt(res, tt.val, tt.mode) 52 | switch { 53 | case tt.ok && err != nil: 54 | t.Errorf("ParseInt(%v, %#v, %v): fail; got error %v, want ok", 55 | typ, tt.val, tt.mode, err) 56 | case !tt.ok && err == nil: 57 | t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want error", 58 | typ, tt.val, tt.mode, elem(res)) 59 | case tt.ok && !reflect.DeepEqual(elem(res), tt.exp): 60 | t.Errorf("ParseInt(%v, %#v, %v): fail; got %v, want %v", 61 | typ, tt.val, tt.mode, elem(res), tt.exp) 62 | default: 63 | t.Logf("ParseInt(%v, %#v, %s): pass; got %v, error %v", 64 | typ, tt.val, tt.mode, elem(res), err) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/scan.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "reflect" 7 | ) 8 | 9 | // ScanFully uses fmt.Sscanf with verb to fully scan val into ptr. 10 | func ScanFully(ptr interface{}, val string, verb byte) error { 11 | t := reflect.ValueOf(ptr).Elem().Type() 12 | // attempt to read extra bytes to make sure the value is consumed 13 | var b []byte 14 | n, err := fmt.Sscanf(val, "%"+string(verb)+"%s", ptr, &b) 15 | switch { 16 | case n < 1 || n == 1 && err != io.EOF: 17 | return fmt.Errorf("failed to parse %q as %v: %v", val, t, err) 18 | case n > 1: 19 | return fmt.Errorf("failed to parse %q as %v: extra characters %q", val, t, string(b)) 20 | } 21 | // n == 1 && err == io.EOF 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /vendor/gopkg.in/gcfg.v1/types/scan_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestScanFully(t *testing.T) { 9 | for _, tt := range []struct { 10 | val string 11 | verb byte 12 | res interface{} 13 | ok bool 14 | }{ 15 | {"a", 'v', int(0), false}, 16 | {"0x", 'v', int(0), true}, 17 | {"0x", 'd', int(0), false}, 18 | } { 19 | d := reflect.New(reflect.TypeOf(tt.res)).Interface() 20 | err := ScanFully(d, tt.val, tt.verb) 21 | switch { 22 | case tt.ok && err != nil: 23 | t.Errorf("ScanFully(%T, %q, '%c'): want ok, got error %v", 24 | d, tt.val, tt.verb, err) 25 | case !tt.ok && err == nil: 26 | t.Errorf("ScanFully(%T, %q, '%c'): want error, got %v", 27 | d, tt.val, tt.verb, elem(d)) 28 | case tt.ok && err == nil && !reflect.DeepEqual(tt.res, elem(d)): 29 | t.Errorf("ScanFully(%T, %q, '%c'): want %v, got %v", 30 | d, tt.val, tt.verb, tt.res, elem(d)) 31 | default: 32 | t.Logf("ScanFully(%T, %q, '%c') = %v; *ptr==%v", 33 | d, tt.val, tt.verb, err, elem(d)) 34 | } 35 | } 36 | } 37 | --------------------------------------------------------------------------------